Skip to content
Merged
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
235 changes: 109 additions & 126 deletions src/app/features/my-projects/my-projects.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { debounceTime, distinctUntilChanged, filter, tap } from 'rxjs';
import {
ChangeDetectionStrategy,
Component,
computed,
DestroyRef,
effect,
inject,
Expand All @@ -25,7 +26,7 @@ import { ActivatedRoute, Router } from '@angular/router';
import { MyProjectsTableComponent, SelectComponent, SubHeaderComponent } from '@osf/shared/components';
import { DEFAULT_TABLE_PARAMS } from '@osf/shared/constants';
import { ResourceType, SortOrder } from '@osf/shared/enums';
import { IS_MEDIUM, parseQueryFilterParams } from '@osf/shared/helpers';
import { IS_MEDIUM } from '@osf/shared/helpers';
import { MyResourcesItem, MyResourcesSearchFilters, QueryParams, TableParameters } from '@osf/shared/models';
import {
BookmarksSelectors,
Expand All @@ -39,6 +40,8 @@ import {
} from '@osf/shared/stores';
import { CustomDialogService, ProjectRedirectDialogService } from '@shared/services';

import { MyProjectsQueryService } from './services/my-projects-query.service';
import { MyProjectsTableParamsService } from './services/my-projects-table-params.service';
import { CreateProjectDialogComponent } from './components';
import { MY_PROJECTS_TABS } from './constants';
import { MyProjectsTab } from './enums';
Expand Down Expand Up @@ -67,7 +70,10 @@ export class MyProjectsComponent implements OnInit {
readonly router = inject(Router);
readonly route = inject(ActivatedRoute);
readonly projectRedirectDialogService = inject(ProjectRedirectDialogService);
readonly queryService = inject(MyProjectsQueryService);
readonly tableParamsService = inject(MyProjectsTableParamsService);

readonly bookmarksPageSize = 100;
readonly isLoading = signal(false);
readonly isTablet = toSignal(inject(IS_MEDIUM));
readonly tabOptions = MY_PROJECTS_TABS;
Expand All @@ -92,8 +98,8 @@ export class MyProjectsComponent implements OnInit {
readonly totalRegistrationsCount = select(MyResourcesSelectors.getTotalRegistrations);
readonly totalPreprintsCount = select(MyResourcesSelectors.getTotalPreprints);
readonly totalBookmarksCount = select(MyResourcesSelectors.getTotalBookmarks);

readonly bookmarksCollectionId = select(BookmarksSelectors.getBookmarksCollectionId);
readonly isBookmarks = computed(() => this.selectedTab() === MyProjectsTab.Bookmarks);

readonly actions = createDispatchMap({
getBookmarksCollectionId: GetBookmarksCollectionId,
Expand All @@ -108,26 +114,72 @@ export class MyProjectsComponent implements OnInit {
this.setupQueryParamsEffect();
this.setupSearchSubscription();
this.setupTotalRecordsEffect();
this.setupBookmarksCollectionEffect();
this.setupCleanup();
}

ngOnInit(): void {
this.actions.getBookmarksCollectionId();
}

setupCleanup(): void {
onPageChange(event: TablePageEvent): void {
const current = this.queryService.getRawParams();
this.queryService.handlePageChange(event.first, event.rows, current, this.selectedTab());
}

onSort(event: SortEvent): void {
if (event.field) {
const current = this.queryService.getRawParams();
this.queryService.handleSort(event.field, event.order as SortOrder, current, this.selectedTab());
}
}

onTabChange(tabIndex: number): void {
this.actions.clearMyProjects();
this.selectedTab.set(tabIndex);
const current = this.queryService.getRawParams();
this.queryService.handleTabSwitch(current, this.selectedTab());
}

createProject(): void {
const dialogWidth = this.isTablet() ? '850px' : '95vw';

this.customDialogService
.open(CreateProjectDialogComponent, {
header: 'myProjects.header.createProject',
width: dialogWidth,
})
.onClose.pipe(
filter((result) => result?.project.id),
tap((result) => this.projectRedirectDialogService.showProjectRedirectDialog(result.project.id)),
takeUntilDestroyed(this.destroyRef)
)
.subscribe();
}

navigateToProject(project: MyResourcesItem): void {
this.activeProject.set(project);
this.router.navigate([project.id]);
}

navigateToRegistry(registry: MyResourcesItem): void {
this.activeProject.set(registry);
this.router.navigate([registry.id]);
}

private setupCleanup(): void {
this.destroyRef.onDestroy(() => {
this.actions.clearMyProjects();
});
}

setupSearchSubscription(): void {
private setupSearchSubscription(): void {
this.searchControl.valueChanges
.pipe(debounceTime(300), distinctUntilChanged(), takeUntilDestroyed(this.destroyRef))
.subscribe((searchValue) => this.handleSearch(searchValue ?? ''));
}

setupTotalRecordsEffect(): void {
private setupTotalRecordsEffect(): void {
effect(() => {
const totalRecords = this.getTotalRecordsForCurrentTab();
untracked(() => {
Expand All @@ -136,7 +188,22 @@ export class MyProjectsComponent implements OnInit {
});
}

getTotalRecordsForCurrentTab(): number {
private setupBookmarksCollectionEffect(): void {
effect(() => {
const collectionId = this.bookmarksCollectionId();
const params = this.queryParams();

if (collectionId && this.isBookmarks() && params) {
untracked(() => {
const queryParams = this.queryService.toQueryModel(params);
this.updateComponentState(queryParams);
this.fetchDataForCurrentTab(queryParams);
});
}
});
}

private getTotalRecordsForCurrentTab(): number {
switch (this.selectedTab()) {
case MyProjectsTab.Projects:
return this.totalProjectsCount();
Expand All @@ -151,49 +218,65 @@ export class MyProjectsComponent implements OnInit {
}
}

setupQueryParamsEffect(): void {
private setupQueryParamsEffect(): void {
effect(() => {
const params = this.queryParams();
if (!params) return;

const { page, size, search, sortColumn, sortOrder } = parseQueryFilterParams(params);
const raw = params;

if (!this.queryService.hasTabInUrl(raw)) {
untracked(() => {
const current = this.queryParams() || {};
this.queryService.updateParams({ page: 1 }, current, this.selectedTab());
});
return;
}

untracked(() => {
const tabFromUrl = this.queryService.getTabFromUrl(raw);
if (tabFromUrl !== null && this.selectedTab() !== tabFromUrl) {
this.selectedTab.set(tabFromUrl);
}

this.updateComponentState({ page, size, search, sortColumn, sortOrder });
this.fetchDataForCurrentTab({
page,
size,
search,
sortColumn,
sortOrder,
if (!this.isBookmarks()) {
const queryParams = this.queryService.toQueryModel(raw);
this.updateComponentState(queryParams);
this.fetchDataForCurrentTab(queryParams);
}
});
});
}

updateComponentState(params: QueryParams): void {
private updateComponentState(params: QueryParams): void {
untracked(() => {
const size = params.size || DEFAULT_TABLE_PARAMS.rows;

this.currentPage.set(params.page ?? 1);
this.currentPageSize.set(size);
this.searchControl.setValue(params.search || '');
this.searchControl.setValue(params.search || '', { emitEvent: false });
this.sortColumn.set(params.sortColumn);
this.sortOrder.set(params.sortOrder ?? SortOrder.Asc);

this.updateTableParams({
const totalRecords = this.getTotalRecordsForCurrentTab();
const tableParams = this.tableParamsService.buildTableParams(size, totalRecords, this.isBookmarks());
tableParams.firstRowIndex = ((params.page ?? 1) - 1) * size;

this.tableParams.set({
...tableParams,
rows: size,
firstRowIndex: ((params.page ?? 1) - 1) * size,
});
});
}

updateTableParams(updates: Partial<TableParameters>): void {
private updateTableParams(updates: Partial<TableParameters>): void {
this.tableParams.update((current) => ({
...current,
...updates,
}));
}

fetchDataForCurrentTab(params: QueryParams): void {
private fetchDataForCurrentTab(params: QueryParams): void {
this.isLoading.set(true);
const filters = this.createFilters(params);
const pageNumber = params.page ?? 1;
Expand All @@ -215,7 +298,7 @@ export class MyProjectsComponent implements OnInit {
action$ = this.actions.getMyBookmarks(
this.bookmarksCollectionId(),
pageNumber,
pageSize,
this.bookmarksPageSize,
filters,
ResourceType.Null
);
Expand All @@ -233,7 +316,7 @@ export class MyProjectsComponent implements OnInit {
});
}

createFilters(params: QueryParams): MyResourcesSearchFilters {
private createFilters(params: QueryParams): MyResourcesSearchFilters {
return {
searchValue: params.search || '',
searchFields:
Expand All @@ -243,108 +326,8 @@ export class MyProjectsComponent implements OnInit {
};
}

handleSearch(searchValue: string): void {
const currentParams = this.queryParams() || {};
this.updateQueryParams({
search: searchValue,
page: 1,
sortColumn: currentParams['sortColumn'],
sortOrder: currentParams['sortOrder'] === 'desc' ? SortOrder.Desc : SortOrder.Asc,
});
}

updateQueryParams(updates: Partial<QueryParams>): void {
const currentParams = this.queryParams() || {};
const queryParams: Record<string, string> = {};

if ('page' in updates || currentParams['page']) {
queryParams['page'] = updates.page?.toString() ?? currentParams['page'];
}
if ('size' in updates || currentParams['size']) {
queryParams['size'] = updates.size?.toString() ?? currentParams['size'];
}

if ('search' in updates || currentParams['search']) {
const search = updates.search ?? currentParams['search'];
if (search) {
queryParams['search'] = search;
}
}

if ('sortColumn' in updates) {
if (updates.sortColumn) {
queryParams['sortColumn'] = updates.sortColumn;
queryParams['sortOrder'] = updates.sortOrder === SortOrder.Desc ? 'desc' : 'asc';
}
} else if (currentParams['sortColumn']) {
queryParams['sortColumn'] = currentParams['sortColumn'];
queryParams['sortOrder'] = currentParams['sortOrder'];
}

this.router.navigate([], {
relativeTo: this.route,
queryParams,
});
}

onPageChange(event: TablePageEvent): void {
const page = Math.floor(event.first / event.rows) + 1;
const currentParams = this.queryParams() || {};

this.updateQueryParams({
page,
size: event.rows,
sortColumn: currentParams['sortColumn'],
sortOrder: currentParams['sortOrder'] === 'desc' ? SortOrder.Desc : SortOrder.Asc,
});
}

onSort(event: SortEvent): void {
if (event.field) {
this.updateQueryParams({
sortColumn: event.field,
sortOrder: event.order as SortOrder,
});
}
}

onTabChange(tabIndex: number): void {
this.actions.clearMyProjects();
this.selectedTab.set(tabIndex);
const currentParams = this.queryParams() || {};

this.updateQueryParams({
page: 1,
size: currentParams['size'],
search: '',
sortColumn: undefined,
sortOrder: undefined,
});
}

createProject(): void {
const dialogWidth = this.isTablet() ? '850px' : '95vw';

this.customDialogService
.open(CreateProjectDialogComponent, {
header: 'myProjects.header.createProject',
width: dialogWidth,
})
.onClose.pipe(
filter((result) => result?.project.id),
tap((result) => this.projectRedirectDialogService.showProjectRedirectDialog(result.project.id)),
takeUntilDestroyed(this.destroyRef)
)
.subscribe();
}

navigateToProject(project: MyResourcesItem): void {
this.activeProject.set(project);
this.router.navigate([project.id]);
}

navigateToRegistry(registry: MyResourcesItem): void {
this.activeProject.set(registry);
this.router.navigate([registry.id]);
private handleSearch(searchValue: string): void {
const current = this.queryService.getRawParams();
this.queryService.handleSearch(searchValue, current, this.selectedTab());
}
}
Loading
Loading