eletrotupi / tcc/ frontend/components/insights/InsightMetricCard.tsxfb784c6226618a02a32f1f7d619d8844b6b623ef
3.8 KB | 157 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
  // Plain-language read of the number, e.g. "Baixa"
19
  band?: string;
20
  windowLabel?: string;
21
  detail?: string;
22
  barRatio?: number;
23
  comparison?: string | null;
24
  comparisonColor?: string;
25
}
26

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

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

61
          {band && <Text style={styles.band}>{band}</Text>}
62

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

77
          {windowLabel && <Text style={styles.window}>{windowLabel}</Text>}
78

79
          {detail && <Text style={styles.detail}>{detail}</Text>}
80

81
          {comparison && (
82
            <Text style={[styles.comparison, { color: comparisonColor }]}>
83
              {comparison}
84
            </Text>
85
          )}
86
        </>
87
      )}
88
    </View>
89
  </Card>
90
);
91

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