// 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.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' },
];
`;