Create a calendar from dates in multiple structure fields

Hi dear community,

I’m currently coding the website of a small french movie theater (already online at https://cinemalasalamandre.fr). In the panel, each movie has its own page with screening schedules listed in a structure field that looks like:

schedules:
  type: structure
  fields:
    day:
      type: date
      display: DD/MM/YYYY
    hour:
      type: time
      display: H:mm

In the front, I’d like to create a page displaying a calendar with movies grouped by month, by day and by four time slots (depending on when the screenings begin). The generated HTML for each month should be:

<h2>July 2021</h2>
<ul class="days">
  <li>
    <h3>Wednesday 7</h3>
    <ul class="slots">
      <li>
        <h4>From 14:00 to 16:00</h4>
        <p>Movie title</p>
        <p>Movie title</p>
      </li>
      <li>
        <h4>From 16:00 to 18:00</h4>
        <p>Movie title</p>
        <p>Movie title</p>
        <p>Movie title</p>
      </li>
      <li>
        <h4>From 18:00 to 20:00</h4>
        <p>Movie title</p>
        <p>Movie title</p>
        <p>Movie title</p>
      </li>
      <li>
        <h4>From 20:00 to 22:00</h4>
        <p>Movie title</p>
        <p>Movie title</p>
        <p>Movie title</p>
        <p>Movie title</p>
      </li>
    </ul>
  </li>
  <li>
    <h3>Thursday 8</h3>
    …

Currently, all the scheduled movies are stored in a collection called ‘scheduledmovies’ (original isn’t it :sweat_smile:).

I really don’t know how to code properly this calendar as my PHP skills are limited when it comes to complex foreach loops. Is there a kind dev who could help me please?

Thank you :pray:

Kirby has a group() function that helps you group a collection into whatever categories they fall into.

You can find an example of how to use it for pages:

And more here:

1 Like

That’s a good start! Thanks for pointing to these grouping functions pixelijn.
I’m sure I’ll find a way to manage this, even if it takes a week and my code is dirty :joy:

1 Like

On a side note, you may find my Recurr plugin useful. It is for recurring dates like events and film showings. GitHub - HashandSalt/kirby-recurr: Plugin for listing recurring dates

Its also capable of generating standard RRules which you can feed straight into something like FullCalendar on the front end. https://fullcalendar.io/

I’ve played around with the grouping functions but I miss the logic to make it work as expected. My brain is giving me pain right now :face_with_head_bandage:

Should I first create a new collection from ALL the schedules listed in ALL movies, group it per month, then filter the ‘scheduledmovies’ collection for each day and time slot?

Or is it better/possible to group the schedules from the ‘scheduledmovies’ collection directly per time slot > day > month?

I’m completely lost, sorry…

Thanks Jim for your input. But I’d prefer to create the calendar from scratch instead of relying on an existing plugin. This way I can improve my PHP skills and, more important, because the final result will not be used as a standard calendar. It will become a tool for users to apply as volunteers in the movie theater. See attached a preview of the page I’d like to build.

Sure… the plugin just gives you a PHP array of all the dates and times. How you display those is up to you.

1 Like

Yes, first you need a collection of all structure items filtered for the given month.

And then this collection needs to be grouped by day and month.

Hi dear Kirby community!

I did not expect this to take me sooooo long (9 days to type the following lines) but the calendar starts to look like the capture above :smiley:

However, I still have an issue which drives me crazy… I will explain it below. But first, I need to explain a bit my (ugly) functions so you get the context.

  1. I created a new collection including ALL upcoming screening dates:
<?php
$alldates = new Collection();
$scheduledmovies = $kirby->collection('scheduledmovies');
foreach($scheduledmovies as $scheduledmovie){
  // Add to the new collection all the items of the scheduled movies structure that have a day in the future and an associated hour  
  $alldates->add($scheduledmovie->schedules()->toStructure()->filter(function ($schedule) {
  return $schedule->day()->toDate('%F') >= date('Y-m-d') && $schedule->hour()->isNotEmpty();
  }));
} ?>
  1. Then I group the new collection based on the dates:
<?php 
// Per month
$groupedSchedulesPerMonth = $alldates->sortBy('day')->group(function($p) {
  return $p->day()->toDate('%Y-%m');
});
foreach($groupedSchedulesPerMonth as $month => $schedulesPerMonth): ?>
  <h2><?= strftime('%B %Y', strtotime($month)) ?></h2>
  <ul class="days">

  <?php
  // Per day
  $groupedSchedulesPerDay = $schedulesPerMonth->group(function($p) {
      return $p->day()->toDate('%F');
  });
  foreach($groupedSchedulesPerDay as $day => $schedule): ?>
    <li>
      <h3><?= str_replace('.', '',strftime('%A %d', strtotime($day)))?></h3>
      <ul class="slots">
  1. And now I try to display the scheduled movies in the appropriate time slots depending on when the screenings begin:
        <li>
          <h4>From 14:00 to 16:00</h4>
          <?php
          // Movies begining before 16:00
          $filteredScheduledMovies = $scheduledmovies->filter(function ($child) use($day) {
            return $child->schedules()->toStructure()->filter(function ($item) use ($day) {
              return ($item->day()->toDate('%F') == $day) && ($item->hour()->toDate('%H') < 16);
            });
          });
          foreach($filteredScheduledMovies as $scheduledmovie){
            echo '<p>'. $scheduledmovie->title() . '</p>';
          } ?>
        </li>

        <li>
          <h4>From 16:00 to 18:00</h4>
          <?php
          // Movies begining between 16:00 and 18:00
          $filteredScheduledMovies = $scheduledmovies->filter(function ($child) use($day) {
            return $child->schedules()->toStructure()->filter(function ($item) use ($day) {
              return ($item->day()->toDate('%F') == $day) && ($item->hour()->toDate('%H') >= 16) && ($item->hour()->toDate('%H') < 18);
            });
          });
          foreach($filteredScheduledMovies as $scheduledmovie){
            echo '<p>'. $scheduledmovie->title() . '</p>';
          } ?>
        </li>

       <li>
          <h4>From 18:00 to 20:00</h4>
          <?php
          // Movies begining between 18:00 and 20:00
          $filteredScheduledMovies = $scheduledmovies->filter(function ($child) use($day) {
            return $child->schedules()->toStructure()->filter(function ($item) use ($day) {
              return ($item->day()->toDate('%F') == $day) && ($item->hour()->toDate('%H') >= 18) && ($item->hour()->toDate('%H') < 20);
            });
          });
          foreach($filteredScheduledMovies as $scheduledmovie){
            echo '<p>'. $scheduledmovie->title() . '</p>';
          } ?>
        </li>

        <li>
          <h4>From 20:00 to 22:00</h4>
          <?php
          // Movies begining after 20:00
          $filteredScheduledMovies = $scheduledmovies->filter(function ($child) use($day) {
            return $child->schedules()->toStructure()->filter(function ($item) use ($day) {
              return ($item->day()->toDate('%F') == $day) && ($item->hour()->toDate('%H') >= 16) && ($item->hour()->toDate('%H') < 18);
            });
          });
          foreach($filteredScheduledMovies as $scheduledmovie){
            echo '<p>'. $scheduledmovie->title() . '</p>';
          } ?>
        </li>
      </ul>
    </li>
    <?php endforeach ?>
  </ul>
<?php endforeach ?>

The code above (3) is not working properly. Currently, the $filteredScheduledMovies collection echoes ALL the scheduled movies in each time slot as if the filter per day and hour was not working.

Could someone here understand why? What should I change in my code? :thinking:

This is the issue I’m facing today. But I’m sure I’ll have another one soon. I prefer to explain it right now in case this lead to a completely different logic:
Users should be able to click one or more time slots in the calendar to apply as volunteer at the movie theater… and I don’t know how to manage that. Should I generate a unique ID for each time slot and store them in the user’s page once clicked? Should I create virtual pages for that? Structure? Collection? I’m a bit lost… again… sorry…

Thank you all for your time and your help :pray:

PS: If you plan to go to Morlaix (France), the movie theater La Salamandre has a really great programming, and their website can help you decide what you gonna watch: https://cinemalasalamandre.fr

:wave:

I think here you should filter $schedule (because that is you current day context), not $scheduledmovies.

Thanks (again) for your help pixelijn. I tried your proposal but it didn’t work…

I finally and hopefully managed to make my functions echo the right movies in the right month/day/time slots by adding a simple boolean, as suggested by Sonja in this post :blush:

$filteredScheduledMovies = $scheduledmovies->filter(function ($child) use($day) {
  return $child->schedules()->toStructure()->filter(function ($item) use ($day) {
    return ($item->day()->toDate('%F') == $day) && ($item->hour()->toDate('%H') >= 16) && ($item->hour()->toDate('%H') < 18);
  })->count();
});

Here is the current result:

Now I’ll have to find a proper way to make each time slot clickable by users so the movie theater team will know who will be contributing as a volunteer :nerd_face: