feat(dashboard): add content page options menu (#757)

* feat(dashboard): add content page options menu

Add a three-dot menu on the course Content page with actions to unlock
all locked lessons/exercises and toggle content grouping. Reuse shared
locked-content collection logic from course settings.

Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>

* fix(dashboard): keep unlock-all enabled and add menu icons

Show a success snackbar when all content is already unlocked instead of
disabling the action. Add lock-open and table-of-contents icons to menu items.

Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>

* fix(dashboard): check courseApi.update return for grouping toggle

Use the update() return value instead of courseApi.success, which can be
false after a successful PUT if the follow-up refresh request fails.

Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>

---------

Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>
This commit is contained in:
Rotimi Best
2026-07-15 22:28:23 +01:00
committed by GitHub
co-authored by Rotimi Best
parent bfafc685e0
commit 656fa4fa7e
14 changed files with 231 additions and 26 deletions
@@ -0,0 +1,112 @@
<script lang="ts">
import EllipsisVerticalIcon from '@lucide/svelte/icons/ellipsis-vertical';
import LockOpenIcon from '@lucide/svelte/icons/lock-open';
import TableOfContentsIcon from '@lucide/svelte/icons/table-of-contents';
import * as DropdownMenu from '@cio/ui/base/dropdown-menu';
import { IconButton } from '@cio/ui/custom/icon-button';
import { RoleBasedSecurity } from '$features/ui';
import { t } from '$lib/utils/functions/translations';
import { contentApi, courseApi } from '$features/course/api';
import { collectLockedContentItems } from '$features/course/utils/content-lock-utils';
import { isObject } from '$lib/utils/functions/isObject';
import { snackbar } from '$features/ui/snackbar/store';
interface Props {
courseId: string;
disabled?: boolean;
}
let { courseId, disabled = false }: Props = $props();
let isUnlockingAll = $state(false);
let isTogglingGrouping = $state(false);
const contentGroupingEnabled = $derived(courseApi.course?.metadata?.isContentGroupingEnabled ?? true);
const lockedContentItems = $derived(collectLockedContentItems(courseApi.course));
const menuDisabled = $derived(disabled || isUnlockingAll || isTogglingGrouping);
async function handleUnlockAllContent() {
if (!courseId || isUnlockingAll) return;
if (lockedContentItems.length === 0) {
snackbar.success('snackbar.course_settings.success.all_content_already_unlocked');
return;
}
isUnlockingAll = true;
const itemsToUnlock = [...lockedContentItems];
const didUnlock = await contentApi.updateContent(
courseId,
itemsToUnlock.map((item) => ({ id: item.id, type: item.type, isUnlocked: true }))
);
if (didUnlock) {
for (const item of itemsToUnlock) {
courseApi.updateContentItem(item.id, item.type, { isUnlocked: true });
}
snackbar.success('snackbar.course_settings.success.unlocked_all_content');
}
isUnlockingAll = false;
}
async function handleToggleContentGrouping() {
const course = courseApi.course;
if (!course || isTogglingGrouping) return;
const nextEnabled = !contentGroupingEnabled;
const metadata = isObject(course.metadata) ? course.metadata : {};
isTogglingGrouping = true;
const result = await courseApi.update(
course.id,
{
metadata: {
...metadata,
isContentGroupingEnabled: nextEnabled
}
},
{ showSuccessToast: false }
);
if (result) {
const snackbarKey = nextEnabled
? 'snackbar.course_settings.success.content_grouping_enabled'
: 'snackbar.course_settings.success.content_grouping_disabled';
snackbar.success(snackbarKey);
}
isTogglingGrouping = false;
}
</script>
<RoleBasedSecurity allowedRoles={[1, 2]}>
<DropdownMenu.Root>
<DropdownMenu.Trigger>
<IconButton
disabled={menuDisabled}
tooltip={t.get('course.navItem.lessons.content_menu.label')}
tooltipSide="bottom"
aria-label={t.get('course.navItem.lessons.content_menu.label')}
>
<EllipsisVerticalIcon size={16} />
</IconButton>
</DropdownMenu.Trigger>
<DropdownMenu.Content align="end">
<DropdownMenu.Item onclick={handleUnlockAllContent} disabled={menuDisabled}>
<span class="flex items-center gap-2">
<LockOpenIcon size={14} />
{$t('course.navItem.lessons.content_menu.unlock_all')}
</span>
</DropdownMenu.Item>
<DropdownMenu.Item onclick={handleToggleContentGrouping} disabled={menuDisabled}>
<span class="flex items-center gap-2">
<TableOfContentsIcon size={14} />
{contentGroupingEnabled
? $t('course.navItem.lessons.content_menu.disable_grouping')
: $t('course.navItem.lessons.content_menu.enable_grouping')}
</span>
</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu.Root>
</RoleBasedSecurity>
@@ -25,7 +25,6 @@
import LockOpenIcon from '@lucide/svelte/icons/lock-open'; import LockOpenIcon from '@lucide/svelte/icons/lock-open';
import type { TCourseType } from '@cio/db/types'; import type { TCourseType } from '@cio/db/types';
import type { Course } from '../utils/types'; import type { Course } from '../utils/types';
import { ContentType } from '@cio/utils/constants/content';
import { t } from '$lib/utils/functions/translations'; import { t } from '$lib/utils/functions/translations';
import { isObject } from '$lib/utils/functions/isObject'; import { isObject } from '$lib/utils/functions/isObject';
import { snackbar } from '$features/ui/snackbar/store'; import { snackbar } from '$features/ui/snackbar/store';
@@ -33,6 +32,7 @@
import { DEFAULT_COMPLIANCE_SETTINGS } from '../utils/compliance-utils'; import { DEFAULT_COMPLIANCE_SETTINGS } from '../utils/compliance-utils';
import { DeleteModal } from '$features/ui'; import { DeleteModal } from '$features/ui';
import { contentApi, courseApi } from '$features/course/api'; import { contentApi, courseApi } from '$features/course/api';
import { collectLockedContentItems } from '$features/course/utils/content-lock-utils';
import { tagApi } from '$features/tag/api'; import { tagApi } from '$features/tag/api';
import { uploadImage } from '$lib/utils/services/upload'; import { uploadImage } from '$lib/utils/services/upload';
import { copyToClipboard } from '$lib/utils/functions/formatYoutubeVideo'; import { copyToClipboard } from '$lib/utils/functions/formatYoutubeVideo';
@@ -120,22 +120,6 @@
let isUnlockingAll = $state(false); 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 lockedContentItems = $derived(collectLockedContentItems(courseApi.course));
const isLiveClassCourse = $derived(courseApi.course?.type === 'LIVE_CLASS'); const isLiveClassCourse = $derived(courseApi.course?.type === 'LIVE_CLASS');
const showLockedContentNotice = $derived($settings.isPublished && lockedContentItems.length > 0); const showLockedContentNotice = $derived($settings.isPublished && lockedContentItems.length > 0);
@@ -1,5 +1,22 @@
import { ContentType } from '@cio/utils/constants/content'; import { ContentType } from '@cio/utils/constants/content';
import type { StudentContentLockReason } from '$features/ai-assistant/utils/content-ask-ai-bar'; 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 { export function getStudentContentLockTitleKey(reason: StudentContentLockReason): string {
if (reason === 'progression_locked') { if (reason === 'progression_locked') {
@@ -289,7 +289,10 @@
"successful_integration": "Integration lykkedes", "successful_integration": "Integration lykkedes",
"successful_deletion": "Integration slettet", "successful_deletion": "Integration slettet",
"update_successful": "Opdateringen lykkedes", "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": { "people": {
@@ -1320,6 +1323,12 @@
"add_content_create_lesson": "Opret lektion", "add_content_create_lesson": "Opret lektion",
"add_content_section_title_placeholder": "f.eks. Modul 1: Introduktion", "add_content_section_title_placeholder": "f.eks. Modul 1: Introduktion",
"add_content_lesson_title_placeholder": "f.eks. Kom godt i gang med variabler", "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", "join_lesson": "Deltag i lektionen",
"no_link": "Intet link", "no_link": "Intet link",
"no_tutor": "Ingen underviser tilføjet", "no_tutor": "Ingen underviser tilføjet",
@@ -289,7 +289,10 @@
"successful_integration": "Integration erfolgreich", "successful_integration": "Integration erfolgreich",
"successful_deletion": "Integration gelöscht", "successful_deletion": "Integration gelöscht",
"update_successful": "Update erfolgreich", "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": { "people": {
@@ -1320,6 +1323,12 @@
"add_content_create_lesson": "Lektion erstellen", "add_content_create_lesson": "Lektion erstellen",
"add_content_section_title_placeholder": "z. B. Modul 1: Einführung", "add_content_section_title_placeholder": "z. B. Modul 1: Einführung",
"add_content_lesson_title_placeholder": "z. B. Erste Schritte mit Variablen", "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", "join_lesson": "Nehmen Sie an der Lektion teil",
"no_link": "Kein Link", "no_link": "Kein Link",
"no_tutor": "Kein Tutor hinzugefügt", "no_tutor": "Kein Tutor hinzugefügt",
@@ -2236,6 +2236,12 @@
}, },
"add_content_section_title_placeholder": "e.g., Module 1: Introduction", "add_content_section_title_placeholder": "e.g., Module 1: Introduction",
"add_content_lesson_title_placeholder": "e.g., Getting Started with Variables", "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", "join_lesson": "Join lesson",
"no_link": "No link", "no_link": "No link",
"no_tutor": "No tutor added", "no_tutor": "No tutor added",
@@ -4173,6 +4179,9 @@
"course_settings": { "course_settings": {
"success": { "success": {
"unlocked_all_content": "All course items unlocked", "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", "download": "Download Complete",
"saved": "Saved successfully", "saved": "Saved successfully",
"successful_integration": "Integration successful", "successful_integration": "Integration successful",
@@ -289,7 +289,10 @@
"successful_integration": "Integración exitosa", "successful_integration": "Integración exitosa",
"successful_deletion": "Integración eliminada", "successful_deletion": "Integración eliminada",
"update_successful": "Actualización exitosa", "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": { "people": {
@@ -1320,6 +1323,12 @@
"add_content_create_lesson": "Crear lección", "add_content_create_lesson": "Crear lección",
"add_content_section_title_placeholder": "por ejemplo, Módulo 1: Introducció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", "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", "join_lesson": "Unirse a la lección",
"no_link": "Sin enlace", "no_link": "Sin enlace",
"no_tutor": "No se agregó ningún tutor", "no_tutor": "No se agregó ningún tutor",
@@ -289,7 +289,10 @@
"successful_integration": "Intégration réussie", "successful_integration": "Intégration réussie",
"successful_deletion": "Intégration supprimée", "successful_deletion": "Intégration supprimée",
"update_successful": "Mise à jour réussie", "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": { "people": {
@@ -1320,6 +1323,12 @@
"add_content_create_lesson": "Créer une leçon", "add_content_create_lesson": "Créer une leçon",
"add_content_section_title_placeholder": "par exemple, Module 1 : Introduction", "add_content_section_title_placeholder": "par exemple, Module 1 : Introduction",
"add_content_lesson_title_placeholder": "par exemple, Premiers pas avec les variables", "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", "join_lesson": "Rejoindre la leçon",
"no_link": "Pas de lien", "no_link": "Pas de lien",
"no_tutor": "Aucun tuteur ajouté", "no_tutor": "Aucun tuteur ajouté",
@@ -289,7 +289,10 @@
"successful_integration": "एकीकरण सफल", "successful_integration": "एकीकरण सफल",
"successful_deletion": "एकीकरण हटा दिया गया", "successful_deletion": "एकीकरण हटा दिया गया",
"update_successful": "अद्यतन सफल", "update_successful": "अद्यतन सफल",
"unlocked_all_content": "सभी पाठ्यक्रम आइटम अनलॉक हो गए" "unlocked_all_content": "सभी पाठ्यक्रम आइटम अनलॉक हो गए",
"all_content_already_unlocked": "सभी सामग्री पहले से अनलॉक है",
"content_grouping_enabled": "सामग्री समूहीकरण सक्षम",
"content_grouping_disabled": "सामग्री समूहीकरण अक्षम"
} }
}, },
"people": { "people": {
@@ -1320,6 +1323,12 @@
"add_content_create_lesson": "पाठ बनाएँ", "add_content_create_lesson": "पाठ बनाएँ",
"add_content_section_title_placeholder": "उदाहरण के लिए, मॉड्यूल 1: परिचय", "add_content_section_title_placeholder": "उदाहरण के लिए, मॉड्यूल 1: परिचय",
"add_content_lesson_title_placeholder": "उदाहरण के लिए, वेरिएबल्स के साथ शुरुआत करना", "add_content_lesson_title_placeholder": "उदाहरण के लिए, वेरिएबल्स के साथ शुरुआत करना",
"content_menu": {
"label": "सामग्री विकल्प",
"unlock_all": "सभी सामग्री अनलॉक करें",
"enable_grouping": "सामग्री समूहीकरण सक्षम करें",
"disable_grouping": "सामग्री समूहीकरण अक्षम करें"
},
"join_lesson": "पाठ से जुड़ें", "join_lesson": "पाठ से जुड़ें",
"no_link": "कोई लिंक नहीं", "no_link": "कोई लिंक नहीं",
"no_tutor": "कोई शिक्षक नहीं जोड़ा गया", "no_tutor": "कोई शिक्षक नहीं जोड़ा गया",
@@ -289,7 +289,10 @@
"successful_integration": "Integracja udana", "successful_integration": "Integracja udana",
"successful_deletion": "Integracja usunięta", "successful_deletion": "Integracja usunięta",
"update_successful": "Aktualizacja pomyślna", "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": { "people": {
@@ -1320,6 +1323,12 @@
"add_content_create_lesson": "Utwórz lekcję", "add_content_create_lesson": "Utwórz lekcję",
"add_content_section_title_placeholder": "np. Moduł 1: Wprowadzenie", "add_content_section_title_placeholder": "np. Moduł 1: Wprowadzenie",
"add_content_lesson_title_placeholder": "np. Pierwsze kroki ze zmiennymi", "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", "join_lesson": "Dołącz do lekcji",
"no_link": "Brak linku", "no_link": "Brak linku",
"no_tutor": "Nie dodano żadnego nauczyciela", "no_tutor": "Nie dodano żadnego nauczyciela",
@@ -289,7 +289,10 @@
"successful_integration": "Integração bem-sucedida", "successful_integration": "Integração bem-sucedida",
"successful_deletion": "Integração excluída", "successful_deletion": "Integração excluída",
"update_successful": "Atualização bem-sucedida", "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": { "people": {
@@ -1320,6 +1323,12 @@
"add_content_create_lesson": "Criar aula", "add_content_create_lesson": "Criar aula",
"add_content_section_title_placeholder": "por exemplo, Módulo 1: Introdução", "add_content_section_title_placeholder": "por exemplo, Módulo 1: Introdução",
"add_content_lesson_title_placeholder": "por exemplo, Introdução às variáveis", "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", "join_lesson": "Participar da aula",
"no_link": "Sem link", "no_link": "Sem link",
"no_tutor": "Nenhum tutor adicionado", "no_tutor": "Nenhum tutor adicionado",
@@ -289,7 +289,10 @@
"successful_integration": "Интеграция прошла успешно", "successful_integration": "Интеграция прошла успешно",
"successful_deletion": "Интеграция удалена", "successful_deletion": "Интеграция удалена",
"update_successful": "Обновление успешно выполнено", "update_successful": "Обновление успешно выполнено",
"unlocked_all_content": "Все предметы курса разблокированы" "unlocked_all_content": "Все предметы курса разблокированы",
"all_content_already_unlocked": "Весь контент уже разблокирован",
"content_grouping_enabled": "Группировка контента включена",
"content_grouping_disabled": "Группировка контента отключена"
} }
}, },
"people": { "people": {
@@ -1320,6 +1323,12 @@
"add_content_create_lesson": "Создать урок", "add_content_create_lesson": "Создать урок",
"add_content_section_title_placeholder": "например, Модуль 1: Введение", "add_content_section_title_placeholder": "например, Модуль 1: Введение",
"add_content_lesson_title_placeholder": "например, Начало работы с переменными", "add_content_lesson_title_placeholder": "например, Начало работы с переменными",
"content_menu": {
"label": "Параметры контента",
"unlock_all": "Разблокировать весь контент",
"enable_grouping": "Включить группировку контента",
"disable_grouping": "Отключить группировку контента"
},
"join_lesson": "Присоединиться к уроку", "join_lesson": "Присоединиться к уроку",
"no_link": "Нет ссылки", "no_link": "Нет ссылки",
"no_tutor": "Репетитор не добавлен", "no_tutor": "Репетитор не добавлен",
@@ -289,7 +289,10 @@
"successful_integration": "Tích hợp thành công", "successful_integration": "Tích hợp thành công",
"successful_deletion": "Đã xóa tích hợp", "successful_deletion": "Đã xóa tích hợp",
"update_successful": "Cập nhật thành công", "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": { "people": {
@@ -1320,6 +1323,12 @@
"add_content_create_lesson": "Tạo bài học", "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_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", "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", "join_lesson": "Tham gia bài học",
"no_link": "Không có liên kết", "no_link": "Không có liên kết",
"no_tutor": "Không có gia sư nào được thêm vào", "no_tutor": "Không có gia sư nào được thêm vào",
@@ -1,5 +1,6 @@
<script lang="ts"> <script lang="ts">
import { LessonsPage } from '$features/course/pages'; import { LessonsPage } from '$features/course/pages';
import ContentPageMenu from '$features/course/components/lesson/content-page-menu.svelte';
import { Button } from '@cio/ui/base/button'; import { Button } from '@cio/ui/base/button';
import { RefreshPageData, RoleBasedSecurity } from '$features/ui'; import { RefreshPageData, RoleBasedSecurity } from '$features/ui';
import * as Page from '@cio/ui/base/page'; import * as Page from '@cio/ui/base/page';
@@ -39,6 +40,7 @@
>{$t('course.navItem.lessons.add_content')}</Button >{$t('course.navItem.lessons.add_content')}</Button
> >
</RoleBasedSecurity> </RoleBasedSecurity>
<ContentPageMenu courseId={data.courseId} disabled={!!$contentEditingStore} />
<RefreshPageData onRefresh={() => courseApi.refreshCourse(data.courseId, $profile.id)} /> <RefreshPageData onRefresh={() => courseApi.refreshCourse(data.courseId, $profile.id)} />
</div> </div>
</Page.Action> </Page.Action>