diff --git a/apps/dashboard/src/lib/features/course/components/lesson/content-page-menu.svelte b/apps/dashboard/src/lib/features/course/components/lesson/content-page-menu.svelte
new file mode 100644
index 00000000..0b8ed273
--- /dev/null
+++ b/apps/dashboard/src/lib/features/course/components/lesson/content-page-menu.svelte
@@ -0,0 +1,112 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {$t('course.navItem.lessons.content_menu.unlock_all')}
+
+
+
+
+
+ {contentGroupingEnabled
+ ? $t('course.navItem.lessons.content_menu.disable_grouping')
+ : $t('course.navItem.lessons.content_menu.enable_grouping')}
+
+
+
+
+
diff --git a/apps/dashboard/src/lib/features/course/pages/settings.svelte b/apps/dashboard/src/lib/features/course/pages/settings.svelte
index e30f1366..dc375a33 100644
--- a/apps/dashboard/src/lib/features/course/pages/settings.svelte
+++ b/apps/dashboard/src/lib/features/course/pages/settings.svelte
@@ -25,7 +25,6 @@
import LockOpenIcon from '@lucide/svelte/icons/lock-open';
import type { TCourseType } from '@cio/db/types';
import type { Course } from '../utils/types';
- import { ContentType } from '@cio/utils/constants/content';
import { t } from '$lib/utils/functions/translations';
import { isObject } from '$lib/utils/functions/isObject';
import { snackbar } from '$features/ui/snackbar/store';
@@ -33,6 +32,7 @@
import { DEFAULT_COMPLIANCE_SETTINGS } from '../utils/compliance-utils';
import { DeleteModal } from '$features/ui';
import { contentApi, courseApi } from '$features/course/api';
+ import { collectLockedContentItems } from '$features/course/utils/content-lock-utils';
import { tagApi } from '$features/tag/api';
import { uploadImage } from '$lib/utils/services/upload';
import { copyToClipboard } from '$lib/utils/functions/formatYoutubeVideo';
@@ -120,22 +120,6 @@
let isUnlockingAll = $state(false);
- type LockedContentItem = { id: string; type: ContentType.Lesson | ContentType.Exercise };
-
- function collectLockedContentItems(course: Course | null): LockedContentItem[] {
- const content = course?.content;
- if (!content) return [];
-
- const items = content.grouped ? content.sections.flatMap((section) => section.items) : content.items;
-
- return items
- .filter((item) => (item.isUnlocked ?? true) === false)
- .map((item) => ({
- id: item.id,
- type: item.type === ContentType.Exercise ? ContentType.Exercise : ContentType.Lesson
- }));
- }
-
const lockedContentItems = $derived(collectLockedContentItems(courseApi.course));
const isLiveClassCourse = $derived(courseApi.course?.type === 'LIVE_CLASS');
const showLockedContentNotice = $derived($settings.isPublished && lockedContentItems.length > 0);
diff --git a/apps/dashboard/src/lib/features/course/utils/content-lock-utils.ts b/apps/dashboard/src/lib/features/course/utils/content-lock-utils.ts
index 7cd7a492..471ff478 100644
--- a/apps/dashboard/src/lib/features/course/utils/content-lock-utils.ts
+++ b/apps/dashboard/src/lib/features/course/utils/content-lock-utils.ts
@@ -1,5 +1,22 @@
import { ContentType } from '@cio/utils/constants/content';
import type { StudentContentLockReason } from '$features/ai-assistant/utils/content-ask-ai-bar';
+import type { Course } from './types';
+import { getCourseContent } from './content';
+
+export type LockedContentItem = { id: string; type: ContentType.Lesson | ContentType.Exercise };
+
+export function collectLockedContentItems(course: Course | null): LockedContentItem[] {
+ const content = getCourseContent(course);
+
+ const items = content.grouped ? content.sections.flatMap((section) => section.items) : content.items;
+
+ return items
+ .filter((item) => (item.isUnlocked ?? true) === false)
+ .map((item) => ({
+ id: item.id,
+ type: item.type === ContentType.Exercise ? ContentType.Exercise : ContentType.Lesson
+ }));
+}
export function getStudentContentLockTitleKey(reason: StudentContentLockReason): string {
if (reason === 'progression_locked') {
diff --git a/apps/dashboard/src/lib/utils/translations/da.json b/apps/dashboard/src/lib/utils/translations/da.json
index a3dcfffa..60c1a744 100644
--- a/apps/dashboard/src/lib/utils/translations/da.json
+++ b/apps/dashboard/src/lib/utils/translations/da.json
@@ -289,7 +289,10 @@
"successful_integration": "Integration lykkedes",
"successful_deletion": "Integration slettet",
"update_successful": "Opdateringen lykkedes",
- "unlocked_all_content": "Alle kursusartikler er låst op"
+ "unlocked_all_content": "Alle kursusartikler er låst op",
+ "all_content_already_unlocked": "Alt indhold er allerede låst op",
+ "content_grouping_enabled": "Indholdsgruppering aktiveret",
+ "content_grouping_disabled": "Indholdsgruppering deaktiveret"
}
},
"people": {
@@ -1320,6 +1323,12 @@
"add_content_create_lesson": "Opret lektion",
"add_content_section_title_placeholder": "f.eks. Modul 1: Introduktion",
"add_content_lesson_title_placeholder": "f.eks. Kom godt i gang med variabler",
+ "content_menu": {
+ "label": "Indholdsindstillinger",
+ "unlock_all": "Lås alt indhold op",
+ "enable_grouping": "Aktivér indholdsgruppering",
+ "disable_grouping": "Deaktivér indholdsgruppering"
+ },
"join_lesson": "Deltag i lektionen",
"no_link": "Intet link",
"no_tutor": "Ingen underviser tilføjet",
diff --git a/apps/dashboard/src/lib/utils/translations/de.json b/apps/dashboard/src/lib/utils/translations/de.json
index 8200beec..8506926f 100644
--- a/apps/dashboard/src/lib/utils/translations/de.json
+++ b/apps/dashboard/src/lib/utils/translations/de.json
@@ -289,7 +289,10 @@
"successful_integration": "Integration erfolgreich",
"successful_deletion": "Integration gelöscht",
"update_successful": "Update erfolgreich",
- "unlocked_all_content": "Alle Kursgegenstände freigeschaltet"
+ "unlocked_all_content": "Alle Kursgegenstände freigeschaltet",
+ "all_content_already_unlocked": "Alle Inhalte sind bereits freigeschaltet",
+ "content_grouping_enabled": "Inhaltsgruppierung aktiviert",
+ "content_grouping_disabled": "Inhaltsgruppierung deaktiviert"
}
},
"people": {
@@ -1320,6 +1323,12 @@
"add_content_create_lesson": "Lektion erstellen",
"add_content_section_title_placeholder": "z. B. Modul 1: Einführung",
"add_content_lesson_title_placeholder": "z. B. Erste Schritte mit Variablen",
+ "content_menu": {
+ "label": "Inhaltsoptionen",
+ "unlock_all": "Alle Inhalte freischalten",
+ "enable_grouping": "Inhaltsgruppierung aktivieren",
+ "disable_grouping": "Inhaltsgruppierung deaktivieren"
+ },
"join_lesson": "Nehmen Sie an der Lektion teil",
"no_link": "Kein Link",
"no_tutor": "Kein Tutor hinzugefügt",
diff --git a/apps/dashboard/src/lib/utils/translations/en.json b/apps/dashboard/src/lib/utils/translations/en.json
index 0458ae76..cf5a9482 100644
--- a/apps/dashboard/src/lib/utils/translations/en.json
+++ b/apps/dashboard/src/lib/utils/translations/en.json
@@ -2236,6 +2236,12 @@
},
"add_content_section_title_placeholder": "e.g., Module 1: Introduction",
"add_content_lesson_title_placeholder": "e.g., Getting Started with Variables",
+ "content_menu": {
+ "label": "Content options",
+ "unlock_all": "Unlock all content",
+ "enable_grouping": "Enable content grouping",
+ "disable_grouping": "Disable content grouping"
+ },
"join_lesson": "Join lesson",
"no_link": "No link",
"no_tutor": "No tutor added",
@@ -4173,6 +4179,9 @@
"course_settings": {
"success": {
"unlocked_all_content": "All course items unlocked",
+ "all_content_already_unlocked": "All content is already unlocked",
+ "content_grouping_enabled": "Content grouping enabled",
+ "content_grouping_disabled": "Content grouping disabled",
"download": "Download Complete",
"saved": "Saved successfully",
"successful_integration": "Integration successful",
diff --git a/apps/dashboard/src/lib/utils/translations/es.json b/apps/dashboard/src/lib/utils/translations/es.json
index 42903d61..02eeb8ac 100644
--- a/apps/dashboard/src/lib/utils/translations/es.json
+++ b/apps/dashboard/src/lib/utils/translations/es.json
@@ -289,7 +289,10 @@
"successful_integration": "Integración exitosa",
"successful_deletion": "Integración eliminada",
"update_successful": "Actualización exitosa",
- "unlocked_all_content": "Todos los elementos del curso desbloqueados"
+ "unlocked_all_content": "Todos los elementos del curso desbloqueados",
+ "all_content_already_unlocked": "Todo el contenido ya está desbloqueado",
+ "content_grouping_enabled": "Agrupación de contenidos activada",
+ "content_grouping_disabled": "Agrupación de contenidos desactivada"
}
},
"people": {
@@ -1320,6 +1323,12 @@
"add_content_create_lesson": "Crear lección",
"add_content_section_title_placeholder": "por ejemplo, Módulo 1: Introducción",
"add_content_lesson_title_placeholder": "por ejemplo, Introducción a las variables",
+ "content_menu": {
+ "label": "Opciones de contenido",
+ "unlock_all": "Desbloquear todo el contenido",
+ "enable_grouping": "Activar agrupación de contenidos",
+ "disable_grouping": "Desactivar agrupación de contenidos"
+ },
"join_lesson": "Unirse a la lección",
"no_link": "Sin enlace",
"no_tutor": "No se agregó ningún tutor",
diff --git a/apps/dashboard/src/lib/utils/translations/fr.json b/apps/dashboard/src/lib/utils/translations/fr.json
index 39eadfca..0212c651 100644
--- a/apps/dashboard/src/lib/utils/translations/fr.json
+++ b/apps/dashboard/src/lib/utils/translations/fr.json
@@ -289,7 +289,10 @@
"successful_integration": "Intégration réussie",
"successful_deletion": "Intégration supprimée",
"update_successful": "Mise à jour réussie",
- "unlocked_all_content": "Tous les éléments de cours débloqués"
+ "unlocked_all_content": "Tous les éléments de cours débloqués",
+ "all_content_already_unlocked": "Tout le contenu est déjà déverrouillé",
+ "content_grouping_enabled": "Regroupement de contenu activé",
+ "content_grouping_disabled": "Regroupement de contenu désactivé"
}
},
"people": {
@@ -1320,6 +1323,12 @@
"add_content_create_lesson": "Créer une leçon",
"add_content_section_title_placeholder": "par exemple, Module 1 : Introduction",
"add_content_lesson_title_placeholder": "par exemple, Premiers pas avec les variables",
+ "content_menu": {
+ "label": "Options de contenu",
+ "unlock_all": "Déverrouiller tout le contenu",
+ "enable_grouping": "Activer le regroupement de contenu",
+ "disable_grouping": "Désactiver le regroupement de contenu"
+ },
"join_lesson": "Rejoindre la leçon",
"no_link": "Pas de lien",
"no_tutor": "Aucun tuteur ajouté",
diff --git a/apps/dashboard/src/lib/utils/translations/hi.json b/apps/dashboard/src/lib/utils/translations/hi.json
index 2e4ed722..18ccfb0b 100644
--- a/apps/dashboard/src/lib/utils/translations/hi.json
+++ b/apps/dashboard/src/lib/utils/translations/hi.json
@@ -289,7 +289,10 @@
"successful_integration": "एकीकरण सफल",
"successful_deletion": "एकीकरण हटा दिया गया",
"update_successful": "अद्यतन सफल",
- "unlocked_all_content": "सभी पाठ्यक्रम आइटम अनलॉक हो गए"
+ "unlocked_all_content": "सभी पाठ्यक्रम आइटम अनलॉक हो गए",
+ "all_content_already_unlocked": "सभी सामग्री पहले से अनलॉक है",
+ "content_grouping_enabled": "सामग्री समूहीकरण सक्षम",
+ "content_grouping_disabled": "सामग्री समूहीकरण अक्षम"
}
},
"people": {
@@ -1320,6 +1323,12 @@
"add_content_create_lesson": "पाठ बनाएँ",
"add_content_section_title_placeholder": "उदाहरण के लिए, मॉड्यूल 1: परिचय",
"add_content_lesson_title_placeholder": "उदाहरण के लिए, वेरिएबल्स के साथ शुरुआत करना",
+ "content_menu": {
+ "label": "सामग्री विकल्प",
+ "unlock_all": "सभी सामग्री अनलॉक करें",
+ "enable_grouping": "सामग्री समूहीकरण सक्षम करें",
+ "disable_grouping": "सामग्री समूहीकरण अक्षम करें"
+ },
"join_lesson": "पाठ से जुड़ें",
"no_link": "कोई लिंक नहीं",
"no_tutor": "कोई शिक्षक नहीं जोड़ा गया",
diff --git a/apps/dashboard/src/lib/utils/translations/pl.json b/apps/dashboard/src/lib/utils/translations/pl.json
index a4c7ca99..def446c3 100644
--- a/apps/dashboard/src/lib/utils/translations/pl.json
+++ b/apps/dashboard/src/lib/utils/translations/pl.json
@@ -289,7 +289,10 @@
"successful_integration": "Integracja udana",
"successful_deletion": "Integracja usunięta",
"update_successful": "Aktualizacja pomyślna",
- "unlocked_all_content": "Wszystkie elementy kursu odblokowane"
+ "unlocked_all_content": "Wszystkie elementy kursu odblokowane",
+ "all_content_already_unlocked": "Cała treść jest już odblokowana",
+ "content_grouping_enabled": "Grupowanie treści włączone",
+ "content_grouping_disabled": "Grupowanie treści wyłączone"
}
},
"people": {
@@ -1320,6 +1323,12 @@
"add_content_create_lesson": "Utwórz lekcję",
"add_content_section_title_placeholder": "np. Moduł 1: Wprowadzenie",
"add_content_lesson_title_placeholder": "np. Pierwsze kroki ze zmiennymi",
+ "content_menu": {
+ "label": "Opcje treści",
+ "unlock_all": "Odblokuj całą treść",
+ "enable_grouping": "Włącz grupowanie treści",
+ "disable_grouping": "Wyłącz grupowanie treści"
+ },
"join_lesson": "Dołącz do lekcji",
"no_link": "Brak linku",
"no_tutor": "Nie dodano żadnego nauczyciela",
diff --git a/apps/dashboard/src/lib/utils/translations/pt.json b/apps/dashboard/src/lib/utils/translations/pt.json
index 9563399b..d6abaaad 100644
--- a/apps/dashboard/src/lib/utils/translations/pt.json
+++ b/apps/dashboard/src/lib/utils/translations/pt.json
@@ -289,7 +289,10 @@
"successful_integration": "Integração bem-sucedida",
"successful_deletion": "Integração excluída",
"update_successful": "Atualização bem-sucedida",
- "unlocked_all_content": "Todos os itens do curso desbloqueados"
+ "unlocked_all_content": "Todos os itens do curso desbloqueados",
+ "all_content_already_unlocked": "Todo o conteúdo já está desbloqueado",
+ "content_grouping_enabled": "Agrupamento de conteúdo ativado",
+ "content_grouping_disabled": "Agrupamento de conteúdo desativado"
}
},
"people": {
@@ -1320,6 +1323,12 @@
"add_content_create_lesson": "Criar aula",
"add_content_section_title_placeholder": "por exemplo, Módulo 1: Introdução",
"add_content_lesson_title_placeholder": "por exemplo, Introdução às variáveis",
+ "content_menu": {
+ "label": "Opções de conteúdo",
+ "unlock_all": "Desbloquear todo o conteúdo",
+ "enable_grouping": "Ativar agrupamento de conteúdo",
+ "disable_grouping": "Desativar agrupamento de conteúdo"
+ },
"join_lesson": "Participar da aula",
"no_link": "Sem link",
"no_tutor": "Nenhum tutor adicionado",
diff --git a/apps/dashboard/src/lib/utils/translations/ru.json b/apps/dashboard/src/lib/utils/translations/ru.json
index d6c17f26..215db764 100644
--- a/apps/dashboard/src/lib/utils/translations/ru.json
+++ b/apps/dashboard/src/lib/utils/translations/ru.json
@@ -289,7 +289,10 @@
"successful_integration": "Интеграция прошла успешно",
"successful_deletion": "Интеграция удалена",
"update_successful": "Обновление успешно выполнено",
- "unlocked_all_content": "Все предметы курса разблокированы"
+ "unlocked_all_content": "Все предметы курса разблокированы",
+ "all_content_already_unlocked": "Весь контент уже разблокирован",
+ "content_grouping_enabled": "Группировка контента включена",
+ "content_grouping_disabled": "Группировка контента отключена"
}
},
"people": {
@@ -1320,6 +1323,12 @@
"add_content_create_lesson": "Создать урок",
"add_content_section_title_placeholder": "например, Модуль 1: Введение",
"add_content_lesson_title_placeholder": "например, Начало работы с переменными",
+ "content_menu": {
+ "label": "Параметры контента",
+ "unlock_all": "Разблокировать весь контент",
+ "enable_grouping": "Включить группировку контента",
+ "disable_grouping": "Отключить группировку контента"
+ },
"join_lesson": "Присоединиться к уроку",
"no_link": "Нет ссылки",
"no_tutor": "Репетитор не добавлен",
diff --git a/apps/dashboard/src/lib/utils/translations/vi.json b/apps/dashboard/src/lib/utils/translations/vi.json
index b9d2fdbc..6a4370ed 100644
--- a/apps/dashboard/src/lib/utils/translations/vi.json
+++ b/apps/dashboard/src/lib/utils/translations/vi.json
@@ -289,7 +289,10 @@
"successful_integration": "Tích hợp thành công",
"successful_deletion": "Đã xóa tích hợp",
"update_successful": "Cập nhật thành công",
- "unlocked_all_content": "Tất cả các mục khóa học đã được mở khóa"
+ "unlocked_all_content": "Tất cả các mục khóa học đã được mở khóa",
+ "all_content_already_unlocked": "Tất cả nội dung đã được mở khóa",
+ "content_grouping_enabled": "Đã bật nhóm nội dung",
+ "content_grouping_disabled": "Đã tắt nhóm nội dung"
}
},
"people": {
@@ -1320,6 +1323,12 @@
"add_content_create_lesson": "Tạo bài học",
"add_content_section_title_placeholder": "ví dụ: Mô-đun 1: Giới thiệu",
"add_content_lesson_title_placeholder": "ví dụ: Bắt đầu với các biến",
+ "content_menu": {
+ "label": "Tùy chọn nội dung",
+ "unlock_all": "Mở khóa tất cả nội dung",
+ "enable_grouping": "Bật nhóm nội dung",
+ "disable_grouping": "Tắt nhóm nội dung"
+ },
"join_lesson": "Tham gia bài học",
"no_link": "Không có liên kết",
"no_tutor": "Không có gia sư nào được thêm vào",
diff --git a/apps/dashboard/src/routes/(app)/courses/[id]/lessons/+page.svelte b/apps/dashboard/src/routes/(app)/courses/[id]/lessons/+page.svelte
index c7693d27..1e3089d0 100644
--- a/apps/dashboard/src/routes/(app)/courses/[id]/lessons/+page.svelte
+++ b/apps/dashboard/src/routes/(app)/courses/[id]/lessons/+page.svelte
@@ -1,5 +1,6 @@