diff --git a/mo/rights.py b/mo/rights.py
index d990340e48afa92d50799b1ef35bb5769a83fa1c..e581dfe6d304c8a8971fe2503bee0baf410f3daf 100644
--- a/mo/rights.py
+++ b/mo/rights.py
@@ -200,28 +200,31 @@ class Rights:
 
     # Interní rozhodovaní o dostupnosti zadání
 
-    def _check_view_statement(self, round: db.Round):
+    def _check_view_statement(self, round: db.Round) -> tuple[bool, str]:
         if round.tasks_file is None:
-            return False
+            return False, "zatím chybí"
 
         if self.have_right(Right.manage_round):
             # Správce kola může vždy všechno
-            return True
-
-        # Pokud už soutěž skončila, přístup k zadání má každý org.
-        # XXX: Rozhodujeme podle stavu kola, nikoliv soutěže!
-        if round.state in [db.RoundState.grading, db.RoundState.closed]:
-            return True
+            return True, ""
 
         # Od stanoveného času vidí zadání orgové s právem view_statement.
-        if (self.have_right(Right.view_statement)
-                and round.state != db.RoundState.preparing
-                and round.pr_tasks_start is not None
-                and mo.now >= round.pr_tasks_start):
-            return True
+        if (self.have_right(Right.view_statement)):
+            # Ve zbylých případech jsme konzervativní a zadání neukazujeme
+            if (round.state == db.RoundState.preparing
+                or round.pr_tasks_start is None):
+                return False, "až začne soutěž"
+            if mo.now < round.pr_tasks_start:
+                return False, mo.util_format.timedelta(round.pr_tasks_start)
 
-        # Ve zbylých případech jsme konzervativní a zadání neukazujeme
-        return False
+            return True, ""
+
+        if round.state not in [db.RoundState.grading, db.RoundState.closed]:
+            return False, "až začnou opravy"
+
+        # Pokud už soutěž skončila, přístup k zadání má každý org.
+        # XXX: Rozhodujeme podle stavu kola, nikoliv soutěže!
+        return True, ""
 
 
 class RoundRights(Rights):
@@ -279,25 +282,11 @@ class ContestRights(Rights):
                 or self.have_right(Right.manage_contest))
 
     def can_view_statement(self) -> bool:
-        return self._check_view_statement(self.contest.round)
+        can, _ = self._check_view_statement(self.contest.round)
+        return can
 
-    def cannot_view_statement_reason(self) -> str:
-        round = self.contest.round
-        if round.tasks_file is None:
-            return "zatím chybí"
-        if self.have_right(Right.manage_round):
-            # Správce kola může vždy všechno
-            return ""
-        if self.have_right(Right.view_statement):
-            if round.state == db.RoundState.preparing:
-                return "až začne soutěž"
-            if round.state == db.RoundState.running and round.pr_tasks_start is None:
-                return "až začnou opravy"
-            if mo.now < round.pr_tasks_start:
-                return mo.util_format.timedelta(round.pr_tasks_start)
-        if round.state not in [db.RoundState.grading, db.RoundState.closed]:
-            return "až začnou opravy"
-        return ""
+    def can_view_statement_with_reason(self) -> (bool, str):
+        return self._check_view_statement(self.contest.round)
 
 
 class Gatekeeper:
diff --git a/mo/web/org.py b/mo/web/org.py
index 142c741fc7a209d0155d2e2b6d2f9a7dd0e27374..d9d49b04e5ea44e0c25614ed31ec792599a08734 100644
--- a/mo/web/org.py
+++ b/mo/web/org.py
@@ -30,30 +30,38 @@ def org_index():
             flash('ID uživatele musí být číslo', 'danger')
 
     sess = db.get_session()
+    contests_with_role = sess.execute("""
+        SELECT contest_id, MIN(role) as role
+        FROM contests
+            JOIN user_roles ur USING(place_id)
+            JOIN rounds r USING(round_id)
+            JOIN users USING(user_id)
+        WHERE
+            user_id = :user_id
+            AND r.year = :current_year
+            AND (ur.year IS NULL OR ur.year = r.year)
+            AND (ur.category IS NULL OR ur.category = r.category)
+            AND (ur.seq IS NULL OR ur.seq = r.seq)
+        GROUP BY contest_id, r.level, r.category, place_id, r.seq, r.part
+        ORDER BY r.level, r.category, place_id, r.seq, r.part
+    """, {'user_id': g.user.user_id, 'current_year': mo.current_year})
 
-    roles = (sess.query(db.UserRole)
-             .filter_by(user_id=g.user.user_id)
-             .options(joinedload(db.UserRole.place))
-             .all())
+    contest_ids = []
+    roles = []
+    for row in contests_with_role:
+        contest_ids.append(row['contest_id'])
+        roles.append(row['role'])
 
-    contests = []
-    for role in roles:
-        q = (sess.query(db.Contest, db.UserRole.role, db.Round)
-                .select_from(db.Contest)
-                .filter_by(place_id=role.place_id)
-                .join(db.UserRole, db.UserRole.user_role_id == role.user_role_id)
-                .join(db.Round))
-        if role.year:
-            q = q.filter(db.Round.year == role.year)
-        if role.category:
-            q = q.filter(db.Round.category == role.category)
-        if role.seq:
-            q = q.filter(db.Round.seq == role.seq)
-        contests += q.options(joinedload(db.Contest.place)).all()
+    contests = (sess.query(db.Contest)
+            .select_from(db.Contest)
+            .join(db.Round)
+            .filter(db.Contest.contest_id.in_(contest_ids))
+            .options(joinedload(db.Contest.place))
+            .order_by(db.Round.level, db.Round.category, db.Contest.place_id,
+                db.Round.seq, db.Round.part)
+            .all())
 
-    contests.sort(key=lambda r: (r[2].year, r[2].category, r[2].seq, r[2].part))
-
-    return render_template('org_index.html', contests=contests, Right=Right,
+    return render_template('org_index.html', contests=zip(contests, roles), Right=Right,
             role_names=db.role_type_names, gatekeeper=g.gatekeeper)
 
 
diff --git a/mo/web/templates/org_index.html b/mo/web/templates/org_index.html
index b631bcb29c1fe6c74565b3154791b7b09f3c01e9..f35a95a7a1d30629fc9c31acb4f751e3c6b436dd 100644
--- a/mo/web/templates/org_index.html
+++ b/mo/web/templates/org_index.html
@@ -6,51 +6,49 @@
 <h3>Moje soutěže</h3>
 
 <table class="table table-bordered">
+{% set curr = namespace(level = -1) %}
+{% for c, role in contests %}
+	{% set cr = gatekeeper.rights_for_contest(c) %}
+	{% if curr.level != c.round.level %}
 	<thead><tr>
 		<th>Kategorie
 		<th>Kolo
+		<th>{{ c.round.get_level().name|capitalize }}
 		<th>Stav
 		<th>Zadání
 		<th>Moje role
 		<th>Odkazy
 	</thead>
-	{% for c,role,r in contests %}
-	{% set rr = gatekeeper.rights_for_contest(c) %}
-	{% if c.state == RoundState.preparing %}
-		<tr class="warning">
-	{% elif c.state == RoundState.running %}
-		<tr class="success">
-	{% elif c.state == RoundState.grading %}
-		<tr class="info">
-	{% else %}
-		<tr>
+		{% set curr.level = c.round.level %}
 	{% endif %}
-		<td class="text-center" style="font-size: 1.2em"><b>{{ r.category }}</b>
-		<td>{{ r.name }} {{ c.place.name_locativ() if c.place.level > 0 else '' }}
+	<tr class="rstate-{{c.state.name}}">
+		<td class="text-center"><b>{{ c.round.category }}</b>
+		<td>{{ c.round.name }}
+		<td>{{ c.place.name }}
 		<td>{{ c.state.friendly_name() }}
 		<td>
-			{% if rr.can_view_statement() %}
-				<a href='{{ url_for('org_task_statement', id=r.round_id) }}'>stáhnout</a>
+			{% set can, reason = cr.can_view_statement_with_reason() %}
+			{% if can %}
+				<a href='{{ url_for('org_task_statement', id=c.round_id) }}'>stáhnout</a>
 			{% else %}
-				{{ rr.cannot_view_statement_reason() }}
+				{{ reason }}
 			{% endif %}
 		<td>{{ role_names[role] }}
 		<td>
 			<a class="btn btn-xs btn-primary" href='{{ url_for('org_contest', id=c.contest_id) }}'>Detail</a>
 			<a class="btn btn-xs btn-default" href='{{ url_for('org_contest_list', id=c.contest_id) }}'>Účastníci</a>
-			{% if rr.can_view_submits() and c.state != RoundState.preparing %}
+			{% if cr.have_right(Right.view_submits) and c.state != RoundState.preparing %}
 			<a class="btn btn-xs btn-success" href='{{ url_for('org_contest_solutions', id=c.contest_id) }}'>Odevzdaná řešení</a>
 			{% endif %}
-			{% if (c.state == RoundState.grading and rr.can_view_submits()) or c.state == RoundState.closed %}
+			{% if (c.state == RoundState.grading and cr.have_right(Right.view_submits)) or c.state == RoundState.closed %}
 			<a class="btn btn-xs btn-warning" href='{{ url_for('org_score', contest_id=c.contest_id) }}'>Výsledky</a>
 			{% endif %}
-			{% if rr.have_right(Right.manage_contest) and c.state != RoundState.closed %}
-			<a class="btn btn-xs btn-primary" href='{{ url_for('org_contest_add_user', id=c.contest_id) }}'>+ Přidat účastníka</a>
+			{% if cr.have_right(Right.manage_contest) and c.state == RoundState.preparing %}
 			<a class="btn btn-xs btn-default" href='{{ url_for('org_contest_import', id=c.contest_id) }}'>Import</a>
 			{% endif %}
 		</td>
 	</tr>
-	{% endfor %}
+{% endfor %}
 </table>
 {% endif %}