111 lines
4.6 KiB
TypeScript
111 lines
4.6 KiB
TypeScript
import React, { useState, useCallback } from 'react'
|
|
import { View, Text, ScrollView, StyleSheet, RefreshControl } from 'react-native'
|
|
import { useFocusEffect } from 'expo-router'
|
|
import { COLORS } from '../../constants/Config'
|
|
import client from '../../services/api'
|
|
|
|
export default function GreenOpsDashboardScreen() {
|
|
const [energy, setEnergy] = useState<any>(null)
|
|
const [carbon, setCarbon] = useState<any>(null)
|
|
const [loading, setLoading] = useState(false)
|
|
|
|
const load = useCallback(async () => {
|
|
setLoading(true)
|
|
try {
|
|
const [e, c] = await Promise.all([
|
|
client.get('/api/greenops/energy'),
|
|
client.get('/api/greenops/carbon'),
|
|
])
|
|
setEnergy(e.data); setCarbon(c.data)
|
|
} catch {} finally { setLoading(false) }
|
|
}, [])
|
|
|
|
useFocusEffect(useCallback(() => { load() }, [load]))
|
|
|
|
const servers = energy?.servers ?? energy?.items ?? []
|
|
const trend = carbon?.monthly ?? carbon?.trend ?? []
|
|
|
|
return (
|
|
<ScrollView style={s.container} refreshControl={<RefreshControl refreshing={loading} onRefresh={load} />} contentContainerStyle={{ padding: 12 }}>
|
|
|
|
{/* 헤더 요약 */}
|
|
<View style={s.summary}>
|
|
<SummaryCard label="이번달 전력" value={energy?.total_kwh != null ? `${energy.total_kwh} kWh` : '-'} color={COLORS.success} icon="⚡" />
|
|
<SummaryCard label="탄소 배출량" value={carbon?.total_co2_kg != null ? `${carbon.total_co2_kg} kg` : '-'} color={COLORS.warning} icon="🌿" />
|
|
<SummaryCard label="효율 점수" value={energy?.efficiency_score != null ? `${energy.efficiency_score}%` : '-'} color={COLORS.accent} icon="📊" />
|
|
</View>
|
|
|
|
{/* 서버별 전력 */}
|
|
{servers.length > 0 && (
|
|
<>
|
|
<Text style={s.section}>서버별 전력 사용량</Text>
|
|
{servers.slice(0, 10).map((s2: any, i: number) => {
|
|
const kwh = s2.kwh ?? s2.power_kwh ?? 0
|
|
const max = Math.max(...servers.map((x: any) => x.kwh ?? x.power_kwh ?? 0), 1)
|
|
return (
|
|
<View key={i} style={s.row}>
|
|
<Text style={s.sName} numberOfLines={1}>{s2.name ?? s2.server_name ?? 'N/A'}</Text>
|
|
<View style={s.barBg}>
|
|
<View style={[s.barFill, { width: `${(kwh/max)*100}%` }]} />
|
|
</View>
|
|
<Text style={s.kwhText}>{kwh}kWh</Text>
|
|
</View>
|
|
)
|
|
})}
|
|
</>
|
|
)}
|
|
|
|
{/* 월별 탄소 추이 */}
|
|
{trend.length > 0 && (
|
|
<>
|
|
<Text style={s.section}>월별 탄소 배출 추이</Text>
|
|
<View style={s.trendRow}>
|
|
{trend.slice(-6).map((t: any, i: number) => {
|
|
const val = t.co2_kg ?? t.value ?? 0
|
|
const max2 = Math.max(...trend.map((x: any) => x.co2_kg ?? x.value ?? 0), 1)
|
|
const h = Math.max(20, Math.round((val/max2)*80))
|
|
return (
|
|
<View key={i} style={s.bar}>
|
|
<View style={[s.barV, { height: h }]} />
|
|
<Text style={s.barLabel}>{t.month?.slice(5) ?? `M${i+1}`}</Text>
|
|
</View>
|
|
)
|
|
})}
|
|
</View>
|
|
</>
|
|
)}
|
|
</ScrollView>
|
|
)
|
|
}
|
|
|
|
function SummaryCard({ label, value, color, icon }: any) {
|
|
return (
|
|
<View style={[c.card, { borderTopColor: color }]}>
|
|
<Text style={c.icon}>{icon}</Text>
|
|
<Text style={[c.val, { color }]}>{value}</Text>
|
|
<Text style={c.label}>{label}</Text>
|
|
</View>
|
|
)
|
|
}
|
|
const c = StyleSheet.create({
|
|
card: { flex: 1, backgroundColor: '#fff', borderRadius: 10, padding: 12, alignItems: 'center', borderTopWidth: 3, elevation: 1 },
|
|
icon: { fontSize: 24, marginBottom: 4 },
|
|
val: { fontSize: 16, fontWeight: '800', marginBottom: 2 },
|
|
label: { fontSize: 11, color: COLORS.muted },
|
|
})
|
|
|
|
const s = StyleSheet.create({
|
|
container: { flex: 1, backgroundColor: COLORS.bg },
|
|
summary: { flexDirection: 'row', gap: 8, marginBottom: 12 },
|
|
section: { fontSize: 14, fontWeight: '700', color: COLORS.text, marginTop: 8, marginBottom: 8 },
|
|
row: { flexDirection: 'row', alignItems: 'center', gap: 8, marginBottom: 8 },
|
|
sName: { fontSize: 12, color: COLORS.text, width: 90 },
|
|
barBg: { flex: 1, height: 8, backgroundColor: COLORS.border, borderRadius: 4 },
|
|
barFill: { height: 8, backgroundColor: COLORS.success, borderRadius: 4 },
|
|
kwhText: { fontSize: 11, color: COLORS.muted, width: 55, textAlign: 'right' },
|
|
trendRow: { flexDirection: 'row', alignItems: 'flex-end', gap: 8, height: 100 },
|
|
bar: { flex: 1, alignItems: 'center' },
|
|
barV: { width: '80%', backgroundColor: COLORS.accent, borderRadius: 4 },
|
|
barLabel: { fontSize: 10, color: COLORS.muted, marginTop: 4 },
|
|
})
|