144 lines
4.4 KiB
TypeScript
144 lines
4.4 KiB
TypeScript
import { memo } from 'react'
|
||
import { Battery, RotateCcw, ShieldCheck, Smartphone } from 'lucide-react'
|
||
import type { Device } from '../../types'
|
||
|
||
type DeviceStatsCardsProps = {
|
||
device: Device
|
||
}
|
||
|
||
function formatWorkTimeHours(worktime?: number | null) {
|
||
if (typeof worktime !== 'number') return '-'
|
||
|
||
const hours = worktime / 1000 / 60 / 60
|
||
|
||
if (hours < 1) {
|
||
const minutes = Math.round(worktime / 1000 / 60)
|
||
|
||
return `${minutes} мин`
|
||
}
|
||
|
||
if (hours < 10) {
|
||
return `${hours.toFixed(1)} ч`
|
||
}
|
||
|
||
return `${Math.round(hours)} ч`
|
||
}
|
||
|
||
function areStatsCardsPropsEqual(
|
||
prev: DeviceStatsCardsProps,
|
||
next: DeviceStatsCardsProps,
|
||
) {
|
||
return (
|
||
prev.device.battery === next.device.battery &&
|
||
prev.device.batteryMaxCapacity === next.device.batteryMaxCapacity &&
|
||
prev.device.chargeCycles === next.device.chargeCycles &&
|
||
prev.device.totalWorkTime === next.device.totalWorkTime &&
|
||
prev.device.mediumImpacts === next.device.mediumImpacts
|
||
)
|
||
}
|
||
|
||
export const DeviceStatsCards = memo(function DeviceStatsCards({
|
||
device,
|
||
}: DeviceStatsCardsProps) {
|
||
|
||
const batteryValue =
|
||
typeof device.battery === 'number'
|
||
? Math.max(0, Math.min(100, device.battery))
|
||
: null
|
||
|
||
const batterySize = 110
|
||
const batteryStroke = 12
|
||
const batteryRadius = (batterySize - batteryStroke) / 2
|
||
const batteryLength = 2 * Math.PI * batteryRadius
|
||
const batteryProgress = batteryValue !== null ? batteryValue / 100 : 0
|
||
const batteryDashOffset = batteryLength * (1 - batteryProgress)
|
||
|
||
function getBatteryColor(value: number | null) {
|
||
if (value === null) return 'var(--device-battery-progress-empty)'
|
||
if (value <= 20) return 'var(--device-battery-progress-danger)'
|
||
if (value <= 50) return 'var(--device-battery-progress-warning)'
|
||
|
||
return 'var(--device-battery-progress-strong)'
|
||
}
|
||
|
||
return (
|
||
<div className="device-card-stats">
|
||
<div className="device-card device-battery">
|
||
<h3>Аккумулятор</h3>
|
||
|
||
<div className="device-battery__content">
|
||
<div className="device-battery__circle">
|
||
<svg
|
||
className="device-battery__circle-svg"
|
||
viewBox={`0 0 ${batterySize} ${batterySize}`}
|
||
aria-hidden="true"
|
||
>
|
||
<g transform={`rotate(45 ${batterySize / 2} ${batterySize / 2})`}>
|
||
<circle
|
||
className="device-battery__circle-track"
|
||
cx={batterySize / 2}
|
||
cy={batterySize / 2}
|
||
r={batteryRadius}
|
||
strokeWidth={batteryStroke}
|
||
/>
|
||
|
||
{batteryValue !== null && (
|
||
<circle
|
||
className="device-battery__circle-progress"
|
||
cx={batterySize / 2}
|
||
cy={batterySize / 2}
|
||
r={batteryRadius}
|
||
strokeWidth={batteryStroke}
|
||
stroke={getBatteryColor(batteryValue)}
|
||
strokeDasharray={batteryLength}
|
||
strokeDashoffset={batteryDashOffset}
|
||
/>
|
||
)}
|
||
</g>
|
||
</svg>
|
||
|
||
<span>{batteryValue !== null ? `${batteryValue}%` : '-'}</span>
|
||
</div>
|
||
|
||
<div className="device-stats">
|
||
<div className="device-stats-card">
|
||
<Battery size={18} />
|
||
|
||
<div className="device-stats-text">
|
||
<span>Остаточная емкость</span>
|
||
<b>{device.batteryMaxCapacity ?? '-'}%</b>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="device-stats-card">
|
||
<RotateCcw size={18} />
|
||
|
||
<div className="device-stats-text">
|
||
<span>Циклов перезарядки</span>
|
||
<b>{device.chargeCycles ?? '-'}</b>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="device-stats-card">
|
||
<Smartphone size={18} />
|
||
|
||
<div className="device-stats-text">
|
||
<span>Общее время работы</span>
|
||
<b>{formatWorkTimeHours(device.totalWorkTime)}</b>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="device-card device-impacts">
|
||
<ShieldCheck size={18} />
|
||
<div className="device-stats-text">
|
||
<span>Средних ударов</span>
|
||
<b>{device.mediumImpacts ?? '-'}</b>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)
|
||
}, areStatsCardsPropsEqual)
|