diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb
index 3e74dea87f6c1d4315d3f8120bb87b404d13a14d..2a1503d2e27d4c281241ce225ffc38cc73e4c184 100644
--- a/app/controllers/users_controller.rb
+++ b/app/controllers/users_controller.rb
@@ -1,2 +1,5 @@
 class UsersController < ApplicationController
+  def leaderboard
+    @workload_data = User.leaderboard
+  end
 end
diff --git a/app/models/session.rb b/app/models/session.rb
index bfc22477df2618024727a5b4fab557e3d781fcf7..3f4ff8406e3b18ec632fa6ee359cdac77ee15527 100644
--- a/app/models/session.rb
+++ b/app/models/session.rb
@@ -56,6 +56,10 @@ class Session < ApplicationRecord
     return filedrop_files.exists? || filedrop_comments.exists?
   end
 
+  def duration_minutes
+    return (ends_at - starts_at) / 60.0
+  end
+
   private
 
   def notify_if_changed
diff --git a/app/models/user.rb b/app/models/user.rb
index aa06e39cb5cd512b3601619c98cc73fb57c99336..1652be201b8e4276a2f034f57975d1d063d06e39 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -30,6 +30,23 @@ class User < ApplicationRecord
     end
   end
 
+  def self.leaderboard
+    all.map { |u| [u.name, u.workload_minutes] }
+      .sort_by { |_, workload| -workload }
+      .reject { |_, workload| workload.zero? }
+      .to_h
+  end
+
+  class Session < ApplicationRecord
+    has_many :assignments
+
+    def workload_minutes
+      # Logic to calculate workload in minutes
+      60 # This is just a placeholder
+    end
+  end
+
+
   def errors
     super.tap { |errors| errors.delete(:password, :blank) if password.nil? }
   end
@@ -66,6 +83,10 @@ class User < ApplicationRecord
     self.avatar_color = "##{r.to_s(16).rjust(2, '0')}#{g.to_s(16).rjust(2, '0')}#{b.to_s(16).rjust(2, '0')}"
   end
 
+  def workload_minutes
+    Assignment.includes(:session).where(user: self).sum { | a | a.session.duration_minutes }
+  end
+
   private
 
   def valid_invitation_token
diff --git a/app/views/users/leaderboard.html.erb b/app/views/users/leaderboard.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..fb22a0c21453eb21c290e475ac826bd3d53613c8
--- /dev/null
+++ b/app/views/users/leaderboard.html.erb
@@ -0,0 +1,22 @@
+<div class="w-full">
+  <h1 class="text-xl my-4">Leaderboard</h1>
+    <dl class="w-full max-w-4xl mx-auto">
+      <% top_workload = @workload_data.values.max %>
+      <% @workload_data.each_with_index do |(username, workload), index| %>
+      <% hours, minutes = workload.divmod(60) %>
+        <div class="bg-gray-50 px-4 py-2 sm:flex sm:items-center sm:gap-4">
+          <dt class="text-sm font-medium text-gray-500 flex-none w-1/4 sm:w-1/6"><%= username %><%= " 👑" if index == 0 %></dt>
+          <dd class="mt-1 text-sm text-gray-900 sm:mt-0 sm:flex-1">
+            <div class="relative pt-1">
+              <div class="overflow-hidden h-2 mb-1 text-xs flex rounded bg-green-200">
+                <div style="width:<%= (100 * workload / top_workload).round %>%;" class="shadow-none flex flex-col text-center whitespace-nowrap text-white justify-center bg-green-500"></div>
+              </div>
+              <span class="text-xs font-semibold inline-block py-1 px-2 uppercase rounded text-green-600 bg-green-200 last:mr-0 mr-1">
+                <%= hours > 0 ? "#{hours}h #{minutes.round}m" : "#{minutes.round} minutes" %>
+              </span>
+            </div>
+          </dd>
+        </div>
+      <% end %>
+    </dl>
+</div>
diff --git a/config/routes.rb b/config/routes.rb
index b66d9e15c7db2e780ff88600e404ba616f812299..a2c4b3993e8283c6abca2c9b63e955e615dd6e76 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -4,6 +4,7 @@ Rails.application.routes.draw do
   mount ActionCable.server => '/cable'
 
   get 'speakers/show'
+  get 'users/leaderboard'
 
   # Reveal health status on /up that returns 200 if the app boots with no exceptions, otherwise 500.
   # Can be used by load balancers and uptime monitors to verify that the app is live.