94 lines
2.4 KiB
TypeScript
94 lines
2.4 KiB
TypeScript
import { useCallback, useEffect, useState } from "react";
|
|
import { Text, View } from "react-native";
|
|
import PieChart from "react-native-pie-chart";
|
|
|
|
import { getRiskLevel } from "@/utils/getRiskLevel";
|
|
|
|
interface RiskScoreIndicatorProps {
|
|
value: number;
|
|
maxValue?: number;
|
|
size?: number;
|
|
subtitle?: string;
|
|
}
|
|
|
|
/**
|
|
* RiskScoreIndicator component displays a visual representation of risk level
|
|
* using PieChart from react-native-pie-chart
|
|
*/
|
|
export const RiskScoreIndicator = ({
|
|
value,
|
|
maxValue = 100,
|
|
size = 100,
|
|
subtitle,
|
|
}: RiskScoreIndicatorProps) => {
|
|
const { color } = getRiskLevel(value);
|
|
|
|
const [animatedValue, setAnimatedValue] = useState(0.1);
|
|
const finalValue = value > 0 ? value : 0.1;
|
|
|
|
const animationSetup = useCallback(() => {
|
|
const startTime = Date.now();
|
|
const duration = 1000;
|
|
|
|
const animate = () => {
|
|
const elapsed = Date.now() - startTime;
|
|
const progress = Math.min(elapsed / duration, 1);
|
|
|
|
const easeOut = (t: number) => 1 - Math.pow(1 - t, 2);
|
|
|
|
const currentValue = 0.1 + easeOut(progress) * (finalValue - 0.1);
|
|
setAnimatedValue(currentValue);
|
|
|
|
if (progress < 1) {
|
|
requestAnimationFrame(animate);
|
|
}
|
|
};
|
|
|
|
const animationFrame = requestAnimationFrame(animate);
|
|
|
|
return () => cancelAnimationFrame(animationFrame);
|
|
}, [finalValue]);
|
|
|
|
useEffect(() => {
|
|
animationSetup();
|
|
}, [animationSetup]);
|
|
|
|
return (
|
|
<View className="items-center">
|
|
<View
|
|
className="relative justify-center items-center bg-white p-3 rounded-full"
|
|
style={{
|
|
width: size,
|
|
height: size,
|
|
}}
|
|
>
|
|
<View className="absolute rotate-[-145deg]">
|
|
<PieChart
|
|
widthAndHeight={size - 12}
|
|
series={[
|
|
{
|
|
value: animatedValue,
|
|
color,
|
|
},
|
|
{
|
|
value:
|
|
maxValue - animatedValue > 0 ? maxValue - animatedValue : 0.1,
|
|
color: "white",
|
|
},
|
|
{ value: maxValue / 4, color: "transparent" },
|
|
]}
|
|
cover={{ radius: 0.8, color: "white" }}
|
|
/>
|
|
</View>
|
|
<View className="absolute bottom-10 items-center">
|
|
<Text className="text-black text-3xl font-normal">
|
|
{Math.round(value)}
|
|
</Text>
|
|
</View>
|
|
</View>
|
|
|
|
{subtitle && <Text className="text-black text-sm mt-2">{subtitle}</Text>}
|
|
</View>
|
|
);
|
|
};
|