|
1 | | -import React, { useEffect, useState, useCallback } from "react"; |
| 1 | +import React, { useEffect, useState, useCallback, useMemo } from "react"; |
2 | 2 | import { |
3 | 3 | View, |
4 | 4 | Text, |
@@ -30,6 +30,25 @@ import { |
30 | 30 | * it has been pending longer than this, in milliseconds. */ |
31 | 31 | const STUCK_PENDING_MS = 60 * 1000; |
32 | 32 |
|
| 33 | +// #692 — Yield transaction history filtering presets. |
| 34 | +const DATE_PRESETS: { label: string; days: number }[] = [ |
| 35 | + { label: "7D", days: 7 }, |
| 36 | + { label: "30D", days: 30 }, |
| 37 | + { label: "90D", days: 90 }, |
| 38 | + { label: "1Y", days: 365 }, |
| 39 | +]; |
| 40 | + |
| 41 | +function presetStartIso(days: number): string { |
| 42 | + const d = new Date(); |
| 43 | + d.setDate(d.getDate() - days); |
| 44 | + d.setHours(0, 0, 0, 0); |
| 45 | + return d.toISOString().slice(0, 10); |
| 46 | +} |
| 47 | + |
| 48 | +function isPresetActive(filters: TransactionFilters, days: number): boolean { |
| 49 | + return filters.dateFrom === presetStartIso(days) && !filters.dateTo; |
| 50 | +} |
| 51 | + |
33 | 52 | // ── Helpers ─────────────────────────────────────────────────────────────────── |
34 | 53 |
|
35 | 54 | /** |
@@ -112,7 +131,8 @@ function hasActiveFilters(f: TransactionFilters): boolean { |
112 | 131 | !!f.dateTo || |
113 | 132 | !!f.amountMin || |
114 | 133 | !!f.amountMax || |
115 | | - !!f.yieldOnly |
| 134 | + !!f.yieldOnly || |
| 135 | + !!f.asset |
116 | 136 | ); |
117 | 137 | } |
118 | 138 |
|
@@ -399,6 +419,15 @@ export default function HistoryScreen() { |
399 | 419 | const listData = groupByDate(items); |
400 | 420 | const filtersActive = hasActiveFilters(filters); |
401 | 421 |
|
| 422 | + // #692 — Distinct asset codes present in the filtered result set. |
| 423 | + const assetOptions = useMemo( |
| 424 | + () => [ |
| 425 | + "All", |
| 426 | + ...Array.from(new Set(items.map((tx) => tx.asset.toUpperCase()))).sort(), |
| 427 | + ], |
| 428 | + [items] |
| 429 | + ); |
| 430 | + |
402 | 431 | const renderItem = useCallback( |
403 | 432 | ({ item }: { item: ListItem }) => { |
404 | 433 | if (item.kind === "header") { |
@@ -567,6 +596,111 @@ export default function HistoryScreen() { |
567 | 596 | </TouchableOpacity> |
568 | 597 | </View> |
569 | 598 |
|
| 599 | + {/* #692 — Yield history filters: asset type chips + date range */} |
| 600 | + {filters.yieldOnly && ( |
| 601 | + <View style={styles.yieldFiltersCard}> |
| 602 | + <View style={styles.yieldFiltersHeader}> |
| 603 | + <Ionicons name="trending-up" size={14} color="#16A34A" /> |
| 604 | + <Text style={styles.yieldFiltersTitle}>Filter yield history</Text> |
| 605 | + </View> |
| 606 | + |
| 607 | + <Text style={styles.yieldFiltersLabel}>Asset type</Text> |
| 608 | + <View style={styles.yieldChipRow}> |
| 609 | + {assetOptions.map((asset) => { |
| 610 | + const selected = |
| 611 | + asset === "All" ? !filters.asset : filters.asset === asset; |
| 612 | + return ( |
| 613 | + <TouchableOpacity |
| 614 | + key={asset} |
| 615 | + style={[styles.assetChip, selected && styles.assetChipActive]} |
| 616 | + onPress={() => |
| 617 | + applyFilters({ |
| 618 | + ...filters, |
| 619 | + asset: asset === "All" ? undefined : asset, |
| 620 | + search: searchText, |
| 621 | + }) |
| 622 | + } |
| 623 | + activeOpacity={0.8} |
| 624 | + accessibilityRole="button" |
| 625 | + accessibilityState={{ selected }} |
| 626 | + accessibilityLabel={`Filter by ${asset}`} |
| 627 | + > |
| 628 | + <Text |
| 629 | + style={[ |
| 630 | + styles.assetChipText, |
| 631 | + selected && styles.assetChipTextActive, |
| 632 | + ]} |
| 633 | + > |
| 634 | + {asset} |
| 635 | + </Text> |
| 636 | + </TouchableOpacity> |
| 637 | + ); |
| 638 | + })} |
| 639 | + </View> |
| 640 | + |
| 641 | + <Text style={styles.yieldFiltersLabel}>Date range</Text> |
| 642 | + <View style={styles.yieldChipRow}> |
| 643 | + <TouchableOpacity |
| 644 | + style={[ |
| 645 | + styles.assetChip, |
| 646 | + !filters.dateFrom && styles.assetChipActive, |
| 647 | + ]} |
| 648 | + onPress={() => |
| 649 | + applyFilters({ |
| 650 | + ...filters, |
| 651 | + dateFrom: undefined, |
| 652 | + dateTo: undefined, |
| 653 | + search: searchText, |
| 654 | + }) |
| 655 | + } |
| 656 | + activeOpacity={0.8} |
| 657 | + accessibilityRole="button" |
| 658 | + accessibilityState={{ selected: !filters.dateFrom }} |
| 659 | + accessibilityLabel="Filter by all dates" |
| 660 | + > |
| 661 | + <Text |
| 662 | + style={[ |
| 663 | + styles.assetChipText, |
| 664 | + !filters.dateFrom && styles.assetChipTextActive, |
| 665 | + ]} |
| 666 | + > |
| 667 | + All |
| 668 | + </Text> |
| 669 | + </TouchableOpacity> |
| 670 | + {DATE_PRESETS.map((preset) => { |
| 671 | + const active = isPresetActive(filters, preset.days); |
| 672 | + return ( |
| 673 | + <TouchableOpacity |
| 674 | + key={preset.label} |
| 675 | + style={[styles.assetChip, active && styles.assetChipActive]} |
| 676 | + onPress={() => |
| 677 | + applyFilters({ |
| 678 | + ...filters, |
| 679 | + dateFrom: presetStartIso(preset.days), |
| 680 | + dateTo: undefined, |
| 681 | + search: searchText, |
| 682 | + }) |
| 683 | + } |
| 684 | + activeOpacity={0.8} |
| 685 | + accessibilityRole="button" |
| 686 | + accessibilityState={{ selected: active }} |
| 687 | + accessibilityLabel={`Filter by last ${preset.label}`} |
| 688 | + > |
| 689 | + <Text |
| 690 | + style={[ |
| 691 | + styles.assetChipText, |
| 692 | + active && styles.assetChipTextActive, |
| 693 | + ]} |
| 694 | + > |
| 695 | + {preset.label} |
| 696 | + </Text> |
| 697 | + </TouchableOpacity> |
| 698 | + ); |
| 699 | + })} |
| 700 | + </View> |
| 701 | + </View> |
| 702 | + )} |
| 703 | + |
570 | 704 | {/* Summary row */} |
571 | 705 | {!loading && items.length > 0 && ( |
572 | 706 | <View style={styles.summaryRow}> |
@@ -890,6 +1024,62 @@ const styles = StyleSheet.create({ |
890 | 1024 | paddingHorizontal: 20, |
891 | 1025 | paddingBottom: Platform.OS === "ios" ? 40 : 24, |
892 | 1026 | }, |
| 1027 | + // #692 — Yield history filter card |
| 1028 | + yieldFiltersCard: { |
| 1029 | + marginHorizontal: 20, |
| 1030 | + marginBottom: 10, |
| 1031 | + padding: 14, |
| 1032 | + borderRadius: 16, |
| 1033 | + backgroundColor: "#F0FDF4", |
| 1034 | + borderWidth: 1, |
| 1035 | + borderColor: "#C8EAD1", |
| 1036 | + }, |
| 1037 | + yieldFiltersHeader: { |
| 1038 | + flexDirection: "row", |
| 1039 | + alignItems: "center", |
| 1040 | + gap: 6, |
| 1041 | + marginBottom: 12, |
| 1042 | + }, |
| 1043 | + yieldFiltersTitle: { |
| 1044 | + fontSize: 13, |
| 1045 | + fontFamily: "Outfit_700Bold", |
| 1046 | + color: "#16A34A", |
| 1047 | + }, |
| 1048 | + yieldFiltersLabel: { |
| 1049 | + fontSize: 11, |
| 1050 | + fontFamily: "Outfit_600SemiBold", |
| 1051 | + color: "#6B8D74", |
| 1052 | + textTransform: "uppercase", |
| 1053 | + letterSpacing: 0.4, |
| 1054 | + marginBottom: 8, |
| 1055 | + marginTop: 4, |
| 1056 | + }, |
| 1057 | + yieldChipRow: { |
| 1058 | + flexDirection: "row", |
| 1059 | + flexWrap: "wrap", |
| 1060 | + gap: 8, |
| 1061 | + marginBottom: 8, |
| 1062 | + }, |
| 1063 | + assetChip: { |
| 1064 | + paddingHorizontal: 14, |
| 1065 | + paddingVertical: 7, |
| 1066 | + borderRadius: 100, |
| 1067 | + borderWidth: 1.5, |
| 1068 | + borderColor: "#B9DCC4", |
| 1069 | + backgroundColor: "#FFFFFF", |
| 1070 | + }, |
| 1071 | + assetChipActive: { |
| 1072 | + borderColor: "#16A34A", |
| 1073 | + backgroundColor: "#16A34A", |
| 1074 | + }, |
| 1075 | + assetChipText: { |
| 1076 | + fontSize: 13, |
| 1077 | + fontFamily: "Outfit_600SemiBold", |
| 1078 | + color: "#166534", |
| 1079 | + }, |
| 1080 | + assetChipTextActive: { |
| 1081 | + color: COLORS.secondary, |
| 1082 | + }, |
893 | 1083 | footerLoader: { paddingVertical: 20, alignItems: "center" }, |
894 | 1084 | // Empty |
895 | 1085 | emptyContainer: { |
|
0 commit comments