Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@
{{ `${$formatNumber(count)} ${count === 1 ? 'channel' : 'channels'}` }}
</h1>
<VLayout
rowwrap
wrap
class="mb-2"
>
<VFlex
xs12
sm4
xl3
sm6
md3
class="px-3"
>
<VSelect
Expand All @@ -21,14 +21,13 @@
item-value="key"
label="Channel Type"
box
clearable
:menu-props="{ offsetY: true }"
/>
</VFlex>
<VFlex
xs12
sm4
xl3
sm6
md3
clearable
class="px-3"
>
Expand All @@ -45,8 +44,8 @@
</VFlex>
<VFlex
xs12
sm4
xl3
sm6
md3
class="px-3"
>
<LanguageDropdown
Expand All @@ -56,8 +55,8 @@
</VFlex>
<VFlex
xs12
sm4
xl3
sm6
md3
class="px-3"
>
<VTextField
Expand Down Expand Up @@ -154,11 +153,29 @@
import Checkbox from 'shared/views/form/Checkbox';
import IconButton from 'shared/views/IconButton';
import LanguageDropdown from 'shared/views/LanguageDropdown';
import { CommunityLibraryStatus } from 'shared/constants';

const ChannelTypeFilter = {
ALL: 'all',

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

praise: Good use of a ChannelTypeFilter enum to replace the previous magic strings ('kolibriStudio', 'community', 'unlisted'). This makes the filter logic easier to follow and less error-prone.

KOLIBRI_LIBRARY: 'kolibriLibrary',
COMMUNITY_LIBRARY: 'communityLibrary',
UNLISTED: 'unlisted',
};

const channelTypeFilterMap = {
kolibriStudio: { label: 'Kolibri Studio Library', params: { public: true, deleted: false } },
community: { label: 'Community Library', params: { has_community_library_submission: true } },
unlisted: {
[ChannelTypeFilter.ALL]: {
label: 'All Channels',
params: {},
},
[ChannelTypeFilter.KOLIBRI_LIBRARY]: {
label: 'Kolibri Studio Library',
params: { public: true, deleted: false },
},
[ChannelTypeFilter.COMMUNITY_LIBRARY]: {
label: 'Community Library',
params: { has_community_library_submission: true },
},
[ChannelTypeFilter.UNLISTED]: {
label: 'Unlisted Channels',
params: { has_community_library_submission: false, public: false },
},
Expand All @@ -178,30 +195,41 @@
const store = proxy.$store;

const statusFilterMap = computed(() => {
if (channelTypeFilter.value === 'kolibriStudio') {
if (channelTypeFilter.value === ChannelTypeFilter.KOLIBRI_LIBRARY) {
return {
live: { label: 'Live', params: {} },
cheffed: { label: 'Sushi chef', params: { cheffed: true } },
};
} else if (channelTypeFilter.value === 'community') {
} else if (channelTypeFilter.value === ChannelTypeFilter.COMMUNITY_LIBRARY) {
return {
live: { label: 'Live', params: { community_library_live: true } },
needsReview: {
label: 'Needs review',
params: {
community_library_live: false,
latest_community_library_submission_status: ['PENDING', 'REJECTED'],
latest_community_library_submission_status: [
CommunityLibraryStatus.PENDING,
CommunityLibraryStatus.REJECTED,
],
},
},
published: { label: 'Published', params: {} },
cheffed: { label: 'Sushi chef', params: { cheffed: true } },
};
} else if (channelTypeFilter.value === 'unlisted') {
} else if (channelTypeFilter.value === ChannelTypeFilter.UNLISTED) {
return {
live: { label: 'Live', params: {} },
draft: { label: 'Draft', params: { published: false } },
published: { label: 'Published', params: { published: true } },
cheffed: { label: 'Sushi chef', params: { cheffed: true } },
live: { label: 'Live', params: { deleted: false } },
draft: { label: 'Draft', params: { published: false, deleted: false } },
published: { label: 'Published', params: { published: true, deleted: false } },
cheffed: { label: 'Sushi chef', params: { cheffed: true, deleted: false } },
deleted: { label: 'Deleted', params: { deleted: true } },
};
} else if (channelTypeFilter.value === ChannelTypeFilter.ALL) {
return {
live: { label: 'Live', params: { deleted: false } },
published: { label: 'Published', params: { published: true, deleted: false } },
draft: { label: 'Draft', params: { published: false, deleted: false } },
cheffed: { label: 'Sushi chef', params: { cheffed: true, deleted: false } },
deleted: { label: 'Deleted', params: { deleted: true } },
};
}
return {};
Expand Down Expand Up @@ -230,7 +258,9 @@
} = useFilter({
name: 'channelType',
filterMap: channelTypeFilterMap,
defaultValue: ChannelTypeFilter.ALL,
});

// Temporal wrapper, must be removed after migrating to KSelect
const channelTypeFilter = computed({
get: () => _channelTypeFilter.value.value || undefined,
Expand Down Expand Up @@ -281,10 +311,14 @@
fetchQueryParams: keywordSearchFetchQueryParams,
} = useKeywordSearch();

watch(channelTypeFilter, () => {
const options = channelStatusOptions.value;
channelStatusFilter.value = options.length ? options[0].value : null;
});
watch(
channelTypeFilter,
() => {
const options = channelStatusOptions.value;
channelStatusFilter.value = options.length ? options[0].value : null;
},
{ immediate: true },
);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

praise: Adding { immediate: true } here is important — without it, the status filter wouldn't be initialized on first render when channelTypeFilter defaults to ALL. Good catch.


const filterFetchQueryParams = computed(() => {
return {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,8 @@ describe('channelTable', () => {
});
describe('filters', () => {
it('changing channel type filter should set query params', async () => {
wrapper.vm.channelTypeFilter = 'community';
expect(router.currentRoute.query.channelType).toBe('community');
wrapper.vm.channelTypeFilter = 'communityLibrary';
expect(router.currentRoute.query.channelType).toBe('communityLibrary');
});
it('changing language filter should set query params', () => {
wrapper.vm.languageFilter = 'en';
Expand All @@ -72,7 +72,7 @@ describe('channelTable', () => {
expect(router.currentRoute.query.keywords).toBe('keyword test');
});
it('changing channel type filter should reset channel status filter', async () => {
wrapper.vm.channelTypeFilter = 'community';
wrapper.vm.channelTypeFilter = 'communityLibrary';
wrapper.vm.channelStatusFilter = 'published';
await wrapper.vm.$nextTick();
wrapper.vm.channelTypeFilter = 'unlisted';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,17 +34,18 @@ import { useQueryParams } from './useQueryParams';
* @param {Object} params The parameters for the filter.
* @param {string} params.name The name of the filter used in query params.
* @param {Object} params.filterMap A map of available filters.
* @param {string|null} [params.defaultValue] Optional default value if query param is not set.
* @returns {UseFilterReturn}
*/
export function useFilter({ name, filterMap }) {
export function useFilter({ name, filterMap, defaultValue = null }) {
const route = useRoute();
const { updateQueryParams } = useQueryParams();

const filter = computed({
get: () => {
const routeFilter = route.query[name];
const filterOption = options.value.find(option => option.value === routeFilter);
return filterOption || {};
return filterOption || options.value.find(option => option.value === defaultValue) || {};

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

praise: Clean fallback chain — try the route query param first, then the default value, then empty object. Minimal change that adds defaultValue support without disrupting existing callers.

},
set: value => {
updateQueryParams({
Expand Down