From a71aa54fa21dfb6b9c5ff7c6c748e8646ed446c9 Mon Sep 17 00:00:00 2001
From: Arnaud Levy <contact@arnaudlevy.com>
Date: Tue, 28 Nov 2023 17:41:18 +0100
Subject: [PATCH] wip

---
 Gemfile.lock                                  |  4 +-
 .../websites/agenda/categories_controller.rb  | 89 +++++++++++++++++++
 .../websites/agenda/events_controller.rb      |  2 +-
 app/models/communication/extranet.rb          | 57 ++++++------
 .../communication/website/agenda/category.rb  | 73 +++++++++++++++
 .../communication/website/agenda/event.rb     |  6 +-
 app/models/communication/website/category.rb  |  5 --
 .../website/with_associated_objects.rb        |  5 ++
 .../websites/agenda/categories/_form.html.erb | 28 ++++++
 .../agenda/categories/_inline.html.erb        |  5 ++
 .../websites/agenda/categories/_list.html.erb | 29 ++++++
 .../agenda/categories/_panel.html.erb         |  6 ++
 .../agenda/categories/_static_about.html.erb  |  6 ++
 .../websites/agenda/categories/edit.html.erb  |  5 ++
 .../websites/agenda/categories/index.html.erb | 10 +++
 .../websites/agenda/categories/new.html.erb   |  5 ++
 .../websites/agenda/categories/show.html.erb  | 35 ++++++++
 .../agenda/categories/static.html.erb         | 23 +++++
 .../websites/agenda/events/index.html.erb     |  2 +-
 config/locales/communication/en.yml           |  8 ++
 config/locales/communication/fr.yml           |  8 ++
 config/routes/admin/communication.rb          | 12 ++-
 ...communication_website_agenda_categories.rb | 20 +++++
 ...communication_website_agenda_categories.rb |  5 ++
 db/schema.rb                                  | 38 ++++++--
 test/fixtures/communication/extranets.yml     | 57 ++++++------
 .../website/agenda/categories.yml             | 62 +++++++++++++
 .../website/agenda/category_test.rb           | 41 +++++++++
 28 files changed, 567 insertions(+), 79 deletions(-)
 create mode 100644 app/controllers/admin/communication/websites/agenda/categories_controller.rb
 create mode 100644 app/models/communication/website/agenda/category.rb
 create mode 100644 app/views/admin/communication/websites/agenda/categories/_form.html.erb
 create mode 100644 app/views/admin/communication/websites/agenda/categories/_inline.html.erb
 create mode 100644 app/views/admin/communication/websites/agenda/categories/_list.html.erb
 create mode 100644 app/views/admin/communication/websites/agenda/categories/_panel.html.erb
 create mode 100644 app/views/admin/communication/websites/agenda/categories/_static_about.html.erb
 create mode 100644 app/views/admin/communication/websites/agenda/categories/edit.html.erb
 create mode 100644 app/views/admin/communication/websites/agenda/categories/index.html.erb
 create mode 100644 app/views/admin/communication/websites/agenda/categories/new.html.erb
 create mode 100644 app/views/admin/communication/websites/agenda/categories/show.html.erb
 create mode 100644 app/views/admin/communication/websites/agenda/categories/static.html.erb
 create mode 100644 db/migrate/20231128155822_create_communication_website_agenda_categories.rb
 create mode 100644 db/migrate/20231128160407_move_join_table_communication_website_agenda_categories.rb
 create mode 100644 test/fixtures/communication/website/agenda/categories.yml
 create mode 100644 test/models/communication/website/agenda/category_test.rb

diff --git a/Gemfile.lock b/Gemfile.lock
index d8c536fbd..d1cd93122 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -112,7 +112,7 @@ GEM
     autoprefixer-rails (10.4.16.0)
       execjs (~> 2)
     aws-eventstream (1.3.0)
-    aws-partitions (1.857.0)
+    aws-partitions (1.859.0)
     aws-sdk-core (3.188.0)
       aws-eventstream (~> 1, >= 1.0.2)
       aws-partitions (~> 1, >= 1.651.0)
@@ -554,7 +554,7 @@ GEM
       actionpack (>= 5.2)
       activesupport (>= 5.2)
       sprockets (>= 3.0.0)
-    stringio (3.0.9)
+    stringio (3.1.0)
     sugar-high (0.7.3)
     sweetloader (0.1.6)
       activesupport (>= 3.0.1)
diff --git a/app/controllers/admin/communication/websites/agenda/categories_controller.rb b/app/controllers/admin/communication/websites/agenda/categories_controller.rb
new file mode 100644
index 000000000..3a8fa18b9
--- /dev/null
+++ b/app/controllers/admin/communication/websites/agenda/categories_controller.rb
@@ -0,0 +1,89 @@
+class Admin::Communication::Websites::Agenda::CategoriesController < Admin::Communication::Websites::Agenda::ApplicationController
+  load_and_authorize_resource class: Communication::Website::Agenda::Category, 
+                              through: :website,
+                              through_association: :agenda_categories
+
+  include Admin::Translatable
+
+  def index
+    @categories = @website.agenda_categories.for_language(current_website_language).ordered
+    breadcrumb
+  end
+
+  def reorder
+    ids = params[:ids] || []
+    ids.each.with_index do |id, index|
+      category = @website.categories.find(id)
+      category.update_column :position, index + 1
+    end
+    @category = @website.agenda_categories.find(params[:itemId])
+    @category.sync_with_git # Will sync siblings
+  end
+
+  def show
+    @events = @category.events.ordered.page(params[:page])
+    breadcrumb
+  end
+
+  def static
+    @about = @category
+    render layout: false
+  end
+
+  def new
+    @category.website = @website
+    breadcrumb
+  end
+
+  def edit
+    breadcrumb
+    add_breadcrumb t('edit')
+  end
+
+  def create
+    @category.website = @website
+    @category.add_photo_import params[:photo_import]
+    if @category.save_and_sync
+      redirect_to admin_communication_website_agenda_category_path(@category), notice: t('admin.successfully_created_html', model: @category.to_s)
+    else
+      breadcrumb
+      render :new, status: :unprocessable_entity
+    end
+  end
+
+  def update
+    @category.add_photo_import params[:photo_import]
+    if @category.update_and_sync(category_params)
+      redirect_to admin_communication_website_agenda_category_path(@category), notice: t('admin.successfully_updated_html', model: @category.to_s)
+    else
+      breadcrumb
+      add_breadcrumb t('edit')
+      render :edit, status: :unprocessable_entity
+    end
+  end
+
+  def destroy
+    @category.destroy
+    redirect_to admin_communication_website_agenda_categories_url, notice: t('admin.successfully_destroyed_html', model: @category.to_s)
+  end
+
+  protected
+  def breadcrumb
+    super
+    add_breadcrumb  Communication::Website::Agenda::Category.model_name.human(count: 2),
+                    admin_communication_website_agenda_categories_path
+    breadcrumb_for @category
+  end
+
+  def category_params
+    params.require(:communication_website_agenda_category)
+          .permit(
+            :name, :meta_description, :summary, :slug, 
+            :featured_image, :featured_image_delete, :featured_image_infos, :featured_image_alt, :featured_image_credit
+          )
+          .merge(
+            university_id: current_university.id,
+            language_id: current_website_language.id
+          )
+  end
+end
diff --git a/app/controllers/admin/communication/websites/agenda/events_controller.rb b/app/controllers/admin/communication/websites/agenda/events_controller.rb
index 4ab259b7e..ead349383 100644
--- a/app/controllers/admin/communication/websites/agenda/events_controller.rb
+++ b/app/controllers/admin/communication/websites/agenda/events_controller.rb
@@ -6,7 +6,7 @@ class Admin::Communication::Websites::Agenda::EventsController < Admin::Communic
 
   def index
     @events = apply_scopes(@events).for_language(current_website_language).ordered_desc.page params[:page]
-    @root_categories = @website.categories.for_language(current_website_language).root.ordered
+    @categories = @website.agenda_categories.for_language(current_website_language).ordered
     breadcrumb
   end
 
diff --git a/app/models/communication/extranet.rb b/app/models/communication/extranet.rb
index 1a4615b3d..0dc316363 100644
--- a/app/models/communication/extranet.rb
+++ b/app/models/communication/extranet.rb
@@ -2,35 +2,34 @@
 #
 # Table name: communication_extranets
 #
-#  id                             :uuid             not null, primary key
-#  about_type                     :string           indexed => [about_id]
-#  allow_experiences_modification :boolean          default(TRUE)
-#  color                          :string
-#  cookies_policy                 :text
-#  css                            :text
-#  feature_alumni                 :boolean          default(FALSE)
-#  feature_contacts               :boolean          default(FALSE)
-#  feature_jobs                   :boolean          default(FALSE)
-#  feature_library                :boolean          default(FALSE)
-#  feature_posts                  :boolean          default(FALSE)
-#  has_sso                        :boolean          default(FALSE)
-#  home_sentence                  :text
-#  host                           :string
-#  name                           :string
-#  privacy_policy                 :text
-#  registration_contact           :string
-#  sass                           :text
-#  sso_button_label               :string
-#  sso_cert                       :text
-#  sso_mapping                    :jsonb
-#  sso_name_identifier_format     :string
-#  sso_provider                   :integer          default("saml")
-#  sso_target_url                 :string
-#  terms                          :text
-#  created_at                     :datetime         not null
-#  updated_at                     :datetime         not null
-#  about_id                       :uuid             indexed => [about_type]
-#  university_id                  :uuid             not null, indexed
+#  id                         :uuid             not null, primary key
+#  about_type                 :string           indexed => [about_id]
+#  color                      :string
+#  cookies_policy             :text
+#  css                        :text
+#  feature_alumni             :boolean          default(FALSE)
+#  feature_contacts           :boolean          default(FALSE)
+#  feature_jobs               :boolean          default(FALSE)
+#  feature_library            :boolean          default(FALSE)
+#  feature_posts              :boolean          default(FALSE)
+#  has_sso                    :boolean          default(FALSE)
+#  home_sentence              :text
+#  host                       :string
+#  name                       :string
+#  privacy_policy             :text
+#  registration_contact       :string
+#  sass                       :text
+#  sso_button_label           :string
+#  sso_cert                   :text
+#  sso_mapping                :jsonb
+#  sso_name_identifier_format :string
+#  sso_provider               :integer          default("saml")
+#  sso_target_url             :string
+#  terms                      :text
+#  created_at                 :datetime         not null
+#  updated_at                 :datetime         not null
+#  about_id                   :uuid             indexed => [about_type]
+#  university_id              :uuid             not null, indexed
 #
 # Indexes
 #
diff --git a/app/models/communication/website/agenda/category.rb b/app/models/communication/website/agenda/category.rb
new file mode 100644
index 000000000..cf25c1c04
--- /dev/null
+++ b/app/models/communication/website/agenda/category.rb
@@ -0,0 +1,73 @@
+# == Schema Information
+#
+# Table name: communication_website_agenda_categories
+#
+#  id                       :uuid             not null, primary key
+#  featured_image_alt       :string
+#  featured_image_credit    :text
+#  meta_description         :text
+#  name                     :string
+#  path                     :string
+#  position                 :integer
+#  slug                     :string
+#  summary                  :text
+#  created_at               :datetime         not null
+#  updated_at               :datetime         not null
+#  communication_website_id :uuid             not null, indexed
+#  language_id              :uuid             not null, indexed
+#  original_id              :uuid             indexed
+#  university_id            :uuid             not null, indexed
+#
+# Indexes
+#
+#  idx_communication_website_agenda_cats_on_website_id             (communication_website_id)
+#  index_communication_website_agenda_categories_on_language_id    (language_id)
+#  index_communication_website_agenda_categories_on_original_id    (original_id)
+#  index_communication_website_agenda_categories_on_university_id  (university_id)
+#
+# Foreign Keys
+#
+#  fk_rails_1e1b9fbf33  (original_id => communication_website_agenda_categories.id)
+#  fk_rails_6cb9a4b8a1  (university_id => universities.id)
+#  fk_rails_7b5ad84dda  (communication_website_id => communication_websites.id)
+#  fk_rails_b0ddee638d  (language_id => languages.id)
+#
+class Communication::Website::Agenda::Category < ApplicationRecord
+  include AsDirectObject
+  include Sanitizable
+  include WithBlobs
+  include WithBlocks
+  include WithFeaturedImage
+  include WithMenuItemTarget
+  include WithPermalink
+  include WithPosition
+  include WithSlug
+  include WithTranslations
+  include WithUniversity
+
+  has_and_belongs_to_many :events,
+                          class_name: 'Communication::Website::Agenda::Event',
+                          join_table: :communication_website_agenda_events_categories,
+                          foreign_key: :communication_website_agenda_category_id,
+                          association_foreign_key: :communication_website_agenda_event_id
+
+
+  def to_s
+    "#{name}"
+  end
+
+  def git_path(website)
+    "#{git_path_content_prefix(website)}events_categories/#{slug_with_ancestors_slugs}/_index.html"
+  end
+
+  def template_static
+    "admin/communication/websites/agenda/categories/static"
+  end
+
+  protected
+
+  def slug_unavailable?(slug)
+    self.class.unscoped.where(communication_website_id: self.communication_website_id, language_id: language_id, slug: slug).where.not(id: self.id).exists?
+  end
+
+end
diff --git a/app/models/communication/website/agenda/event.rb b/app/models/communication/website/agenda/event.rb
index bd670f2cc..6e9b0ebba 100644
--- a/app/models/communication/website/agenda/event.rb
+++ b/app/models/communication/website/agenda/event.rb
@@ -59,11 +59,11 @@ class Communication::Website::Agenda::Event < ApplicationRecord
               class_name: 'Communication::Website::Agenda::Event',
               optional: true
 
-  has_and_belongs_to_many :categories,
-                          class_name: 'Communication::Website::Category',
+  has_and_belongs_to_many :events,
+                          class_name: 'Communication::Website::Agenda::Category',
                           join_table: :communication_website_agenda_events_categories,
                           foreign_key: :communication_website_agenda_event_id,
-                          association_foreign_key: :communication_website_category_id
+                          association_foreign_key: :communication_website_agenda_category_id
 
   scope :ordered_desc, -> { order(from_day: :desc, from_hour: :desc) }
   scope :ordered_asc, -> { order(:from_day, :from_hour) }
diff --git a/app/models/communication/website/category.rb b/app/models/communication/website/category.rb
index 715d87fbe..d391c4fde 100644
--- a/app/models/communication/website/category.rb
+++ b/app/models/communication/website/category.rb
@@ -70,11 +70,6 @@ class Communication::Website::Category < ApplicationRecord
                           join_table: :communication_website_categories_posts,
                           foreign_key: :communication_website_category_id,
                           association_foreign_key: :communication_website_post_id
-  has_and_belongs_to_many :events,
-                          class_name: 'Communication::Website::Agenda::Event',
-                          join_table: :communication_website_agenda_events_categories,
-                          foreign_key: :communication_website_category_id,
-                          association_foreign_key: :communication_website_agenda_event_id
 
   validates :name, presence: true
 
diff --git a/app/models/communication/website/with_associated_objects.rb b/app/models/communication/website/with_associated_objects.rb
index 413ef344f..134d1712d 100644
--- a/app/models/communication/website/with_associated_objects.rb
+++ b/app/models/communication/website/with_associated_objects.rb
@@ -18,6 +18,11 @@ module Communication::Website::WithAssociatedObjects
                 foreign_key: :communication_website_id,
                 dependent: :destroy
 
+    has_many    :agenda_categories,
+                class_name: 'Communication::Website::Agenda::Category',
+                foreign_key: :communication_website_id,
+                dependent: :destroy
+
     has_many    :permalinks,
                 class_name: "Communication::Website::Permalink",
                 dependent: :destroy
diff --git a/app/views/admin/communication/websites/agenda/categories/_form.html.erb b/app/views/admin/communication/websites/agenda/categories/_form.html.erb
new file mode 100644
index 000000000..9cc01d277
--- /dev/null
+++ b/app/views/admin/communication/websites/agenda/categories/_form.html.erb
@@ -0,0 +1,28 @@
+<%= simple_form_for [:admin, category] do |f| %>
+  <%= f.error_notification %>
+  <%= f.error_notification message: f.object.errors[:base].to_sentence if f.object.errors[:base].present? %>
+
+  <div class="row">
+    <div class="col-md-8">
+      <%= osuny_panel t('content') do %>
+        <%= f.input :name %>
+        <%= render 'admin/application/summary/form', f: f, about: category %>
+      <% end %>
+      <%= render 'admin/application/meta_description/form', f: f, about: category %>
+    </div>
+    <div class="col-md-4">
+      <%= osuny_panel t('metadata') do %>
+        <%= f.input :slug,
+                    as: :string,
+                    input_html: category.persisted? ? {} : {
+                      class: 'js-slug-input',
+                      data: { source: '#communication_website_agenda_category_name' }
+                    } %>
+      <% end %>
+      <%= render 'admin/application/featured_image/edit', about: category, f: f %>
+    </div>
+  </div>
+  <% content_for :action_bar_right do %>
+    <%= submit f %>
+  <% end %>
+<% end %>
diff --git a/app/views/admin/communication/websites/agenda/categories/_inline.html.erb b/app/views/admin/communication/websites/agenda/categories/_inline.html.erb
new file mode 100644
index 000000000..2594e1625
--- /dev/null
+++ b/app/views/admin/communication/websites/agenda/categories/_inline.html.erb
@@ -0,0 +1,5 @@
+<ul class="list-unstyled mb-0">
+  <% about.categories.each do |category| %>
+    <li><%= category %></li>
+  <% end %>
+</ul>
diff --git a/app/views/admin/communication/websites/agenda/categories/_list.html.erb b/app/views/admin/communication/websites/agenda/categories/_list.html.erb
new file mode 100644
index 000000000..d1d625b70
--- /dev/null
+++ b/app/views/admin/communication/websites/agenda/categories/_list.html.erb
@@ -0,0 +1,29 @@
+<div class="table-responsive">
+  <table class="<%= table_classes %>">
+    <thead>
+      <tr>
+        <th><%= Communication::Website::Category.human_attribute_name('title') %></th>
+        <th></th>
+      </tr>
+    </thead>
+    <tbody>
+      <% categories.each do |category| %>
+        <tr>
+          <td><%= link_to category, admin_communication_website_agenda_category_path(website_id: category.website.id, id: category.id) %></td>
+          <td class="text-end">
+            <div class="btn-group" role="group">
+              <%= link_to t('edit'),
+                        edit_admin_communication_website_category_path(website_id: category.website.id, id: category.id),
+                        class: button_classes if can?(:update, category) %>
+              <%= link_to t('delete'),
+                        admin_communication_website_category_path(website_id: category.website.id, id: category.id),
+                        method: :delete,
+                        data: { confirm: t('please_confirm') },
+                        class: button_classes_danger if can?(:destroy, category) %>
+            </div>
+          </td>
+        </tr>
+      <% end %>
+    </tbody>
+  </table>
+</div>
diff --git a/app/views/admin/communication/websites/agenda/categories/_panel.html.erb b/app/views/admin/communication/websites/agenda/categories/_panel.html.erb
new file mode 100644
index 000000000..9c47fed43
--- /dev/null
+++ b/app/views/admin/communication/websites/agenda/categories/_panel.html.erb
@@ -0,0 +1,6 @@
+<% if can?(:create, Communication::Website::Agenda::Category)  %>
+  <% action = create_link Communication::Website::Agenda::Category %>
+  <%= osuny_panel Communication::Website::Agenda::Category.model_name.human(count: 2), action: action do %>
+    <%= render 'admin/communication/websites/agenda/categories/list', categories: categories %>
+  <% end %>
+<% end %>
diff --git a/app/views/admin/communication/websites/agenda/categories/_static_about.html.erb b/app/views/admin/communication/websites/agenda/categories/_static_about.html.erb
new file mode 100644
index 000000000..9fb5b731f
--- /dev/null
+++ b/app/views/admin/communication/websites/agenda/categories/_static_about.html.erb
@@ -0,0 +1,6 @@
+<% if @about.categories.any? %>
+events_categories:
+  <% @about.agenda_categories.each do |category| %>
+  - "<%= category.slug_with_ancestors_slugs %>"
+  <% end %>
+<% end %>
\ No newline at end of file
diff --git a/app/views/admin/communication/websites/agenda/categories/edit.html.erb b/app/views/admin/communication/websites/agenda/categories/edit.html.erb
new file mode 100644
index 000000000..bbb03b372
--- /dev/null
+++ b/app/views/admin/communication/websites/agenda/categories/edit.html.erb
@@ -0,0 +1,5 @@
+<% content_for :title, @category %>
+
+<%= render 'admin/communication/websites/sidebar' do %>
+  <%= render 'form', category: @category %>
+<% end %>
diff --git a/app/views/admin/communication/websites/agenda/categories/index.html.erb b/app/views/admin/communication/websites/agenda/categories/index.html.erb
new file mode 100644
index 000000000..ec838b813
--- /dev/null
+++ b/app/views/admin/communication/websites/agenda/categories/index.html.erb
@@ -0,0 +1,10 @@
+<% content_for :title, "#{Communication::Website::Agenda::Category.model_name.human(count: 2)} (#{@categories.count})" %>
+
+<%= render 'admin/communication/websites/sidebar' do %>
+  <%
+  action = create_link Communication::Website::Agenda::Category
+  %>
+  <%= osuny_panel Communication::Website::Agenda::Category.model_name.human(count: 2), action: action do %>
+    <%= render 'admin/communication/websites/agenda/categories/list', categories: @categories %>
+  <% end %>
+<% end %>
diff --git a/app/views/admin/communication/websites/agenda/categories/new.html.erb b/app/views/admin/communication/websites/agenda/categories/new.html.erb
new file mode 100644
index 000000000..9f8ca435e
--- /dev/null
+++ b/app/views/admin/communication/websites/agenda/categories/new.html.erb
@@ -0,0 +1,5 @@
+<% content_for :title, Communication::Website::Category.model_name.human %>
+
+<%= render 'admin/communication/websites/sidebar' do %>
+  <%= render 'form', category: @category %>
+<% end %>
diff --git a/app/views/admin/communication/websites/agenda/categories/show.html.erb b/app/views/admin/communication/websites/agenda/categories/show.html.erb
new file mode 100644
index 000000000..a4eca79ec
--- /dev/null
+++ b/app/views/admin/communication/websites/agenda/categories/show.html.erb
@@ -0,0 +1,35 @@
+<% content_for :title, @category %>
+
+<%= render 'admin/communication/websites/sidebar' do %>
+  <div class="row">
+    <div class="col-md-8">
+      <%= render 'admin/application/summary/show', about: @category %>
+      <%= render 'admin/communication/blocks/content/editor', about: @category %>
+    </div>
+    <div class="col-md-4">
+      <%= render 'admin/application/i18n/widget', about: @category %>
+      <%= osuny_panel t('metadata') do %>
+        <%= osuny_label Communication::Website::Category.human_attribute_name('slug') %>
+        <p><%= @category.slug %></p>
+      <% end %>
+      <%= render 'admin/application/featured_image/show', about: @category %>
+      <%= render 'admin/application/meta_description/show', about: @category %>
+    </div>
+  </div>
+  <% if @events.total_count > 0 %>
+    <%= osuny_panel Communication::Website::Agenda::Event.model_name.human(count: 2),
+                    subtitle: "#{@posts.total_count} #{Communication::Website::Post.model_name.human(count: @posts.total_count).downcase }" do %>
+      <%= render 'admin/communication/websites/posts/list', posts: @posts, hide_category: true %>
+      <%= paginate @posts, theme: 'bootstrap-5' %>
+    <% end %>
+  <% end %>
+<% end %>
+
+<% content_for :action_bar_left do %>
+  <%= destroy_link @category %>
+  <%= static_link static_admin_communication_website_category_path(@category) %>
+<% end %>
+
+<% content_for :action_bar_right do %>
+  <%= edit_link @category %>
+<% end %>
diff --git a/app/views/admin/communication/websites/agenda/categories/static.html.erb b/app/views/admin/communication/websites/agenda/categories/static.html.erb
new file mode 100644
index 000000000..13234a961
--- /dev/null
+++ b/app/views/admin/communication/websites/agenda/categories/static.html.erb
@@ -0,0 +1,23 @@
+---
+title: "<%= @about.name %>"
+<%= render 'admin/application/static/permalink', forced_slug: @about.slug_with_ancestors_slugs %>
+<%= render 'admin/application/static/design', full_width: true, toc_offcanvas: true %>
+<%= render 'admin/application/static/breadcrumbs', 
+            pages: @website.special_page(Communication::Website::Page::CommunicationPost).ancestors_and_self,
+            current: @about %>
+<% if @about.parent %>
+parent: "<%= @about.parent.path %>"
+<% end %>
+<% if @about.children.any? %>
+children:
+<% @about.children.ordered.each do |child| %>
+  - <%= child.path %>
+<% end %>
+<% end %>
+position: <%= @about.position %>
+<%= render 'admin/application/i18n/static' %>
+<%= render 'admin/application/featured_image/static' %>
+<%= render 'admin/application/meta_description/static' %>
+<%= render 'admin/application/summary/static' %>
+<%= render 'admin/communication/blocks/content/static', about: @about %>
+---
diff --git a/app/views/admin/communication/websites/agenda/events/index.html.erb b/app/views/admin/communication/websites/agenda/events/index.html.erb
index 9f75cfd77..b531b5224 100644
--- a/app/views/admin/communication/websites/agenda/events/index.html.erb
+++ b/app/views/admin/communication/websites/agenda/events/index.html.erb
@@ -16,6 +16,6 @@
     <%= paginate @events, theme: 'bootstrap-5' %>
   <% end %>
   
-  <%= render 'admin/communication/websites/categories/panel', root_categories: @root_categories %>
+  <%= render 'admin/communication/websites/agenda/categories/panel', categories: @categories %>
 <% end %>
 
diff --git a/config/locales/communication/en.yml b/config/locales/communication/en.yml
index 8e2833cd0..29fc8783f 100644
--- a/config/locales/communication/en.yml
+++ b/config/locales/communication/en.yml
@@ -31,6 +31,9 @@ en:
       communication/website:
         one: Website
         other: Websites
+      communication/website/agenda/category:
+        one: Category
+        other: Categories
       communication/website/agenda/event:
         one: Event
         other: Events
@@ -164,6 +167,11 @@ en:
         social_youtube: "Youtube (private, belongs to Google)"
         university: University
         url: URL
+      communication/website/agenda/category:
+        featured_image: Featured image
+        featured_image_alt: Alt text
+        name: Name
+        slug: Slug
       communication/website/agenda/event:
         categories: Categories
         dates: Dates
diff --git a/config/locales/communication/fr.yml b/config/locales/communication/fr.yml
index 47a12107d..72dd8bc72 100644
--- a/config/locales/communication/fr.yml
+++ b/config/locales/communication/fr.yml
@@ -31,6 +31,9 @@ fr:
       communication/website:
         one: Site Web
         other: Sites Web
+      communication/website/agenda/category:
+        one: Catégorie
+        other: Catégories
       communication/website/agenda/event:
         one: Événement
         other: Événements
@@ -164,6 +167,11 @@ fr:
         social_youtube: "Youtube (privatif, appartient à Google)"
         university: Université
         url: URL
+      communication/website/agenda/category:
+        featured_image: Image à la une
+        featured_image_alt: Texte alternatif
+        name: Nom
+        slug: Slug
       communication/website/agenda/event:
         categories: Catégories
         dates: Dates
diff --git a/config/routes/admin/communication.rb b/config/routes/admin/communication.rb
index 8fde35bc6..8c2c32d80 100644
--- a/config/routes/admin/communication.rb
+++ b/config/routes/admin/communication.rb
@@ -54,14 +54,22 @@ namespace :communication do
         post :publish
       end
     end
-    namespace :agenda do
-      resources :events, controller: '/admin/communication/websites/agenda/events', path: '/:lang/events' do
+    namespace :agenda, path: '/:lang/agenda' do
+      resources :events, controller: '/admin/communication/websites/agenda/events' do
         member do
           get :static
           post :duplicate
           post :publish
         end
       end
+      resources :categories, controller: '/admin/communication/websites/agenda/categories' do
+        collection do
+          post :reorder
+        end
+        member do
+          get :static
+        end
+      end
     end
     resources :menus, controller: 'websites/menus', path: '/:lang/menus' do
       member do
diff --git a/db/migrate/20231128155822_create_communication_website_agenda_categories.rb b/db/migrate/20231128155822_create_communication_website_agenda_categories.rb
new file mode 100644
index 000000000..13e224049
--- /dev/null
+++ b/db/migrate/20231128155822_create_communication_website_agenda_categories.rb
@@ -0,0 +1,20 @@
+class CreateCommunicationWebsiteAgendaCategories < ActiveRecord::Migration[7.1]
+  def change
+    create_table :communication_website_agenda_categories, id: :uuid do |t|
+      t.string :name
+      t.string :path
+      t.integer :position
+      t.string :featured_image_alt
+      t.text :featured_image_credit
+      t.text :meta_description
+      t.string :slug
+      t.text :summary
+      t.references :communication_website, null: false, foreign_key: { to_table: :communication_websites }, type: :uuid, index: { name: 'idx_communication_website_agenda_cats_on_website_id' }
+      t.references :language, null: false, foreign_key: true, type: :uuid
+      t.references :original, foreign_key: {to_table: :communication_website_agenda_categories}, type: :uuid
+      t.references :university, null: false, foreign_key: true, type: :uuid
+
+      t.timestamps
+    end
+  end
+end
diff --git a/db/migrate/20231128160407_move_join_table_communication_website_agenda_categories.rb b/db/migrate/20231128160407_move_join_table_communication_website_agenda_categories.rb
new file mode 100644
index 000000000..5424f41e1
--- /dev/null
+++ b/db/migrate/20231128160407_move_join_table_communication_website_agenda_categories.rb
@@ -0,0 +1,5 @@
+class MoveJoinTableCommunicationWebsiteAgendaCategories < ActiveRecord::Migration[7.1]
+  def change
+    rename_column :communication_website_agenda_events_categories, :communication_website_category_id, :communication_website_agenda_category_id
+  end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 7bf56b6d1..a98447356 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,7 +10,7 @@
 #
 # It's strongly recommended that you check this file into your version control system.
 
-ActiveRecord::Schema[7.1].define(version: 2023_10_31_104523) do
+ActiveRecord::Schema[7.1].define(version: 2023_11_28_160407) do
   # These are extensions that must be enabled in order to support this database
   enable_extension "pgcrypto"
   enable_extension "plpgsql"
@@ -106,8 +106,8 @@ ActiveRecord::Schema[7.1].define(version: 2023_10_31_104523) do
     t.datetime "updated_at", null: false
     t.string "title"
     t.boolean "published", default: true
-    t.uuid "communication_website_id"
     t.uuid "heading_id"
+    t.uuid "communication_website_id"
     t.string "migration_identifier"
     t.index ["about_type", "about_id"], name: "index_communication_website_blocks_on_about"
     t.index ["communication_website_id"], name: "index_communication_blocks_on_communication_website_id"
@@ -229,11 +229,31 @@ ActiveRecord::Schema[7.1].define(version: 2023_10_31_104523) do
     t.text "home_sentence"
     t.text "sass"
     t.text "css"
-    t.boolean "allow_experiences_modification", default: true
     t.index ["about_type", "about_id"], name: "index_communication_extranets_on_about"
     t.index ["university_id"], name: "index_communication_extranets_on_university_id"
   end
 
+  create_table "communication_website_agenda_categories", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
+    t.string "name"
+    t.string "path"
+    t.integer "position"
+    t.string "featured_image_alt"
+    t.text "featured_image_credit"
+    t.text "meta_description"
+    t.string "slug"
+    t.text "summary"
+    t.uuid "communication_website_id", null: false
+    t.uuid "language_id", null: false
+    t.uuid "original_id"
+    t.uuid "university_id", null: false
+    t.datetime "created_at", null: false
+    t.datetime "updated_at", null: false
+    t.index ["communication_website_id"], name: "idx_communication_website_agenda_cats_on_website_id"
+    t.index ["language_id"], name: "index_communication_website_agenda_categories_on_language_id"
+    t.index ["original_id"], name: "index_communication_website_agenda_categories_on_original_id"
+    t.index ["university_id"], name: "index_communication_website_agenda_categories_on_university_id"
+  end
+
   create_table "communication_website_agenda_events", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
     t.string "title"
     t.text "summary"
@@ -264,9 +284,9 @@ ActiveRecord::Schema[7.1].define(version: 2023_10_31_104523) do
 
   create_table "communication_website_agenda_events_categories", id: false, force: :cascade do |t|
     t.uuid "communication_website_agenda_event_id", null: false
-    t.uuid "communication_website_category_id", null: false
-    t.index ["communication_website_agenda_event_id", "communication_website_category_id"], name: "event_category"
-    t.index ["communication_website_category_id", "communication_website_agenda_event_id"], name: "category_event"
+    t.uuid "communication_website_agenda_category_id", null: false
+    t.index ["communication_website_agenda_category_id", "communication_website_agenda_event_id"], name: "category_event"
+    t.index ["communication_website_agenda_event_id", "communication_website_agenda_category_id"], name: "event_category"
   end
 
   create_table "communication_website_categories", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t|
@@ -399,7 +419,7 @@ ActiveRecord::Schema[7.1].define(version: 2023_10_31_104523) do
     t.index ["university_id"], name: "index_communication_website_pages_on_university_id"
   end
 
-  create_table "communication_website_permalinks", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
+  create_table "communication_website_permalinks", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t|
     t.uuid "university_id", null: false
     t.uuid "website_id", null: false
     t.string "about_type", null: false
@@ -1133,6 +1153,10 @@ ActiveRecord::Schema[7.1].define(version: 2023_10_31_104523) do
   add_foreign_key "communication_extranet_posts", "universities"
   add_foreign_key "communication_extranet_posts", "university_people", column: "author_id"
   add_foreign_key "communication_extranets", "universities"
+  add_foreign_key "communication_website_agenda_categories", "communication_website_agenda_categories", column: "original_id"
+  add_foreign_key "communication_website_agenda_categories", "communication_websites"
+  add_foreign_key "communication_website_agenda_categories", "languages"
+  add_foreign_key "communication_website_agenda_categories", "universities"
   add_foreign_key "communication_website_agenda_events", "communication_website_agenda_events", column: "original_id"
   add_foreign_key "communication_website_agenda_events", "communication_website_agenda_events", column: "parent_id"
   add_foreign_key "communication_website_agenda_events", "communication_websites"
diff --git a/test/fixtures/communication/extranets.yml b/test/fixtures/communication/extranets.yml
index 5b00f559b..c4a725645 100644
--- a/test/fixtures/communication/extranets.yml
+++ b/test/fixtures/communication/extranets.yml
@@ -2,35 +2,34 @@
 #
 # Table name: communication_extranets
 #
-#  id                             :uuid             not null, primary key
-#  about_type                     :string           indexed => [about_id]
-#  allow_experiences_modification :boolean          default(TRUE)
-#  color                          :string
-#  cookies_policy                 :text
-#  css                            :text
-#  feature_alumni                 :boolean          default(FALSE)
-#  feature_contacts               :boolean          default(FALSE)
-#  feature_jobs                   :boolean          default(FALSE)
-#  feature_library                :boolean          default(FALSE)
-#  feature_posts                  :boolean          default(FALSE)
-#  has_sso                        :boolean          default(FALSE)
-#  home_sentence                  :text
-#  host                           :string
-#  name                           :string
-#  privacy_policy                 :text
-#  registration_contact           :string
-#  sass                           :text
-#  sso_button_label               :string
-#  sso_cert                       :text
-#  sso_mapping                    :jsonb
-#  sso_name_identifier_format     :string
-#  sso_provider                   :integer          default("saml")
-#  sso_target_url                 :string
-#  terms                          :text
-#  created_at                     :datetime         not null
-#  updated_at                     :datetime         not null
-#  about_id                       :uuid             indexed => [about_type]
-#  university_id                  :uuid             not null, indexed
+#  id                         :uuid             not null, primary key
+#  about_type                 :string           indexed => [about_id]
+#  color                      :string
+#  cookies_policy             :text
+#  css                        :text
+#  feature_alumni             :boolean          default(FALSE)
+#  feature_contacts           :boolean          default(FALSE)
+#  feature_jobs               :boolean          default(FALSE)
+#  feature_library            :boolean          default(FALSE)
+#  feature_posts              :boolean          default(FALSE)
+#  has_sso                    :boolean          default(FALSE)
+#  home_sentence              :text
+#  host                       :string
+#  name                       :string
+#  privacy_policy             :text
+#  registration_contact       :string
+#  sass                       :text
+#  sso_button_label           :string
+#  sso_cert                   :text
+#  sso_mapping                :jsonb
+#  sso_name_identifier_format :string
+#  sso_provider               :integer          default("saml")
+#  sso_target_url             :string
+#  terms                      :text
+#  created_at                 :datetime         not null
+#  updated_at                 :datetime         not null
+#  about_id                   :uuid             indexed => [about_type]
+#  university_id              :uuid             not null, indexed
 #
 # Indexes
 #
diff --git a/test/fixtures/communication/website/agenda/categories.yml b/test/fixtures/communication/website/agenda/categories.yml
new file mode 100644
index 000000000..80aea801a
--- /dev/null
+++ b/test/fixtures/communication/website/agenda/categories.yml
@@ -0,0 +1,62 @@
+# == Schema Information
+#
+# Table name: communication_website_agenda_categories
+#
+#  id                       :uuid             not null, primary key
+#  featured_image_alt       :string
+#  featured_image_credit    :text
+#  meta_description         :text
+#  name                     :string
+#  path                     :string
+#  position                 :integer
+#  slug                     :string
+#  summary                  :text
+#  created_at               :datetime         not null
+#  updated_at               :datetime         not null
+#  communication_website_id :uuid             not null, indexed
+#  language_id              :uuid             not null, indexed
+#  original_id              :uuid             indexed
+#  university_id            :uuid             not null, indexed
+#
+# Indexes
+#
+#  idx_communication_website_agenda_cats_on_website_id             (communication_website_id)
+#  index_communication_website_agenda_categories_on_language_id    (language_id)
+#  index_communication_website_agenda_categories_on_original_id    (original_id)
+#  index_communication_website_agenda_categories_on_university_id  (university_id)
+#
+# Foreign Keys
+#
+#  fk_rails_1e1b9fbf33  (original_id => communication_website_agenda_categories.id)
+#  fk_rails_6cb9a4b8a1  (university_id => universities.id)
+#  fk_rails_7b5ad84dda  (communication_website_id => communication_websites.id)
+#  fk_rails_b0ddee638d  (language_id => languages.id)
+#
+
+one:
+  name: MyString
+  path: MyString
+  position: 1
+  featured_image_alt: MyString
+  featured_image_credit: MyText
+  meta_description: MyText
+  slug: MyString
+  summary: MyText
+  communication_website: one
+  language: one
+  original: one
+  university: one
+
+two:
+  name: MyString
+  path: MyString
+  position: 1
+  featured_image_alt: MyString
+  featured_image_credit: MyText
+  meta_description: MyText
+  slug: MyString
+  summary: MyText
+  communication_website: two
+  language: two
+  original: two
+  university: two
diff --git a/test/models/communication/website/agenda/category_test.rb b/test/models/communication/website/agenda/category_test.rb
new file mode 100644
index 000000000..2f1dfdb7f
--- /dev/null
+++ b/test/models/communication/website/agenda/category_test.rb
@@ -0,0 +1,41 @@
+# == Schema Information
+#
+# Table name: communication_website_agenda_categories
+#
+#  id                       :uuid             not null, primary key
+#  featured_image_alt       :string
+#  featured_image_credit    :text
+#  meta_description         :text
+#  name                     :string
+#  path                     :string
+#  position                 :integer
+#  slug                     :string
+#  summary                  :text
+#  created_at               :datetime         not null
+#  updated_at               :datetime         not null
+#  communication_website_id :uuid             not null, indexed
+#  language_id              :uuid             not null, indexed
+#  original_id              :uuid             indexed
+#  university_id            :uuid             not null, indexed
+#
+# Indexes
+#
+#  idx_communication_website_agenda_cats_on_website_id             (communication_website_id)
+#  index_communication_website_agenda_categories_on_language_id    (language_id)
+#  index_communication_website_agenda_categories_on_original_id    (original_id)
+#  index_communication_website_agenda_categories_on_university_id  (university_id)
+#
+# Foreign Keys
+#
+#  fk_rails_1e1b9fbf33  (original_id => communication_website_agenda_categories.id)
+#  fk_rails_6cb9a4b8a1  (university_id => universities.id)
+#  fk_rails_7b5ad84dda  (communication_website_id => communication_websites.id)
+#  fk_rails_b0ddee638d  (language_id => languages.id)
+#
+require "test_helper"
+
+class Communication::Website::Agenda::CategoryTest < ActiveSupport::TestCase
+  # test "the truth" do
+  #   assert true
+  # end
+end
-- 
GitLab