PRD: Multi-Select Checkbox Component (Angular + Angular Material) ================================================================= 1. Overview ----------- We need a reusable Angular Material component that enables users to select multiple items using checkboxes within a dropdown or similar interface. This component will be used to filter or select items (e.g., Line Items, Bid Zones, Suppliers) before performing an action like downloading a catalog. 2. Goals & Objectives --------------------- 1. **Usability** * Provide a clear, intuitive UI using Angular Material patterns (e.g., `mat-select`, `mat-checkbox`). 2. **Reusability** * Create a standalone component that can be plugged into different parts of the application with minimal configuration. 3. **Performance** * Efficiently handle large lists of selectable items without significant UI lag. 4. **Consistency** * Use Angular Material design principles to maintain a uniform look and feel across the app. 3. User Stories --------------- 1. **As a user**, I want to see a dropdown that lists multiple items, each with a checkbox, so I can select or deselect them. 2. **As a user**, I want a “Select All” option that quickly selects every item in the list. 3. **As a user**, I want a “Clear All” option that quickly deselects all items. 4. **As a user**, I want to see a count of how many items I’ve selected. 5. **As a user**, I want to confirm my selection and use it to filter or download specific data. 4. Functional Requirements -------------------------- 1. **Dropdown (mat-select) with Checkboxes** * The component should display a list of items in a dropdown (or a popover) with each item having a checkbox. * Use `[multiple]="true"` on `mat-select` to allow multi-selection. 2. **Select All / Deselect All** * A special “Select All” option (checkbox or action) at the top of the list. * Toggling “Select All” should select/deselect every item in the list. 3. **Display Selected Count** * The trigger for the dropdown (the `mat-select-trigger`) should show the count of selected items (e.g., “3 selected”). 4. **Accessibility** * Ensure keyboard navigation and screen-reader compatibility (ARIA labels, focus states, etc.). 5. **Integration** * The component should emit an event (or use a reactive form control) that reflects the currently selected values so that parent components can react (e.g., enabling a “Download” button). 5. Non-Functional Requirements ------------------------------ 1. **Performance** * Must handle up to hundreds (or potentially thousands) of items without freezing the UI. * Consider virtual scrolling or lazy loading if needed. 2. **Maintainability** * Well-structured code with clear separation of component logic, styling, and tests. 3. **Scalability** * Ability to extend to grouped or hierarchical options in the future. 6. UI/UX Design --------------- A typical Angular Material form field containing a multi-select dropdown: ``` +------------------------------------------------------+ | Apply Filters To Download Catalog | +------------------------------------------------------+ | [Select Line Items v] | | - [ ] Select All | | - [ ] 29TDHEX_ARIBA1_LONG NAME | | - [ ] BAA OFF SAA OFF | | - [ ] SMARTLINK LLC | | - [ ] C0107 - Coaxial Cable System... | | ... | +------------------------------------------------------+ | [Select Bid Zones v] | | - [ ] New York | | - [ ] Chicago | | - [ ] New Jersey | | ... | +------------------------------------------------------+ | [Select Suppliers v] | | - [ ] Nokia | | ... | +------------------------------------------------------+ | [ Cancel ] [ Download ] | +------------------------------------------------------+ ``` * Each filter category uses the same multi-select component, just bound to different data arrays. * The user can open each dropdown to select items. * “Select All” and “Clear All” appear within the dropdown list or as part of the list. 7. Implementation Steps ----------------------- ### Step 1: Create the Multi-Select Component **File**: `multi-select-dropdown.component.ts` **Selector**: `<app-multi-select-dropdown>` 1. **Inputs** ```ts @Input() placeholder: string = 'Select options'; @Input() options: { label: string; value: any }[] = []; @Input() selectedValues: any[] = []; // optional, for default selection ``` * `options` is the list of selectable items. * `selectedValues` is the list of pre-selected items (if any). 2. **Outputs** ```ts @Output() selectionChange = new EventEmitter<any[]>(); ``` 3. **Template (HTML)** ```html <mat-form-field appearance="fill"> <mat-label>{{ placeholder }}</mat-label> <mat-select [value]="selectedValues" [multiple]="true" (selectionChange)="onSelectionChange($event)"> <!-- Select All Option --> <mat-option #selectAllOption (click)="toggleSelectAll($event)" [disabled]="options.length === 0"> <mat-checkbox [checked]="isAllSelected()" (click)="$event.preventDefault()"> Select All </mat-checkbox> </mat-option> <mat-divider></mat-divider> <!-- Actual Options --> <mat-option *ngFor="let option of options" [value]="option.value"> <mat-checkbox [checked]="selectedValues.includes(option.value)" (click)="$event.preventDefault()"> {{ option.label }} </mat-checkbox> </mat-option> </mat-select> <!-- Optionally display selected count in mat-select-trigger --> <mat-select-trigger> {{ selectedValues.length }} selected </mat-select-trigger> </mat-form-field> ``` 4. **Component Logic (TypeScript)** ```ts import { Component, Input, Output, EventEmitter } from '@angular/core'; import { MatSelectChange } from '@angular/material/select'; @Component({ selector: 'app-multi-select-dropdown', templateUrl: './multi-select-dropdown.component.html', styleUrls: ['./multi-select-dropdown.component.scss'] }) export class MultiSelectDropdownComponent { @Input() placeholder = 'Select options'; @Input() options: { label: string; value: any }[] = []; @Input() selectedValues: any[] = []; @Output() selectionChange = new EventEmitter<any[]>(); onSelectionChange(event: MatSelectChange) { // event.value is an array of currently selected values this.selectedValues = event.value; this.selectionChange.emit(this.selectedValues); } isAllSelected(): boolean { return this.selectedValues.length === this.options.length && this.options.length > 0; } toggleSelectAll(event: Event) { event.stopPropagation(); if (this.isAllSelected()) { // Clear all this.selectedValues = []; } else { // Select all this.selectedValues = this.options.map(opt => opt.value); } this.selectionChange.emit(this.selectedValues); } } ``` 5. **Styles (SCSS)** * Keep it minimal, relying mostly on Angular Material’s styling. * Add spacing or custom styling as needed. ### Step 2: Integrate into the Filter Modal **File**: `apply-filters-modal.component.ts` 1. **Data Sources** ```ts lineItems = [ { label: 'C0107 - Coaxial Cable System...', value: 'c0107' }, { label: '29TDHEX_ARIBA1_LONG NAME', value: '29tdhex' }, // ... ]; bidZones = [ { label: 'New York', value: 'ny' }, { label: 'Chicago', value: 'chicago' }, // ... ]; suppliers = [ { label: 'Nokia', value: 'nokia' }, // ... ]; ``` 2. **Template** (`apply-filters-modal.component.html`) ```html <h2>Apply Filters To Download Catalog</h2> <app-multi-select-dropdown placeholder="Select Line Items" [options]="lineItems" (selectionChange)="onLineItemsChange($event)"> </app-multi-select-dropdown> <app-multi-select-dropdown placeholder="Select Bid Zones" [options]="bidZones" (selectionChange)="onBidZonesChange($event)"> </app-multi-select-dropdown> <app-multi-select-dropdown placeholder="Select Suppliers" [options]="suppliers" (selectionChange)="onSuppliersChange($event)"> </app-multi-select-dropdown> <div class="actions"> <button mat-button (click)="onCancel()">Cancel</button> <button mat-raised-button color="primary" (click)="onDownload()">Download</button> </div> ``` 3. **Logic** (`apply-filters-modal.component.ts`) ```ts import { Component } from '@angular/core'; @Component({ selector: 'app-apply-filters-modal', templateUrl: './apply-filters-modal.component.html', styleUrls: ['./apply-filters-modal.component.scss'] }) export class ApplyFiltersModalComponent { selectedLineItems: any[] = []; selectedBidZones: any[] = []; selectedSuppliers: any[] = []; onLineItemsChange(selected: any[]) { this.selectedLineItems = selected; } onBidZonesChange(selected: any[]) { this.selectedBidZones = selected; } onSuppliersChange(selected: any[]) { this.selectedSuppliers = selected; } onCancel() { // Close modal or reset } onDownload() { // Combine all selected arrays into filter criteria const filterPayload = { lineItems: this.selectedLineItems, bidZones: this.selectedBidZones, suppliers: this.selectedSuppliers }; // Trigger the actual download logic (service call, etc.) // e.g., this.catalogService.downloadFilteredCatalog(filterPayload); } } ``` ### Step 3: Testing 1. **Unit Tests (Jasmine/Karma)** * **Component Tests**: * Check that “Select All” adds all items to `selectedValues`. * Check that toggling an individual item updates `selectedValues` correctly. * Check that the component emits `selectionChange` with the correct array. 2. **Integration Tests** * In `apply-filters-modal.component.spec.ts`, verify that selecting/deselecting items updates the parent component’s state. * Verify that clicking “Download” calls the appropriate service with the expected payload. 3. **E2E Tests (Protractor/Cypress)** * Open the modal, select a few items, verify that “Download” uses the correct filters. * Test the “Select All” and “Clear All” flow. ### Step 4: Deployment * Once QA and UAT (User Acceptance Testing) are complete, integrate into the main application build. * Ensure the new component is documented in the project’s UI component library or style guide. * * * 8. Acceptance Criteria ---------------------- 1. **UI**: Displays a multi-select dropdown using Angular Material, each option has a checkbox. 2. **Functionality**: “Select All” and “Deselect All” work as expected. 3. **State Management**: The parent component receives the correct list of selected values. 4. **Accessibility**: Keyboard and screen-reader navigation are supported via Angular Material. 5. **Performance**: Large option sets do not significantly degrade the user experience (consider lazy loading if necessary). * * * 9. Timeline & Milestones ------------------------ 1. **Design/Prototyping**: 1 week 2. **Development**: 1 week 3. **Integration & Testing**: 1 week 4. **UAT & QA**: 3-5 days 5. **Deployment**: After final approvals * * * 10. Risks & Mitigation ---------------------- 1. **Large Data Sets** * Consider virtual scrolling or pagination if performance is impacted. 2. **Complex Filtering** * If future requirements include hierarchical filters or advanced search, plan for an expandable component design. 3. **Browser Compatibility** * Angular Material supports modern browsers; verify any required polyfills for older browsers if necessary. * * * ### Conclusion By implementing this **Multi-Select Checkbox** component with Angular Material, we ensure a consistent, accessible, and maintainable approach to multi-selection across the application. The outlined design and steps provide a clear roadmap for development, integration, and testing. Once approved, the team can proceed with the implementation and rollout. * * * **End of PRD**