Add transactions page with fuzzy search and success dialog for expenses
Features: - Created TransactionsPage with mobile-optimized layout - Card-based transaction items with status indicators - Fuzzy search by description, payee, reference, username, and tags - Day filter options (5, 30, 60, 90 days) - Pagination support - Responsive design for mobile and desktop - Added getUserTransactions API method to ExpensesAPI - Supports filtering by days, user ID, and account type - Returns paginated transaction data - Updated AddExpense component with success confirmation - Shows success message in same dialog after submission - Provides option to navigate to transactions page - Clean single-dialog approach - Added "My Transactions" link to navbar menu - Added Transaction and TransactionListResponse types - Added permission management types and API methods (grantPermission, listPermissions, revokePermission) - Installed alert-dialog component for UI consistency 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
78fba2a637
commit
be00c61c77
20 changed files with 914 additions and 74 deletions
|
|
@ -8,7 +8,10 @@ import type {
|
|||
ExpenseEntryRequest,
|
||||
ExpenseEntry,
|
||||
AccountNode,
|
||||
UserInfo
|
||||
UserInfo,
|
||||
AccountPermission,
|
||||
GrantPermissionRequest,
|
||||
TransactionListResponse
|
||||
} from '../types'
|
||||
import { appConfig } from '@/app.config'
|
||||
|
||||
|
|
@ -302,4 +305,136 @@ export class ExpensesAPI extends BaseService {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* List all account permissions (admin only)
|
||||
*
|
||||
* @param adminKey - Admin key for authentication
|
||||
*/
|
||||
async listPermissions(adminKey: string): Promise<AccountPermission[]> {
|
||||
try {
|
||||
const response = await fetch(`${this.baseUrl}/castle/api/v1/permissions`, {
|
||||
method: 'GET',
|
||||
headers: this.getHeaders(adminKey),
|
||||
signal: AbortSignal.timeout(this.config?.apiConfig?.timeout || 30000)
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to list permissions: ${response.statusText}`)
|
||||
}
|
||||
|
||||
const permissions = await response.json()
|
||||
return permissions as AccountPermission[]
|
||||
} catch (error) {
|
||||
console.error('[ExpensesAPI] Error listing permissions:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Grant account permission to a user (admin only)
|
||||
*
|
||||
* @param adminKey - Admin key for authentication
|
||||
* @param request - Permission grant request
|
||||
*/
|
||||
async grantPermission(
|
||||
adminKey: string,
|
||||
request: GrantPermissionRequest
|
||||
): Promise<AccountPermission> {
|
||||
try {
|
||||
const response = await fetch(`${this.baseUrl}/castle/api/v1/permissions`, {
|
||||
method: 'POST',
|
||||
headers: this.getHeaders(adminKey),
|
||||
body: JSON.stringify(request),
|
||||
signal: AbortSignal.timeout(this.config?.apiConfig?.timeout || 30000)
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json().catch(() => ({}))
|
||||
const errorMessage =
|
||||
errorData.detail || `Failed to grant permission: ${response.statusText}`
|
||||
throw new Error(errorMessage)
|
||||
}
|
||||
|
||||
const permission = await response.json()
|
||||
return permission as AccountPermission
|
||||
} catch (error) {
|
||||
console.error('[ExpensesAPI] Error granting permission:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Revoke account permission (admin only)
|
||||
*
|
||||
* @param adminKey - Admin key for authentication
|
||||
* @param permissionId - ID of the permission to revoke
|
||||
*/
|
||||
async revokePermission(adminKey: string, permissionId: string): Promise<void> {
|
||||
try {
|
||||
const response = await fetch(
|
||||
`${this.baseUrl}/castle/api/v1/permissions/${permissionId}`,
|
||||
{
|
||||
method: 'DELETE',
|
||||
headers: this.getHeaders(adminKey),
|
||||
signal: AbortSignal.timeout(this.config?.apiConfig?.timeout || 30000)
|
||||
}
|
||||
)
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json().catch(() => ({}))
|
||||
const errorMessage =
|
||||
errorData.detail || `Failed to revoke permission: ${response.statusText}`
|
||||
throw new Error(errorMessage)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[ExpensesAPI] Error revoking permission:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user's transactions from journal
|
||||
*
|
||||
* @param walletKey - Wallet key for authentication (invoice key)
|
||||
* @param options - Query options for filtering and pagination
|
||||
*/
|
||||
async getUserTransactions(
|
||||
walletKey: string,
|
||||
options?: {
|
||||
limit?: number
|
||||
offset?: number
|
||||
days?: number // 5, 30, 60, or 90
|
||||
filter_user_id?: string
|
||||
filter_account_type?: string
|
||||
}
|
||||
): Promise<TransactionListResponse> {
|
||||
try {
|
||||
const url = new URL(`${this.baseUrl}/castle/api/v1/entries/user`)
|
||||
|
||||
// Add query parameters
|
||||
if (options?.limit) url.searchParams.set('limit', String(options.limit))
|
||||
if (options?.offset) url.searchParams.set('offset', String(options.offset))
|
||||
if (options?.days) url.searchParams.set('days', String(options.days))
|
||||
if (options?.filter_user_id)
|
||||
url.searchParams.set('filter_user_id', options.filter_user_id)
|
||||
if (options?.filter_account_type)
|
||||
url.searchParams.set('filter_account_type', options.filter_account_type)
|
||||
|
||||
const response = await fetch(url.toString(), {
|
||||
method: 'GET',
|
||||
headers: this.getHeaders(walletKey),
|
||||
signal: AbortSignal.timeout(this.config?.apiConfig?.timeout || 30000)
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to fetch transactions: ${response.statusText}`)
|
||||
}
|
||||
|
||||
return await response.json()
|
||||
} catch (error) {
|
||||
console.error('[ExpensesAPI] Error fetching transactions:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue