guardia-messenger/app/(tabs)/greenops_dashboard.tsx

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 },
})