eletrotupi / tcc/ frontend/components/insights/InsightMetricCard.tsx5a1373aed06bccb3f57d57b91da01d9500c1ce91
3.6 KB | 152 lines Raw
1
import React from "react";
2
import { ActivityIndicator, StyleSheet, View } from "react-native";
3
import { Ionicons } from "@expo/vector-icons";
4
import { Text } from "@/components/ui/Text";
5
import { Card } from "@/components/ui/Cards";
6
import { BorderRadius, Colors, Spacing, Typography } from "@/constants/theme";
7

8
type IoniconName = React.ComponentProps<typeof Ionicons>["name"];
9

10
interface InsightMetricCardProps {
11
  icon: IoniconName;
12
  title: string;
13
  iconColor?: string;
14
  iconBackground?: string;
15
  isLoading?: boolean;
16
  emptyMessage?: string;
17
  value?: string;
18
  windowLabel?: string;
19
  detail?: string;
20
  barRatio?: number;
21
  comparison?: string | null;
22
  comparisonColor?: string;
23
}
24

25
// One card for the daily insight pair: a tinted icon beside the title, a hero
26
// number with its window, an optional bounded bar, and the comparison line
27
export const InsightMetricCard: React.FC<InsightMetricCardProps> = ({
28
  icon,
29
  title,
30
  iconColor = Colors.light.tint,
31
  iconBackground = Colors.light.tint + "1A",
32
  isLoading = false,
33
  emptyMessage,
34
  value,
35
  windowLabel,
36
  detail,
37
  barRatio,
38
  comparison,
39
  comparisonColor = Colors.light.textSecondary,
40
}) => (
41
  <Card style={styles.container}>
42
    <View style={styles.header}>
43
      <View style={[styles.iconWrap, { backgroundColor: iconBackground }]}>
44
        <Ionicons name={icon} size={18} color={iconColor} />
45
      </View>
46
      <Text style={styles.title}>{title}</Text>
47
    </View>
48

49
    <View style={styles.body}>
50
      {isLoading ? (
51
        <ActivityIndicator size="small" color={iconColor} />
52
      ) : value == null ? (
53
        <Text style={styles.empty}>{emptyMessage}</Text>
54
      ) : (
55
        <>
56
          <View style={styles.heroRow}>
57
            <Text style={styles.value}>{value}</Text>
58
            {windowLabel && <Text style={styles.window}>{windowLabel}</Text>}
59
          </View>
60

61
          {barRatio != null && (
62
            <View style={styles.track}>
63
              <View
64
                style={[
65
                  styles.fill,
66
                  {
67
                    width: `${Math.round(barRatio * 100)}%`,
68
                    backgroundColor: iconColor,
69
                  },
70
                ]}
71
              />
72
            </View>
73
          )}
74

75
          {detail && <Text style={styles.detail}>{detail}</Text>}
76

77
          {comparison && (
78
            <Text style={[styles.comparison, { color: comparisonColor }]}>
79
              {comparison}
80
            </Text>
81
          )}
82
        </>
83
      )}
84
    </View>
85
  </Card>
86
);
87

88
const styles = StyleSheet.create({
89
  container: {
90
    flex: 1,
91
  },
92
  header: {
93
    flexDirection: "row",
94
    alignItems: "center",
95
    gap: Spacing.inlineGapSm,
96
  },
97
  iconWrap: {
98
    width: 32,
99
    height: 32,
100
    borderRadius: BorderRadius.md,
101
    alignItems: "center",
102
    justifyContent: "center",
103
  },
104
  title: {
105
    ...Typography.bodyMd,
106
    fontWeight: "700",
107
    color: Colors.light.text,
108
  },
109
  body: {
110
    marginTop: 10,
111
    gap: 6,
112
  },
113
  heroRow: {
114
    flexDirection: "row",
115
    alignItems: "baseline",
116
    gap: 6,
117
  },
118
  value: {
119
    ...Typography.headlineXg,
120
    fontSize: 28,
121
    lineHeight: 32,
122
    color: Colors.light.text,
123
  },
124
  window: {
125
    ...Typography.labelSm,
126
    fontWeight: "500",
127
    color: Colors.light.textSecondary,
128
  },
129
  track: {
130
    height: 6,
131
    borderRadius: BorderRadius.full,
132
    backgroundColor: Colors.light.divider,
133
    overflow: "hidden",
134
  },
135
  fill: {
136
    height: "100%",
137
    borderRadius: BorderRadius.full,
138
  },
139
  detail: {
140
    ...Typography.labelXs,
141
    color: Colors.light.textSecondary,
142
  },
143
  comparison: {
144
    ...Typography.labelSm,
145
    fontWeight: "600",
146
  },
147
  empty: {
148
    ...Typography.labelSm,
149
    color: Colors.light.textSecondary,
150
    opacity: 0.6,
151
  },
152
});