diff --git a/App.js b/App.js
index 5b04146..cb3877f 100644
--- a/App.js
+++ b/App.js
@@ -14,6 +14,7 @@ import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import { SafeAreaProvider } from 'react-native-safe-area-context';
import PlantDetailScreen from './screens/PlantDetailScreen';
+import ComparisonScreen from './screens/ComparisonScreen';
const API_URL = 'https://solorpower.dadot.net/plants/1';
const Stack = createNativeStackNavigator();
@@ -156,6 +157,12 @@ function DashboardScreen({ navigation }) {
☀️ 태양광 발전 현황
+ navigation.navigate('Comparison')}
+ >
+ 📊 전체 비교
+
실시간 모니터링 대시보드
@@ -269,6 +276,7 @@ export default function App() {
}}
>
+
@@ -497,4 +505,18 @@ const styles = StyleSheet.create({
width: 40,
textAlign: 'right',
},
+ compareButton: {
+ backgroundColor: 'rgba(255,255,255,0.2)',
+ paddingHorizontal: 12,
+ paddingVertical: 6,
+ borderRadius: 20,
+ borderWidth: 1,
+ borderColor: 'rgba(255,255,255,0.4)',
+ marginLeft: 12,
+ },
+ compareButtonText: {
+ color: '#FFFFFF',
+ fontSize: 14,
+ fontWeight: '600',
+ },
});
diff --git a/screens/ComparisonScreen.js b/screens/ComparisonScreen.js
new file mode 100644
index 0000000..d6b09bc
--- /dev/null
+++ b/screens/ComparisonScreen.js
@@ -0,0 +1,432 @@
+import React, { useState, useEffect, useCallback } from 'react';
+import {
+ StyleSheet,
+ View,
+ Text,
+ TouchableOpacity,
+ ActivityIndicator,
+ ScrollView,
+ useWindowDimensions,
+} from 'react-native';
+import { SafeAreaView } from 'react-native-safe-area-context';
+import { BarChart } from 'react-native-gifted-charts';
+
+const API_BASE_URL = 'https://solorpower.dadot.net';
+
+export default function ComparisonScreen({ navigation }) {
+ const [period, setPeriod] = useState('day');
+ const [statsData, setStatsData] = useState([]);
+ const [loading, setLoading] = useState(true);
+ const [error, setError] = useState(null);
+ const [currentDate, setCurrentDate] = useState(new Date());
+ const { width } = useWindowDimensions();
+
+ useEffect(() => {
+ setCurrentDate(new Date());
+ }, [period]);
+
+ const moveDate = (direction) => {
+ const newDate = new Date(currentDate);
+ if (period === 'day') {
+ newDate.setDate(newDate.getDate() + direction);
+ } else if (period === 'month') {
+ newDate.setMonth(newDate.getMonth() + direction);
+ } else if (period === 'year') {
+ newDate.setFullYear(newDate.getFullYear() + direction);
+ }
+ setCurrentDate(newDate);
+ };
+
+ const formatDateDisplay = () => {
+ const y = currentDate.getFullYear();
+ const m = currentDate.getMonth() + 1;
+ const d = currentDate.getDate();
+
+ if (period === 'day') return `${y}년 ${m}월 ${d}일`;
+ if (period === 'month') return `${y}년 ${m}월`;
+ if (period === 'year') return `${y}년`;
+ return '';
+ };
+
+ const fetchStats = useCallback(async () => {
+ try {
+ setLoading(true);
+ setError(null);
+
+ const y = currentDate.getFullYear();
+ const m = currentDate.getMonth() + 1;
+ const d = currentDate.getDate();
+ const dateStr = `${y}-${String(m).padStart(2, '0')}-${String(d).padStart(2, '0')}`;
+
+ let query = `period=${period}`;
+ if (period === 'day') {
+ query += `&date=${dateStr}`;
+ } else if (period === 'month') {
+ query += `&year=${y}&month=${m}`;
+ } else if (period === 'year') {
+ query += `&year=${y}`;
+ }
+
+ const response = await fetch(`${API_BASE_URL}/plants/stats/comparison?${query}`);
+ if (!response.ok) throw new Error(`HTTP Error: ${response.status}`);
+
+ const result = await response.json();
+ setStatsData(result.data || []);
+
+ } catch (err) {
+ setError(err.message);
+ setStatsData([]);
+ } finally {
+ setLoading(false);
+ }
+ }, [period, currentDate]);
+
+ useEffect(() => {
+ fetchStats();
+ }, [fetchStats]);
+
+ // Chart Data Preparation
+ const chartData = statsData.map(item => {
+ let label = item.plant_name;
+ const match = label.match(/(\d+호기)/);
+ if (match) {
+ label = match[1];
+ } else {
+ label = label.replace(/태양과바람|발전소/g, '').trim();
+ }
+
+ return {
+ value: item.generation,
+ label: label,
+ frontColor: '#3B82F6',
+ topLabelComponent: () => (
+
+ {item.generation.toFixed(0)}
+
+ ),
+ };
+ });
+
+ const renderTabs = () => (
+
+ {['day', 'month', 'year'].map((p) => (
+ setPeriod(p)}
+ >
+
+ {p === 'day' ? '일간' : p === 'month' ? '월간' : '연간'}
+
+
+ ))}
+
+ );
+
+ const renderDateControl = () => (
+
+ moveDate(-1)} style={styles.arrowButton}>
+ ◀
+
+ {formatDateDisplay()}
+ moveDate(1)} style={styles.arrowButton}>
+ ▶
+
+
+ );
+
+ const renderTable = () => (
+
+
+ 발전소
+ 발전량
+ 용량
+ 시간
+
+ {statsData.map((item, index) => (
+
+ {item.plant_name}
+ {item.generation.toFixed(1)}
+ {item.capacity}
+
+ {item.generation_hours.toFixed(2)}
+
+
+ ))}
+ {statsData.length === 0 && (
+
+ 데이터가 없습니다
+
+ )}
+
+ );
+
+ const chartWidth = width - 40;
+
+ return (
+
+
+ navigation.goBack()}>
+ ← 뒤로
+
+ 전체 발전소 비교
+
+
+
+
+ {renderDateControl()}
+ {renderTabs()}
+
+ {loading ? (
+
+
+ 데이터 불러오는 중...
+
+ ) : error ? (
+
+ 데이터 조회 실패
+ {error}
+
+ 다시 시도
+
+
+ ) : (
+ <>
+
+ 📊 발전량 비교 (kWh)
+
+ {statsData.length > 0 ? (
+
+ ) : (
+ 표시할 데이터가 없습니다.
+ )}
+
+
+
+
+
+ 📋 상세 현황
+ 시간 = 발전량 / 설비용량 {period === 'year' ? '/ 365' : ''}
+
+ {renderTable()}
+
+ >
+ )}
+
+
+ );
+}
+
+const styles = StyleSheet.create({
+ container: {
+ flex: 1,
+ backgroundColor: '#F3F4F6',
+ },
+ header: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ justifyContent: 'space-between',
+ backgroundColor: '#1E40AF',
+ padding: 16,
+ },
+ backButton: {
+ padding: 8,
+ },
+ backButtonText: {
+ color: '#FFFFFF',
+ fontSize: 16,
+ fontWeight: '500',
+ },
+ headerTitle: {
+ fontSize: 18,
+ fontWeight: 'bold',
+ color: '#FFFFFF',
+ },
+ content: {
+ flex: 1,
+ },
+ scrollContent: {
+ padding: 16,
+ paddingBottom: 40,
+ },
+ dateControl: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ justifyContent: 'center',
+ marginBottom: 16,
+ gap: 16,
+ },
+ arrowButton: {
+ padding: 8,
+ },
+ arrowText: {
+ fontSize: 20,
+ color: '#4B5563',
+ },
+ dateText: {
+ fontSize: 18,
+ fontWeight: 'bold',
+ color: '#1F2937',
+ },
+ tabContainer: {
+ flexDirection: 'row',
+ backgroundColor: '#FFFFFF',
+ borderRadius: 12,
+ padding: 4,
+ marginBottom: 16,
+ shadowColor: '#000',
+ shadowOpacity: 0.05,
+ shadowRadius: 4,
+ elevation: 2,
+ },
+ tab: {
+ flex: 1,
+ paddingVertical: 10,
+ alignItems: 'center',
+ borderRadius: 8,
+ },
+ tabActive: {
+ backgroundColor: '#3B82F6',
+ },
+ tabText: {
+ fontSize: 14,
+ fontWeight: '600',
+ color: '#6B7280',
+ },
+ tabTextActive: {
+ color: '#FFFFFF',
+ },
+ loadingContainer: {
+ height: 300,
+ justifyContent: 'center',
+ alignItems: 'center',
+ },
+ loadingText: {
+ marginTop: 12,
+ color: '#6B7280',
+ },
+ errorContainer: {
+ height: 300,
+ justifyContent: 'center',
+ alignItems: 'center',
+ },
+ errorText: {
+ fontSize: 16,
+ color: '#EF4444',
+ marginBottom: 8,
+ },
+ errorDetail: {
+ fontSize: 12,
+ color: '#6B7280',
+ marginBottom: 16,
+ },
+ retryButton: {
+ backgroundColor: '#3B82F6',
+ paddingHorizontal: 20,
+ paddingVertical: 10,
+ borderRadius: 8,
+ },
+ retryButtonText: {
+ color: '#FFFFFF',
+ },
+ chartSection: {
+ backgroundColor: '#FFFFFF',
+ borderRadius: 16,
+ padding: 16,
+ marginBottom: 16,
+ shadowColor: '#000',
+ shadowOpacity: 0.05,
+ shadowRadius: 8,
+ elevation: 2,
+ },
+ sectionTitle: {
+ fontSize: 16,
+ fontWeight: 'bold',
+ color: '#1F2937',
+ marginBottom: 12,
+ },
+ chartContainer: {
+ alignItems: 'center',
+ paddingVertical: 10,
+ },
+ noDataText: {
+ color: '#9CA3AF',
+ marginVertical: 20,
+ },
+ tableSection: {
+ backgroundColor: '#FFFFFF',
+ borderRadius: 16,
+ padding: 16,
+ shadowColor: '#000',
+ shadowOpacity: 0.05,
+ shadowRadius: 8,
+ elevation: 2,
+ },
+ tableHeaderRow: {
+ flexDirection: 'row',
+ justifyContent: 'space-between',
+ alignItems: 'center',
+ marginBottom: 12,
+ },
+ unitText: {
+ fontSize: 11,
+ color: '#6B7280',
+ },
+ table: {
+ borderWidth: 1,
+ borderColor: '#E5E7EB',
+ borderRadius: 8,
+ overflow: 'hidden',
+ },
+ tableHeader: {
+ flexDirection: 'row',
+ backgroundColor: '#F3F4F6',
+ paddingVertical: 10,
+ paddingHorizontal: 8,
+ borderBottomWidth: 1,
+ borderBottomColor: '#E5E7EB',
+ },
+ columnHeader: {
+ fontSize: 12,
+ fontWeight: 'bold',
+ color: '#4B5563',
+ textAlign: 'center',
+ },
+ tableRow: {
+ flexDirection: 'row',
+ paddingVertical: 12,
+ paddingHorizontal: 8,
+ borderBottomWidth: 1,
+ borderBottomColor: '#E5E7EB',
+ },
+ tableRowAlt: {
+ backgroundColor: '#F9FAFB',
+ },
+ cell: {
+ fontSize: 12,
+ color: '#1F2937',
+ textAlign: 'center',
+ },
+ emptyRow: {
+ padding: 20,
+ alignItems: 'center',
+ },
+ emptyText: {
+ color: '#9CA3AF',
+ fontSize: 14,
+ },
+});