Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Conversation

@abdimo101
Copy link
Member

@abdimo101 abdimo101 commented Nov 12, 2025

Description

This PR relocates the search bar into the dynamic-mat-table header with improved styling.

Dataset page:
image

Proposal page:
image

Search bar when writing:
image

Motivation

Fixes:

Please provide a list of the fixes implemented in this PR

  • Items added

Changes:

Please provide a list of the changes implemented by this PR

  • changes made

Tests included

  • Included for each change/fix?
  • Passing? (Merge will not be approved unless this is checked)

Documentation

  • swagger documentation updated [required]
  • official documentation updated [nice-to-have]

official documentation info

If you have updated the official documentation, please provide PR # and URL of the pages where the updates are included

Backend version

  • Does it require a specific version of the backend
  • which version of the backend is required:

Summary by Sourcery

Relocate and redesign the global search bar by embedding it within the dynamic material table header on both dataset and proposal pages, revamp layout and styling of dashboard and table header controls, and wire up new search event handling in table components.

New Features:

  • Relocate global text search into the dynamic material table header with dedicated input, clear, and apply controls
  • Expose globalTextSearch inputs and outputs on dataset and proposal table components to handle search state and actions

Enhancements:

  • Refactor dataset dashboard layout by introducing .dashboard-main and .dashboard-header wrappers and reorganizing action and table columns
  • Redesign search input styling with rounded borders, focused state shadows, and inline paginator alignment in table headers
  • Update default placeholder text for proposal table global search

Chores:

  • Adjust top offset of proposal sidebar filter panel and remove unused margin in dataset filter

@abdimo101 abdimo101 requested a review from a team as a code owner November 12, 2025 14:08
Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey there - I've reviewed your changes and they look great!

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location> `src/app/shared/modules/dynamic-material-table/table/dynamic-mat-table.component.scss:362-360` </location>
<code_context>
   background-color: #ffffff;
 }
+
+.table-header-controls {
+  display: flex;
+  align-items: center;
+  gap: 1rem;
+  margin: 1rem 0;
+
+  &.with-side-filter {
+    margin-left: 3rem;
+  }
+
+  .global-search-wrapper {
</code_context>

<issue_to_address>
**suggestion:** Use of ::ng-deep for styling may lead to maintenance issues.

Since ::ng-deep is deprecated and may be removed in future Angular releases, please explore alternatives like custom themes or encapsulation to achieve the desired styling.

Suggested implementation:

```
    .custom-text-field-wrapper .mat-mdc-text-field-wrapper {
      background-color: white;

```

To complete this change, you must also:
1. Add the `custom-text-field-wrapper` class to the `mat-form-field` element in your component's template, e.g.:
   ```html
   <mat-form-field class="custom-text-field-wrapper">
   ```
2. If you are using Angular's default view encapsulation, this selector will work. If you have issues with specificity, consider using `ViewEncapsulation.None` in your component decorator, but this is usually not necessary if you use a custom class.
</issue_to_address>

### Comment 2
<location> `src/app/proposals/proposal-table/proposal-table.component.ts:311` </location>
<code_context>
+    );
+  }
+
+  onTextSearchChange(term: string) {
+    this.globalTextSearch = term;
+    this.store.dispatch(setSearchTermsAction({ terms: term }));
</code_context>

<issue_to_address>
**issue (complexity):** Consider moving search query parsing and router logic into a dedicated service and using a reactive FormControl for input handling.

Consider extracting the “searchQuery” parsing/serialization + router calls into a small service and driving your input via a reactive FormControl. That will remove the three new methods, the manual JSON.parse/stringify and the tight coupling to ActivatedRoute/Router in your component.

Example service (search-query.service.ts):
```ts
import { Injectable } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { map } from 'rxjs/operators';
import { Observable } from 'rxjs';

interface SearchQuery { text?: string; /*…other filters…*/ }

@Injectable({ providedIn: 'root' })
export class SearchQueryService {
  constructor(private route: ActivatedRoute, private router: Router) {}

  // Observable of current text search
  get text$(): Observable<string | undefined> {
    return this.route.queryParamMap.pipe(
      map(mp => {
        const raw = mp.get('searchQuery');
        return raw ? (JSON.parse(raw) as SearchQuery).text : undefined;
      })
    );
  }

  // Update text search and reset pageIndex
  setText(text?: string) {
    const raw = this.route.snapshot.queryParamMap.get('searchQuery');
    const q = (raw ? JSON.parse(raw) : {}) as SearchQuery;
    if (text) { q.text = text; } else { delete q.text; }
    return this.router.navigate([], {
      queryParams: { searchQuery: JSON.stringify(q), pageIndex: 0 },
      queryParamsHandling: 'merge'
    });
  }
}
```

Then in your component:
```ts
import { FormControl } from '@angular/forms';
import { debounceTime } from 'rxjs/operators';

export class ProposalTableComponent implements OnInit, OnDestroy {
  textControl = new FormControl('');
  private subs: Subscription[] = [];

  constructor(private searchQs: SearchQueryService, /**/) {}

  ngOnInit() {
    this.subs.push(
      // seed input from URL
      this.searchQs.text$.subscribe(t => this.textControl.setValue(t || '', { emitEvent: false })),

      // update URL/store on user input
      this.textControl.valueChanges
        .pipe(debounceTime(300))
        .subscribe(text => {
          this.searchQs.setText(text || undefined);
          this.store.dispatch(addProposalFilterAction({ key: 'text', value: text, filterType: 'text' }));
        })
    );
  }

  ngOnDestroy() { this.subs.forEach(s => s.unsubscribe()); }
}
```

This removes onTextSearchChange, getTextSearchParam and onTextSearchAction, centralizes parsing logic, and makes your component far simpler.
</issue_to_address>

### Comment 3
<location> `src/app/datasets/dataset-table/dataset-table.component.ts:566` </location>
<code_context>
       }),
     );
+
+    this.subscriptions.push(
+      this.route.queryParams.subscribe((queryParams) => {
+        const searchQuery = JSON.parse(queryParams.searchQuery || "{}");
</code_context>

<issue_to_address>
**issue (complexity):** Consider moving query parameter subscriptions and related action dispatches from the component into NgRx Effects to streamline the component logic.

```markdown
Rather than having the component manually subscribe to `route.queryParams` and dispatch multiple actions in two separate handlers, you can push that logic into NgRx Effects.  Your component then only needs a single “set‐text” dispatch and no manual subscriptions.  

**1) Create two Effects in `dataset.effects.ts`:**  
```ts
// dataset.effects.ts
import { Injectable } from '@angular/core';
import { RouterNavigationAction, ROUTER_NAVIGATION } from '@ngrx/router-store';
import { Actions, createEffect, ofType } from '@ngrx/effects';
import {
  setTextFilterAction,
  fetchDatasetsAction,
  fetchFacetCountsAction
} from 'state-management/actions/datasets.actions';
import { map, switchMap } from 'rxjs/operators';

@Injectable()
export class DatasetEffects {
  // Listen for router‐store navigation and pull out ?searchQuery
  initSearchFromUrl$ = createEffect(() =>
    this.actions$.pipe(
      ofType<RouterNavigationAction>(ROUTER_NAVIGATION),
      map(action => {
        const qp = action.payload.routerState.root.queryParams?.searchQuery;
        const { text = '' } = qp ? JSON.parse(qp) : {};
        return setTextFilterAction({ text });
      })
    )
  );

  // Whenever setTextFilterAction fires, fetch data + facets
  fetchOnFilter$ = createEffect(() =>
    this.actions$.pipe(
      ofType(setTextFilterAction),
      switchMap(() => [
        fetchDatasetsAction(),
        fetchFacetCountsAction()
      ])
    )
  );

  constructor(private actions$: Actions) {}
}
```

**2) Simplify your component:**
- Remove the `route.queryParams` subscription and `subscriptions` array
- Remove `onTextSearchAction()`
- Let `onTextSearchChange()` only dispatch the single action  
```ts
// your-table.component.ts (excerpt)
export class YourTableComponent {
  globalTextSearch = '';

  constructor(private store: Store) {}

  // now just emit one action
  onTextSearchChange(term: string) {
    this.globalTextSearch = term;
    this.store.dispatch(setTextFilterAction({ text: term }));
  }

  // drop onTextSearchAction() entirely
}
```

With this:  
- The Effect `initSearchFromUrl$` handles URL → store  
- `fetchOnFilter$` handles store → side-effects (fetch + facet)  
- The component is flatter and only responsible for wiring UI → `setTextFilterAction`  
```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants