import { Component, computed, inject, signal } from '@angular/core'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { ActivatedRoute, Router } from '@angular/router'; import type { FilterStateModel, PaginationMetaModel } from '@africanies/africanies-models'; import { ActionMenuComponent, type AfricaniesMenuItem, CellDefDirective, ChipComponent, type ChipVariant, DEFAULT_PAGE_SIZE, emptyFilterState, FilterDrawerService, FilterQueryService, type TableColumn, TableComponent, toFilterParams, trackShipmentsFilterConfig, } from '@africanies/africanies-ui'; import { DemoSectionComponent } from '../../shared/demo-section.component'; import { PageHeaderComponent } from '../../shared/page-header.component'; import { USECASE_SHIPMENT_BACK } from '../../snippets'; import { USECASE_SHIPMENTS, type UsecaseShipment } from './shipment-data'; const FILTER_CONFIG = trackShipmentsFilterConfig; /** * Parent list — no Back. Filters and the pager sync to the URL. Row actions * open a child detail route and keep those queries so Back restores the list. */ @Component({ selector: 'app-shipment-list-page', standalone: true, imports: [ ActionMenuComponent, CellDefDirective, ChipComponent, TableComponent, PageHeaderComponent, DemoSectionComponent, ], template: `
{{ row.reference }} {{ row.status }}
`, }) export class ShipmentListPage { private readonly router = inject(Router); private readonly route = inject(ActivatedRoute); private readonly filterDrawer = inject(FilterDrawerService); private readonly filterQuery = inject(FilterQueryService); protected readonly routeCode = USECASE_SHIPMENT_BACK; protected readonly page = signal(1); protected readonly pageSize = signal(DEFAULT_PAGE_SIZE); protected readonly filterState = signal(emptyFilterState()); constructor() { this.syncFromUrl(); this.route.queryParamMap.pipe(takeUntilDestroyed()).subscribe(() => { this.syncFromUrl(); }); } protected readonly columns: TableColumn[] = [ { key: 'reference', header: 'Reference' }, { key: 'route', header: 'Route' }, { key: 'status', header: 'Status' }, { key: 'actions', header: '', width: '3.5rem' }, ]; protected readonly rowTrackBy = (row: UsecaseShipment) => row.reference; protected readonly filteredRows = computed(() => { const state = this.filterState(); const search = state.search?.trim().toLowerCase(); const shipmentStatus = state.values['shipment_status']; const paymentStatus = state.values['payment_status']; const tracking = state.values['tracking_number']?.trim().toLowerCase(); return USECASE_SHIPMENTS.filter((row) => { if ( search && !row.reference.toLowerCase().includes(search) && !row.trackingNumber.toLowerCase().includes(search) ) { return false; } if (shipmentStatus && row.shipmentStatus !== shipmentStatus) { return false; } if (paymentStatus && row.paymentStatus !== paymentStatus) { return false; } if ( tracking && !row.trackingNumber.toLowerCase().includes(tracking) ) { return false; } return true; }); }); protected readonly pageRows = computed(() => { const size = this.pageSize(); const start = (this.page() - 1) * size; return this.filteredRows().slice(start, start + size); }); protected readonly meta = computed((): PaginationMetaModel => { const per_page = this.pageSize(); const total_items = this.filteredRows().length; const total_pages = Math.max(1, Math.ceil(total_items / per_page)); const current_page = Math.min(this.page(), total_pages); return { current_page, per_page, total_items, total_pages, has_next_page: current_page < total_pages, has_previous_page: current_page > 1, }; }); protected readonly activeFilterCount = computed(() => { const params = toFilterParams(this.filterState(), FILTER_CONFIG); return Object.keys(params).filter( (key) => key !== 'page' && key !== 'size' && key !== 'order' && key !== 'per_page', ).length; }); protected statusVariant(status: UsecaseShipment['status']): ChipVariant { switch (status) { case 'Delivered': return 'success'; case 'Exception': return 'danger'; case 'Pending': return 'warning'; default: return 'neutral'; } } protected rowActions(row: UsecaseShipment): AfricaniesMenuItem[] { return [ { label: 'View details', icon: 'eye', onClick: () => { void this.router.navigate(['/usecases/shipment', row.reference], { queryParamsHandling: 'preserve', }); }, }, ]; } protected onPageChange(next: number): void { this.page.set(next); } protected onSizeChange(next: number): void { this.pageSize.set(next); this.page.set(1); } protected openFilters(): void { this.filterDrawer .open({ config: FILTER_CONFIG, state: this.filterState(), title: 'Filter shipments', }) .afterClosed() .subscribe((result) => { if (result?.applied) { this.filterState.set(result.state); this.page.set(1); } }); } /** * Seed pager + filters from the URL when those queries are present. */ private syncFromUrl(): void { const state = this.filterQuery.read(FILTER_CONFIG); this.filterState.set(state); this.page.set(state.page ?? 1); this.pageSize.set(state.size ?? DEFAULT_PAGE_SIZE); } }