= ({
}
}, [scrollIntoView]);
+ if (lifecycleFilter && lifecycleState !== lifecycleFilter) {
+ return null;
+ }
+
return (
= ({
/>
)}
subtitle={getSubtitle()}
- actions={showActions && (
-
-
-
- {isShowRerunLink && (
-
- {messages.btnReRunText.defaultMessage}
-
+ actions={(
+
+ {!isLibraries && lifecycleState && lifecycleState !== 'published' && (
+
+ )}
+ {showActions && (
+
+
+
+ {isShowRerunLink && (
+
+ {messages.btnReRunText.defaultMessage}
+
+ )}
+
+ {intl.formatMessage(messages.viewLiveBtnText)}
+
+
+
)}
-
- {intl.formatMessage(messages.viewLiveBtnText)}
-
-
-
+
)}
/>
{isMigrated && migratedToKey
diff --git a/src/studio-home/data/slice.ts b/src/studio-home/data/slice.ts
index eeb9297ff6..d6b229b763 100644
--- a/src/studio-home/data/slice.ts
+++ b/src/studio-home/data/slice.ts
@@ -12,6 +12,7 @@ export interface Params {
activeOnly?: boolean;
isFiltered?: boolean;
cleanFilters?: boolean;
+ lifecycleFilter?: string;
}
export const studioHomeCoursesRequestParamsDefault: Params = {
@@ -22,6 +23,7 @@ export const studioHomeCoursesRequestParamsDefault: Params = {
activeOnly: undefined,
isFiltered: false,
cleanFilters: false,
+ lifecycleFilter: undefined,
};
const slice = createSlice({
diff --git a/src/studio-home/tabs-section/courses-tab/courses-filters/courses-lifecycle-filter-menu/index.jsx b/src/studio-home/tabs-section/courses-tab/courses-filters/courses-lifecycle-filter-menu/index.jsx
new file mode 100644
index 0000000000..bf2f051979
--- /dev/null
+++ b/src/studio-home/tabs-section/courses-tab/courses-filters/courses-lifecycle-filter-menu/index.jsx
@@ -0,0 +1,41 @@
+import PropTypes from 'prop-types';
+
+import CoursesFilterMenu from '../courses-filter-menu';
+
+const lifecycleStates = [
+ {
+ id: 'all-lifecycle',
+ name: 'All Review States',
+ value: 'allLifecycleStates',
+ },
+ {
+ id: 'draft',
+ name: 'Draft',
+ value: 'draft',
+ },
+ {
+ id: 'for-review',
+ name: 'For Review',
+ value: 'for_review',
+ },
+ {
+ id: 'approved',
+ name: 'Approved',
+ value: 'approved',
+ },
+];
+
+const CoursesLifecycleFilterMenu = ({ onItemMenuSelected }) => (
+
+);
+
+CoursesLifecycleFilterMenu.propTypes = {
+ onItemMenuSelected: PropTypes.func.isRequired,
+};
+
+export default CoursesLifecycleFilterMenu;
diff --git a/src/studio-home/tabs-section/courses-tab/courses-filters/index.jsx b/src/studio-home/tabs-section/courses-tab/courses-filters/index.jsx
index d3d6873ea0..814fa87418 100644
--- a/src/studio-home/tabs-section/courses-tab/courses-filters/index.jsx
+++ b/src/studio-home/tabs-section/courses-tab/courses-filters/index.jsx
@@ -11,6 +11,7 @@ import { fetchStudioHomeData } from '../../../data/thunks';
import { LoadingSpinner } from '../../../../generic/Loading';
import CoursesTypesFilterMenu from './courses-types-filter-menu';
import CoursesOrderFilterMenu from './courses-order-filter-menu';
+import CoursesLifecycleFilterMenu from './courses-lifecycle-filter-menu';
import './index.scss';
import messages from './messages';
@@ -73,6 +74,11 @@ const CoursesFilters = ({
dispatch(fetchStudioHomeData(locationValue, false, { page: 1, ...customParams }, true));
};
+ const handleLifecycleFilterSelected = (filterValue) => {
+ const lifecycleFilter = filterValue === 'allLifecycleStates' ? undefined : filterValue;
+ dispatch(updateStudioHomeCoursesCustomParams({ lifecycleFilter, isFiltered: true }));
+ };
+
const handleSearchCourses = (searchValueDebounced) => {
const valueFormatted = searchValueDebounced.trim();
const filterParams = {
@@ -122,6 +128,7 @@ const CoursesFilters = ({
+
);
};
diff --git a/src/studio-home/tabs-section/courses-tab/index.tsx b/src/studio-home/tabs-section/courses-tab/index.tsx
index 214b4e6115..2a1a9dad14 100644
--- a/src/studio-home/tabs-section/courses-tab/index.tsx
+++ b/src/studio-home/tabs-section/courses-tab/index.tsx
@@ -12,6 +12,8 @@ import {
import { Error } from '@openedx/paragon/icons';
import { COURSE_CREATOR_STATES } from '@src/constants';
+import { useBulkCourseAggregateStates } from '@src/course-lifecycle/data/apiHooks';
+import type { LifecycleState } from '@src/course-lifecycle/data/types';
import { getStudioHomeData, getStudioHomeCoursesParams } from '@src/studio-home/data/selectors';
import { resetStudioHomeCoursesCustomParams, updateStudioHomeCoursesCustomParams } from '@src/studio-home/data/slice';
import { fetchStudioHomeData } from '@src/studio-home/data/thunks';
@@ -63,7 +65,10 @@ const CoursesTab: React.FC = ({
optimizationEnabled,
} = useSelector(getStudioHomeData);
const studioHomeCoursesParams = useSelector(getStudioHomeCoursesParams);
- const { currentPage, isFiltered } = studioHomeCoursesParams;
+ const { currentPage, isFiltered, lifecycleFilter } = studioHomeCoursesParams;
+
+ const courseKeys = coursesDataItems?.map((c) => c.courseKey) ?? [];
+ const { data: bulkLifecycleStates = {}, isLoading: isLifecycleLoading } = useBulkCourseAggregateStates(courseKeys);
const hasAbilityToCreateCourse = courseCreatorStatus === COURSE_CREATOR_STATES.granted;
const showCollapsible = [
COURSE_CREATOR_STATES.denied,
@@ -98,6 +103,10 @@ const CoursesTab: React.FC = ({
const isNotFilteringCourses = !isFiltered && !isLoading;
const hasCourses = coursesDataItems?.length > 0;
+ const visibleCoursesCount = lifecycleFilter
+ ? (coursesDataItems?.filter(({ courseKey }) => bulkLifecycleStates[courseKey] === lifecycleFilter).length ?? 0)
+ : (coursesDataItems?.length ?? 0);
+ const hasVisibleCourses = visibleCoursesCount > 0;
if (isLoading && !isFiltered) {
return (
@@ -153,6 +162,8 @@ const CoursesTab: React.FC = ({
number={number}
run={run}
url={url}
+ lifecycleState={bulkLifecycleStates[courseKey] as LifecycleState}
+ lifecycleFilter={lifecycleFilter}
/>
),
)}
@@ -176,7 +187,7 @@ const CoursesTab: React.FC = ({
)
)}
- {isFiltered && !hasCourses && !isLoading && (
+ {isFiltered && !hasVisibleCourses && !isLoading && !isLifecycleLoading && (
{intl.formatMessage(messages.coursesTabCourseNotFoundAlertTitle)}