260 lines
6.6 KiB
TypeScript
260 lines
6.6 KiB
TypeScript
/**
|
|
* History utility functions.
|
|
*
|
|
* Pure functions for parsing and formatting wallet history data.
|
|
* These functions have no React dependencies and can be used
|
|
* in both TUI and CLI contexts.
|
|
*/
|
|
|
|
import type { HistoryItem, HistoryItemType } from '../services/history.js';
|
|
|
|
/**
|
|
* Color names for history item types.
|
|
* These are semantic color names that can be mapped to actual colors
|
|
* by the consuming application (TUI or CLI).
|
|
*/
|
|
export type HistoryColorName = 'info' | 'warning' | 'success' | 'error' | 'muted' | 'text';
|
|
|
|
/**
|
|
* Formatted history list item data.
|
|
*/
|
|
export interface FormattedHistoryItem {
|
|
/** The display label for the history item */
|
|
label: string;
|
|
/** Optional secondary description */
|
|
description?: string;
|
|
/** The formatted date string */
|
|
dateStr?: string;
|
|
/** The semantic color name for this item type */
|
|
color: HistoryColorName;
|
|
/** The history item type */
|
|
type: HistoryItemType;
|
|
/** Whether the item data is valid */
|
|
isValid: boolean;
|
|
}
|
|
|
|
/**
|
|
* Get the semantic color name for a history item type.
|
|
*
|
|
* @param type - The history item type
|
|
* @param isSelected - Whether the item is currently selected
|
|
* @returns A semantic color name
|
|
*/
|
|
export function getHistoryItemColorName(type: HistoryItemType, isSelected: boolean = false): HistoryColorName {
|
|
if (isSelected) return 'info'; // Use focus color when selected
|
|
|
|
switch (type) {
|
|
case 'invitation_created':
|
|
return 'text';
|
|
case 'utxo_reserved':
|
|
return 'warning';
|
|
case 'utxo_received':
|
|
return 'success';
|
|
default:
|
|
return 'text';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Format a satoshi value for display.
|
|
*
|
|
* @param satoshis - The value in satoshis
|
|
* @returns Formatted string with BCH amount
|
|
*/
|
|
export function formatSatoshisValue(satoshis: bigint | number): string {
|
|
const value = typeof satoshis === 'bigint' ? satoshis : BigInt(satoshis);
|
|
const bch = Number(value) / 100_000_000;
|
|
return `${bch.toFixed(8)} BCH (${value.toLocaleString()} sats)`;
|
|
}
|
|
|
|
/**
|
|
* Format a timestamp for display.
|
|
*
|
|
* @param timestamp - Unix timestamp in milliseconds
|
|
* @returns Formatted date string or undefined
|
|
*/
|
|
export function formatHistoryDate(timestamp?: number): string | undefined {
|
|
if (!timestamp) return undefined;
|
|
return new Date(timestamp).toLocaleDateString();
|
|
}
|
|
|
|
/**
|
|
* Format a history item for display in a list.
|
|
*
|
|
* @param item - The history item to format
|
|
* @param isSelected - Whether the item is currently selected
|
|
* @returns Formatted item data for display
|
|
*/
|
|
export function formatHistoryListItem(
|
|
item: HistoryItem | null | undefined,
|
|
isSelected: boolean = false
|
|
): FormattedHistoryItem {
|
|
if (!item) {
|
|
return {
|
|
label: '',
|
|
description: undefined,
|
|
dateStr: undefined,
|
|
color: 'muted',
|
|
type: 'utxo_received',
|
|
isValid: false,
|
|
};
|
|
}
|
|
|
|
const dateStr = formatHistoryDate(item.timestamp);
|
|
const color = getHistoryItemColorName(item.type, isSelected);
|
|
|
|
switch (item.type) {
|
|
case 'invitation_created':
|
|
return {
|
|
label: `[Invitation] ${item.description}`,
|
|
description: undefined,
|
|
dateStr,
|
|
color,
|
|
type: item.type,
|
|
isValid: true,
|
|
};
|
|
|
|
case 'utxo_reserved': {
|
|
const satsStr = item.valueSatoshis !== undefined
|
|
? formatSatoshisValue(item.valueSatoshis)
|
|
: 'Unknown amount';
|
|
return {
|
|
label: `[Reserved] ${satsStr}`,
|
|
description: item.description,
|
|
dateStr,
|
|
color,
|
|
type: item.type,
|
|
isValid: true,
|
|
};
|
|
}
|
|
|
|
case 'utxo_received': {
|
|
const satsStr = item.valueSatoshis !== undefined
|
|
? formatSatoshisValue(item.valueSatoshis)
|
|
: 'Unknown amount';
|
|
const reservedTag = item.reserved ? ' [Reserved]' : '';
|
|
return {
|
|
label: satsStr,
|
|
description: `${item.description}${reservedTag}`,
|
|
dateStr,
|
|
color,
|
|
type: item.type,
|
|
isValid: true,
|
|
};
|
|
}
|
|
|
|
default:
|
|
return {
|
|
label: `${item.type}: ${item.description}`,
|
|
description: undefined,
|
|
dateStr,
|
|
color: 'text',
|
|
type: item.type,
|
|
isValid: true,
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get a type label for display.
|
|
*
|
|
* @param type - The history item type
|
|
* @returns Human-readable type label
|
|
*/
|
|
export function getHistoryTypeLabel(type: HistoryItemType): string {
|
|
switch (type) {
|
|
case 'invitation_created':
|
|
return 'Invitation';
|
|
case 'utxo_reserved':
|
|
return 'Reserved';
|
|
case 'utxo_received':
|
|
return 'Received';
|
|
default:
|
|
return type;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Calculate scrolling window indices for a list.
|
|
*
|
|
* @param selectedIndex - Currently selected index
|
|
* @param totalItems - Total number of items
|
|
* @param maxVisible - Maximum visible items
|
|
* @returns Start and end indices for the visible window
|
|
*/
|
|
export function calculateScrollWindow(
|
|
selectedIndex: number,
|
|
totalItems: number,
|
|
maxVisible: number
|
|
): { startIndex: number; endIndex: number } {
|
|
const halfWindow = Math.floor(maxVisible / 2);
|
|
let startIndex = Math.max(0, selectedIndex - halfWindow);
|
|
const endIndex = Math.min(totalItems, startIndex + maxVisible);
|
|
|
|
// Adjust start if we're near the end
|
|
if (endIndex - startIndex < maxVisible) {
|
|
startIndex = Math.max(0, endIndex - maxVisible);
|
|
}
|
|
|
|
return { startIndex, endIndex };
|
|
}
|
|
|
|
/**
|
|
* Check if a history item is a UTXO-related event.
|
|
*
|
|
* @param item - The history item to check
|
|
* @returns True if the item is UTXO-related
|
|
*/
|
|
export function isUtxoEvent(item: HistoryItem): boolean {
|
|
return item.type === 'utxo_received' || item.type === 'utxo_reserved';
|
|
}
|
|
|
|
/**
|
|
* Filter history items by type.
|
|
*
|
|
* @param items - Array of history items
|
|
* @param types - Types to include
|
|
* @returns Filtered array
|
|
*/
|
|
export function filterHistoryByType(
|
|
items: HistoryItem[],
|
|
types: HistoryItemType[]
|
|
): HistoryItem[] {
|
|
return items.filter(item => types.includes(item.type));
|
|
}
|
|
|
|
/**
|
|
* Get summary statistics for history items.
|
|
*
|
|
* @param items - Array of history items
|
|
* @returns Summary statistics
|
|
*/
|
|
export function getHistorySummary(items: HistoryItem[]): {
|
|
totalReceived: bigint;
|
|
totalReserved: bigint;
|
|
invitationCount: number;
|
|
utxoCount: number;
|
|
} {
|
|
let totalReceived = 0n;
|
|
let totalReserved = 0n;
|
|
let invitationCount = 0;
|
|
let utxoCount = 0;
|
|
|
|
for (const item of items) {
|
|
switch (item.type) {
|
|
case 'invitation_created':
|
|
invitationCount++;
|
|
break;
|
|
case 'utxo_reserved':
|
|
totalReserved += item.valueSatoshis ?? 0n;
|
|
break;
|
|
case 'utxo_received':
|
|
totalReceived += item.valueSatoshis ?? 0n;
|
|
utxoCount++;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return { totalReceived, totalReserved, invitationCount, utxoCount };
|
|
}
|