diff --git a/module/Application/config/volume-horaire.config.php b/module/Application/config/volume-horaire.config.php
index 3775e9a3247e08d3e933ebc94746d954ac71ceb6..76703aa409855f909cf0c20fd9fc050b5176da3b 100644
--- a/module/Application/config/volume-horaire.config.php
+++ b/module/Application/config/volume-horaire.config.php
@@ -13,8 +13,8 @@ return [
                 'options'       => [
                     'route'    => '/volume-horaire',
                     'defaults' => [
-                        'controller'    => 'Application\Controller\VolumeHoraire',
-                        'action'        => 'index',
+                        'controller' => 'Application\Controller\VolumeHoraire',
+                        'action'     => 'index',
                     ],
                 ],
                 'may_terminate' => true,
@@ -81,7 +81,8 @@ return [
     ],
     'view_helpers'    => [
         'invokables' => [
-            'volumeHoraireListe' => View\Helper\VolumeHoraire\Liste::class,
+            'volumeHoraireListe'           => View\Helper\VolumeHoraire\Liste::class,
+            'volumeHoraireListeCalendaire' => View\Helper\VolumeHoraire\ListeCalendaire::class,
         ],
     ],
     'form_elements'   => [
diff --git a/module/Application/src/Application/Controller/VolumeHoraireController.php b/module/Application/src/Application/Controller/VolumeHoraireController.php
index f00f7769d56695512d5716a50e2955ae8804be8a..9d8ebda1761a4144a151365dd091f90c669c2807 100644
--- a/module/Application/src/Application/Controller/VolumeHoraireController.php
+++ b/module/Application/src/Application/Controller/VolumeHoraireController.php
@@ -44,7 +44,8 @@ class VolumeHoraireController extends AbstractController
         $readOnly           = 1 == (int)$this->params()->fromQuery('read-only', 0);
 
         $volumeHoraireListe = $service->getVolumeHoraireListe()->setTypeVolumehoraire( $typeVolumeHoraire );
-        return compact('volumeHoraireListe', 'readOnly');
+        $semestriel = $this->getServiceContext()->isModaliteServicesSemestriel();
+        return compact('volumeHoraireListe', 'readOnly', 'semestriel');
     }
 
     public function saisieAction()
diff --git a/module/Application/src/Application/View/Helper/Service/Liste.php b/module/Application/src/Application/View/Helper/Service/Liste.php
index fc6520d5b6cb7c8db9b99a4fbcf98440d56db219..dba39b8de5e55db92ee2fe4ead572a4f808f2e41 100644
--- a/module/Application/src/Application/View/Helper/Service/Liste.php
+++ b/module/Application/src/Application/View/Helper/Service/Liste.php
@@ -300,8 +300,14 @@ class Liste extends AbstractViewHelper
     {
         $ligneView = $this->getView()->serviceLigne($this, $service);
 
-        $volumeHoraireListe = $this->getView()->volumeHoraireListe($service->getVolumeHoraireListe());
-        /* @var $volumeHoraireListe \Application\View\Helper\VolumeHoraire\Liste */
+        if ($this->getServiceContext()->isModaliteServicesSemestriel()){
+            $volumeHoraireListe = $this->getView()->volumeHoraireListe($service->getVolumeHoraireListe());
+            /* @var $volumeHoraireListe \Application\View\Helper\VolumeHoraire\Liste */
+        }else{
+            $volumeHoraireListe = $this->getView()->volumeHoraireListeCalendaire($service->getVolumeHoraireListe());
+            /* @var $volumeHoraireListe \Application\View\Helper\VolumeHoraire\ListeCalendaire */
+        }
+
 
         $attribs = [
             'id'       => 'service-' . $service->getId() . '-ligne',
diff --git a/module/Application/src/Application/View/Helper/VolumeHoraire/ListeCalendaire.php b/module/Application/src/Application/View/Helper/VolumeHoraire/ListeCalendaire.php
new file mode 100644
index 0000000000000000000000000000000000000000..7be7a6cad00173876f22ff5e8a92d70e407286dd
--- /dev/null
+++ b/module/Application/src/Application/View/Helper/VolumeHoraire/ListeCalendaire.php
@@ -0,0 +1,286 @@
+<?php
+
+namespace Application\View\Helper\VolumeHoraire;
+
+use Application\Entity\Db\MotifNonPaiement;
+use Application\Provider\Privilege\Privileges;
+use Application\Service\Traits\ServiceServiceAwareTrait;
+use Application\Service\Traits\TypeInterventionServiceAwareTrait;
+use Application\View\Helper\AbstractViewHelper;
+use Application\Entity\VolumeHoraireListe;
+use Application\Entity\Db\TypeIntervention;
+
+
+/**
+ * Aide de vue permettant d'afficher une liste de volumes horaires
+ *
+ * @author Laurent LÉCLUSE <laurent.lecluse at unicaen.fr>
+ */
+class ListeCalendaire extends AbstractViewHelper
+{
+    use ServiceServiceAwareTrait;
+    use TypeInterventionServiceAwareTrait;
+
+    /**
+     * @var VolumeHoraireListe
+     */
+    protected $volumeHoraireListe;
+
+    /**
+     * Liste des types d'intervention
+     *
+     * @var TypeIntervention[]
+     */
+    protected $typesIntervention;
+
+    /**
+     * readOnly
+     *
+     * @var boolean
+     */
+    protected $readOnly;
+
+    /**
+     * Mode lecture seule forcé
+     *
+     * @var boolean
+     */
+    protected $forcedReadOnly;
+
+    /**
+     * hasForbiddenPeriodes
+     *
+     * @var boolean
+     */
+    protected $hasForbiddenPeriodes = false;
+
+
+
+
+    /**
+     *
+     * @return boolean
+     */
+    public function getReadOnly()
+    {
+        return $this->readOnly || $this->forcedReadOnly;
+    }
+
+    /**
+     *
+     * @param boolean $readOnly
+     * @return self
+     */
+    public function setReadOnly($readOnly)
+    {
+        $this->readOnly = $readOnly;
+        return $this;
+    }
+
+    public function hasForbiddenPeriodes()
+    {
+        return $this->hasForbiddenPeriodes;
+    }
+
+    /**
+     * Helper entry point.
+     *
+     * @param VolumeHoraireListe $volumeHoraireListe
+     * @return self
+     */
+    final public function __invoke( VolumeHoraireListe $volumeHoraireListe )
+    {
+        /* Initialisation */
+        $this->setVolumeHoraireListe( $volumeHoraireListe );
+        return $this;
+    }
+
+    /**
+     * Retourne le code HTML généré par cette aide de vue.
+     *
+     * @return string
+     */
+    public function __toString()
+    {
+        return $this->render();
+    }
+
+    public function getRefreshUrl()
+    {
+        $url = $this->getView()->url(
+                'volume-horaire/liste',
+                [
+                    'service' => $this->getVolumeHoraireListe()->getService()->getId()
+                ], ['query' => [
+                    'read-only' => $this->getReadOnly() ? '1' : '0',
+                    'type-volume-horaire' => $this->getVolumeHoraireListe()->getTypeVolumehoraire()->getId(),
+                ]]);
+        return $url;
+    }
+
+    /**
+     * Génère le code HTML.
+     *
+     * @return string
+     */
+    public function render(){
+        $this->hasForbiddenPeriodes = false;
+        $canViewMNP = $this->getView()->isAllowed($this->getVolumeHoraireListe()->getService()->getIntervenant(), Privileges::MOTIF_NON_PAIEMENT_VISUALISATION);
+        $canEditMNP = $this->getView()->isAllowed($this->getVolumeHoraireListe()->getService()->getIntervenant(), Privileges::MOTIF_NON_PAIEMENT_EDITION);
+
+        $out = '<table class="table table-condensed table-bordered volume-horaire">';
+        $out .= '<tr>';
+        $out .= "<th style=\"width:10%\">Période</th>\n";
+        foreach( $this->getTypesInterventions() as $ti ){
+            $out .= "<th style=\"width:1%\"><abbr title=\"".$ti->getLibelle()."\">".$ti->getCode()."</abbr></th>\n";
+        }
+        if ($canViewMNP){
+            $out .= "<th style=\"width:25%\">Motif de non paiement</th>\n";
+        }
+        $out .= "</tr>\n";
+        $periodes = $this->getPeriodes();
+        foreach( $periodes as $periode ){
+            $vhl = $this->getVolumeHoraireListe()->setPeriode($periode)->setTypeIntervention(false);
+            $motifsNonPaiement = [];
+            if ($canViewMNP){  // découpage par motif de non paiement
+                $motifsNonPaiement = $vhl->getMotifsNonPaiement();
+                if (!isset($motifsNonPaiement[0]) && !$canEditMNP){
+                    $motifsNonPaiement = [0 => null] + $motifsNonPaiement;
+                }
+            }
+            if(empty($motifsNonPaiement)){
+                $motifsNonPaiement = [0 => false];
+            }
+            foreach( $motifsNonPaiement as $motifNonPaiement ){
+                $readOnly = $motifNonPaiement instanceof MotifNonPaiement && !$canEditMNP;
+                $forbiddenPeriode = false;
+                if (
+                       $this->getVolumeHoraireListe()->getService()
+                    && $this->getVolumeHoraireListe()->getService()->getElementPedagogique()
+                    && $this->getVolumeHoraireListe()->getService()->getElementPedagogique()->getPeriode()
+                    && $this->getVolumeHoraireListe()->getService()->getElementPedagogique()->getPeriode() !== $periode
+                ){
+                    $forbiddenPeriode = true;
+                    $this->hasForbiddenPeriodes = true;
+                }
+                if ($forbiddenPeriode){
+                    $out .= '<tr class="bg-danger">';
+                    $out .= "<td><abbr title=\"La période n'est pas conforme à l'enseignement\">".$this->renderPeriode($periode)."</abbr></td>\n";
+                }else{
+                    $out .= '<tr>';
+                    $out .= "<td>".$this->renderPeriode($periode)."</td>\n";
+                }
+
+                foreach( $this->typesIntervention as $typeIntervention ){
+                    $vhl->setMotifNonPaiement($motifNonPaiement)
+                        ->setTypeIntervention($typeIntervention);
+                    if ($vhl->getHeures() == 0){
+                        $class="heures-empty";
+                    }else{
+                        $class="heures-not-empty";
+                    }
+                    $out .= '<td style="text-align:right" class="'.$class.'">'.$this->renderHeures( $vhl, $readOnly ).'</td>';
+                }
+                if ($canViewMNP){
+                    $out .= "<td>".$this->renderMotifNonPaiement($motifNonPaiement)."</td>\n";
+                }
+                $out .= "</tr>\n";
+            }
+        }
+        $out .= '</table>'."\n";
+        return $out;
+    }
+
+    protected function renderPeriode($periode)
+    {
+        if (! $periode) return "Indéterminée";
+        $out = (string)$periode;
+        return $out;
+    }
+
+    public function renderHeures(VolumeHoraireListe $volumeHoraireListe, $readOnly=false)
+    {
+        $heures = $volumeHoraireListe->getHeures();
+        $heures = \UnicaenApp\Util::formattedNumber($heures);
+
+        $query = $volumeHoraireListe->filtersToArray();
+        if (false === $volumeHoraireListe->getMotifNonPaiement()){
+            $query['tous-motifs-non-paiement'] = '1';
+        }
+        if ($readOnly || $this->getReadOnly()){
+            return $heures;
+        }else{
+            $url = $this->getView()->url(
+                        'volume-horaire/saisie',
+                        ['service' => $volumeHoraireListe->getService()->getId()],
+                        ['query' => $query]
+                   );
+
+            return "<a class=\"ajax-popover volume-horaire\" data-event=\"save-volume-horaire\" data-placement=\"bottom\" data-service=\"".$volumeHoraireListe->getService()->getId()."\" href=\"".$url."\" >$heures</a>";
+        }
+    }
+
+    protected function renderMotifNonPaiement($motifNonPaiement)
+    {
+        if (! empty($motifNonPaiement)){
+            $out = $motifNonPaiement->getLibelleLong();
+        }else{
+            $out = '';
+        }
+        return $out;
+    }
+
+    /**
+     *
+     * @return VolumeHoraireListe
+     */
+    public function getVolumeHoraireListe()
+    {
+        return $this->volumeHoraireListe;
+    }
+
+    public function setVolumeHoraireListe(VolumeHoraireListe $volumeHoraireListe)
+    {
+        $this->volumeHoraireListe = $volumeHoraireListe;
+        $this->forcedReadOnly = ! $this->getView()->isAllowed($volumeHoraireListe->getService(), Privileges::ENSEIGNEMENT_EDITION);
+        $this->typesIntervention = null;
+        return $this;
+    }
+
+    public function getTypesInterventions()
+    {
+        if (! $this->typesIntervention){
+            if ($this->getVolumeHoraireListe()->getService()->getElementPedagogique()){
+                $tis = $this->getVolumeHoraireListe()->getService()->getElementPedagogique()->getTypeIntervention();
+            }else{
+                $qb = $this->getServiceTypeIntervention()->finderByContext();
+                $this->getServiceTypeIntervention()->finderByHistorique($qb);
+                $tis = $this->getServiceTypeIntervention()->getList( $qb );
+            }
+            $this->typesIntervention = [];
+            foreach( $tis as $ti ){
+                $this->typesIntervention[] = $ti;
+            }
+            uasort( $this->typesIntervention, function($a,$b){ return $a->getordre() > $b->getOrdre(); });
+        }
+        return $this->typesIntervention;
+    }
+
+    public function getPeriodes()
+    {
+        $vhl = $this->getVolumeHoraireListe()->getChild()
+                                             ->setTypeIntervention(false)
+                                             ->setPeriode(false);
+
+        $periodes = $this->getServiceService()->getPeriodes( $vhl->getService() );
+        $vhPeriodes = $vhl->getPeriodes();
+        foreach($vhPeriodes as $periode ){
+            if (! isset($periodes[$periode->getId()])) $periodes[$periode->getId()] = $periode;
+        }
+        uasort( $periodes, function( $a, $b ){
+            return ($a ? $a->getOrdre() : '') > ($b ? $b->getOrdre() : '');
+        });
+        return $periodes;
+    }
+
+}
\ No newline at end of file
diff --git a/module/Application/view/application/volume-horaire/liste.phtml b/module/Application/view/application/volume-horaire/liste.phtml
index ffd95318b914788d409b378ae144e6a607273171..5b962b8cf86d2bde92d9343e9df44bb58c33b319 100644
--- a/module/Application/view/application/volume-horaire/liste.phtml
+++ b/module/Application/view/application/volume-horaire/liste.phtml
@@ -1,6 +1,22 @@
 <?php
 
-echo $this
-        ->volumeHoraireListe( $volumeHoraireListe )
+use Application\Entity\VolumeHoraireListe;
+
+/**
+ * @var $this               \Application\View\Renderer\PhpRenderer
+ * @var $volumeHoraireListe VolumeHoraireListe
+ * @var $readOnly           bool
+ * @var $semestriel         bool
+ */
+
+if ($semestriel){
+    echo $this
+        ->volumeHoraireListe($volumeHoraireListe)
+        ->setReadOnly($readOnly)
+        ->render();
+}else{
+    echo $this
+        ->volumehorairelisteCalendaire($volumeHoraireListe)
         ->setReadOnly($readOnly)
-        ->render();
\ No newline at end of file
+        ->render();
+}
\ No newline at end of file