// Table & pagination copy-paste examples. export /** * */ const TABLE_LIST = `// Server-driven list: keep the table mounted; loading / empty / error render in the body. // Toolbar: Refresh (left); Filters + Export (right). Pass [meta] for the built-in pager. // Hydrate page/size/filters from FilterQueryService when the URL has those queries. // Pager + Apply write the same keys back. Refetch on sortChange / pageChange / sizeChange. // Refresh / tab focus → [refreshing] (rows stay). Page/size → pager spinner. // First load and STN↔SFN (listFetchKind reason 'mode') → clear rows + body loader. // Empty and hard errors stay in-grid so Filters / Export / pager remain available. // Expandable rows: africaniesRowDetail="Label" let-row for label / component value pairs. // Column width: omit to size to content; set width (e.g. '3.5rem') to pin actions. // The actions column sticks to the right while the table scrolls horizontally. import { Component, computed, effect, inject, signal, untracked } from '@angular/core'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { ApiClient, downloadCsv, listFetchKind, ShippingModeService, type ListFetchReason } from '@africanies/africanies-core'; import type { AsyncQueryStateModel, PaginationMetaModel } from '@africanies/africanies-models'; import { ActionMenuComponent, CellDefDirective, ChipComponent, CopyButtonComponent, DEFAULT_PAGE_SIZE, FilterQueryService, RowDetailDefDirective, TableComponent, trackShipmentsFilterConfig, type AfricaniesMenuItem, type TableColumn, type TableSortChange, } from '@africanies/africanies-ui'; import { filter, firstValueFrom, fromEvent } from 'rxjs'; interface Shipment { reference: string; status: 'In transit' | 'Delivered' | 'Pending' | 'Exception'; mode: 'sfn' | 'stn'; destination: string; valueUsd: number; } const PAGE_SIZE = DEFAULT_PAGE_SIZE; @Component({ selector: 'app-shipment-list-page', standalone: true, imports: [ TableComponent, CellDefDirective, RowDetailDefDirective, ActionMenuComponent, ChipComponent, CopyButtonComponent, ], template: \`
{{ row.reference }}
{{ row.status }} {{ formatUsd(row.valueUsd) }} {{ row.destination }} {{ formatUsd(row.valueUsd) }} {{ row.status }}
\`, }) export class ShipmentListPageComponent { private readonly api = inject(ApiClient); private readonly shipping = inject(ShippingModeService); private readonly filterQuery = inject(FilterQueryService); private readonly urlState = this.filterQuery.read(trackShipmentsFilterConfig); protected readonly page = signal(this.urlState.page ?? 1); protected readonly size = signal(this.urlState.size ?? PAGE_SIZE); protected readonly sort = signal(null); protected readonly rows = signal([]); protected readonly meta = signal(null); protected readonly fetchError = signal(null); protected readonly isLoading = signal(false); protected readonly isRefreshing = signal(false); protected readonly isPageLoading = signal(false); protected readonly columns: TableColumn[] = [ { key: 'reference', header: 'Reference', sortable: true }, { key: 'status', header: 'Status', sortable: true }, { key: 'mode', header: 'Mode' }, { key: 'destination', header: 'Destination', sortable: true }, { key: 'valueUsd', header: 'Value', sortable: true }, { key: 'actions', header: '', width: '3.5rem' }, ]; protected readonly rowTrackBy = (row: Shipment) => row.reference; protected rowActions(row: Shipment): AfricaniesMenuItem[] { return [ { label: 'Open', icon: 'eye', onClick: () => this.open(row), }, { label: 'Edit', icon: 'edit', onClick: () => this.edit(row), }, { label: 'Copy reference', icon: 'copy', onClick: () => this.copyReference(row), }, { label: 'Delete', icon: 'trash', danger: true, dividerBefore: true, onClick: () => this.delete(row), }, ]; } protected readonly listState = computed((): AsyncQueryStateModel => ({ data: this.rows(), isLoading: this.isLoading(), isFetching: this.isRefreshing() || this.isPageLoading(), isError: this.fetchError() != null, error: this.fetchError(), })); constructor() { effect(() => { this.shipping.mode(); untracked(() => void this.refetch({ reason: this.rows().length > 0 ? 'mode' : 'initial', }), ); }); fromEvent(document, 'visibilitychange') .pipe( filter(() => document.visibilityState === 'visible'), takeUntilDestroyed(), ) .subscribe(() => void this.refetch({ reason: 'focus' })); } protected async refetch( opts: { reason: ListFetchReason } = { reason: 'initial' }, ): Promise { const kind = listFetchKind({ hasData: this.rows().length > 0, reason: opts.reason, }); this.isLoading.set(kind === 'loading'); this.isRefreshing.set(kind === 'refreshing'); this.isPageLoading.set(kind === 'pagination'); if (kind === 'loading') { this.rows.set([]); } this.fetchError.set(null); try { const currentSort = this.sort(); const res = await firstValueFrom( this.api.getResource('shipments', null, { page: this.page(), size: this.size(), // Map sort key/direction to your API query params when supported. orderBy: currentSort?.key, order: currentSort?.direction, }), ); this.rows.set(res.data ?? []); this.meta.set(res.pagination ?? null); } catch { this.fetchError.set('Could not load shipments.'); } finally { this.isLoading.set(false); this.isRefreshing.set(false); this.isPageLoading.set(false); } } protected onSort(change: TableSortChange): void { this.sort.set(change); this.page.set(1); void this.refetch({ reason: 'page' }); } protected onPageChange(next: number): void { this.page.set(next); void this.refetch({ reason: 'page' }); } protected onSizeChange(next: number): void { this.size.set(next); this.page.set(1); void this.refetch({ reason: 'page' }); } private open(row: Shipment): void { /* navigate to detail */ } private edit(row: Shipment): void { /* open edit flow */ } private copyReference(row: Shipment): void { /* clipboard.writeText(row.reference) */ } private delete(row: Shipment): void { /* confirm + API delete */ } protected exportCsv(): void { downloadCsv({ filename: 'shipments.csv', headers: ['Reference', 'Status', 'Destination', 'Value (USD)'], rows: this.rows().map((row) => [ row.reference, row.status, row.destination, row.valueUsd, ]), }); } protected formatUsd(value: number): string { return new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD', maximumFractionDigits: 0, }).format(value); } } // --- Playground-only demo (in-memory slice — NOT for production) --- // // const ALL_ROWS: Shipment[] = [/* static fixture */]; // // pageRows = computed(() => { // const start = (this.page() - 1) * PAGE_SIZE; // return sortedRows().slice(start, start + PAGE_SIZE); // }); // // meta = computed((): PaginationMetaModel => ({ /* derived from ALL_ROWS.length */ })); `; export /** * */ const TABLE_COMPACT = `// Plain text columns only — no africaniesCellDef needed; cells render String(row[key]). // Fine for small previews; skip AsyncState/pagination if the set is static. import { Component } from '@angular/core'; import { TableComponent, type TableColumn } from '@africanies/africanies-ui'; interface Shipment { reference: string; destination: string; status: string; } @Component({ selector: 'app-shipment-compact-preview', standalone: true, imports: [TableComponent], template: \` \`, }) export class ShipmentCompactPreviewComponent { protected readonly columns: TableColumn[] = [ { key: 'reference', header: 'Reference' }, { key: 'destination', header: 'Destination' }, { key: 'status', header: 'Status' }, ]; protected readonly rows: Shipment[] = [ { reference: 'SFN-1042', destination: 'Lagos', status: 'In transit' }, { reference: 'STN-2088', destination: 'Accra', status: 'Delivered' }, { reference: 'SFN-1099', destination: 'Nairobi', status: 'Pending' }, ]; } `; export /** * */ const TABLE_CONTENT_STACK = `// Stacked title / subtitle / extra line. Drop into a table cell (or a detail panel). // The table row grows to fit — no extra layout work. import { Component } from '@angular/core'; import { ContentStackComponent } from '@africanies/africanies-ui'; @Component({ selector: 'app-packaged-by', standalone: true, imports: [ContentStackComponent], template: \` \`, }) export class PackagedByComponent {} `; export /** * */ const TABLE_CUSTOMERS = `// Customer list: switch columns by account type (individual vs business). // User: date created, customer, attempts, identifier, status, performed by, action. // Business: date created, business, attempts, company type, incorp no, incorp date, website, status, action. protected readonly userColumns: TableColumn[] = [ { key: 'createdAt', header: 'Date created', sortable: true }, { key: 'customer', header: 'Customer' }, { key: 'attempts', header: 'Attempts', sortable: true }, { key: 'identifier', header: 'Identifier' }, { key: 'status', header: 'Status', sortable: true }, { key: 'performedBy', header: 'Performed by' }, { key: 'actions', header: 'Action', width: '3.5rem' }, ]; protected readonly businessColumns: TableColumn[] = [ { key: 'createdAt', header: 'Date created', sortable: true }, { key: 'business', header: 'Business' }, { key: 'attempts', header: 'Attempts', sortable: true }, { key: 'companyType', header: 'Company type' }, { key: 'incorpNo', header: 'Incorp no' }, { key: 'incorpDate', header: 'Incorp date' }, { key: 'website', header: 'Website' }, { key: 'status', header: 'Status', sortable: true }, { key: 'actions', header: 'Action', width: '3.5rem' }, ]; `;