Compare commits
No commits in common. "0e0b64f20f3d3dfd31a99eb8adf7d65c82536251" and "3420bee2bf88489153c1369b3307db486d617cb0" have entirely different histories.
0e0b64f20f
...
3420bee2bf
81
App.js
81
App.js
|
|
@ -8,14 +8,12 @@ import {
|
||||||
ActivityIndicator,
|
ActivityIndicator,
|
||||||
useWindowDimensions,
|
useWindowDimensions,
|
||||||
RefreshControl,
|
RefreshControl,
|
||||||
Switch,
|
|
||||||
} from 'react-native';
|
} from 'react-native';
|
||||||
import { StatusBar } from 'expo-status-bar';
|
import { StatusBar } from 'expo-status-bar';
|
||||||
import { NavigationContainer } from '@react-navigation/native';
|
import { NavigationContainer } from '@react-navigation/native';
|
||||||
import { createNativeStackNavigator } from '@react-navigation/native-stack';
|
import { createNativeStackNavigator } from '@react-navigation/native-stack';
|
||||||
import { SafeAreaProvider } from 'react-native-safe-area-context';
|
import { SafeAreaProvider } from 'react-native-safe-area-context';
|
||||||
import PlantDetailScreen from './screens/PlantDetailScreen';
|
import PlantDetailScreen from './screens/PlantDetailScreen';
|
||||||
import ComparisonScreen from './screens/ComparisonScreen';
|
|
||||||
|
|
||||||
const API_URL = 'https://solorpower.dadot.net/plants/1';
|
const API_URL = 'https://solorpower.dadot.net/plants/1';
|
||||||
const Stack = createNativeStackNavigator();
|
const Stack = createNativeStackNavigator();
|
||||||
|
|
@ -51,42 +49,6 @@ function DashboardScreen({ navigation }) {
|
||||||
fetchData();
|
fetchData();
|
||||||
}, [fetchData]);
|
}, [fetchData]);
|
||||||
|
|
||||||
const handleToggleAlert = async (plant, newValue) => {
|
|
||||||
try {
|
|
||||||
setData((prev) => {
|
|
||||||
if (!prev) return prev;
|
|
||||||
const newData = { ...prev };
|
|
||||||
newData.data = newData.data.map(p =>
|
|
||||||
p.id === plant.id ? { ...p, alerts_enabled: newValue } : p
|
|
||||||
);
|
|
||||||
return newData;
|
|
||||||
});
|
|
||||||
|
|
||||||
const response = await fetch(`https://solorpower.dadot.net/plants/${plant.company_id || 1}/${plant.id}/alerts`, {
|
|
||||||
method: 'PATCH',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
body: JSON.stringify({ alerts_enabled: newValue }),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error('Failed to update alert settings');
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.error(err);
|
|
||||||
setData((prev) => {
|
|
||||||
if (!prev) return prev;
|
|
||||||
const newData = { ...prev };
|
|
||||||
newData.data = newData.data.map(p =>
|
|
||||||
p.id === plant.id ? { ...p, alerts_enabled: !newValue } : p
|
|
||||||
);
|
|
||||||
return newData;
|
|
||||||
});
|
|
||||||
alert('알림 설정 변경에 실패했습니다.');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const onRefresh = useCallback(() => {
|
const onRefresh = useCallback(() => {
|
||||||
setRefreshing(true);
|
setRefreshing(true);
|
||||||
fetchData();
|
fetchData();
|
||||||
|
|
@ -194,12 +156,6 @@ function DashboardScreen({ navigation }) {
|
||||||
<View style={styles.header}>
|
<View style={styles.header}>
|
||||||
<View style={styles.headerTop}>
|
<View style={styles.headerTop}>
|
||||||
<Text style={styles.headerTitle}>☀️ 태양광 발전 현황</Text>
|
<Text style={styles.headerTitle}>☀️ 태양광 발전 현황</Text>
|
||||||
<TouchableOpacity
|
|
||||||
style={styles.compareButton}
|
|
||||||
onPress={() => navigation.navigate('Comparison')}
|
|
||||||
>
|
|
||||||
<Text style={styles.compareButtonText}>📊 전체 비교</Text>
|
|
||||||
</TouchableOpacity>
|
|
||||||
</View>
|
</View>
|
||||||
<Text style={styles.headerSubtitle}>실시간 모니터링 대시보드</Text>
|
<Text style={styles.headerSubtitle}>실시간 모니터링 대시보드</Text>
|
||||||
</View>
|
</View>
|
||||||
|
|
@ -255,17 +211,7 @@ function DashboardScreen({ navigation }) {
|
||||||
{statusInfo.label}
|
{statusInfo.label}
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
<View style={styles.alertToggleContainer}>
|
<Text style={styles.arrowIcon}>›</Text>
|
||||||
<Text style={styles.alertToggleLabel}>
|
|
||||||
{plant.alerts_enabled !== false ? '알림 ON' : '알림 OFF'}
|
|
||||||
</Text>
|
|
||||||
<Switch
|
|
||||||
value={plant.alerts_enabled !== false}
|
|
||||||
onValueChange={(value) => handleToggleAlert(plant, value)}
|
|
||||||
trackColor={{ false: '#D1D5DB', true: '#3B82F6' }}
|
|
||||||
thumbColor={'#FFFFFF'}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
<View style={styles.cardBody}>
|
<View style={styles.cardBody}>
|
||||||
|
|
@ -323,7 +269,6 @@ export default function App() {
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Stack.Screen name="Dashboard" component={DashboardScreen} />
|
<Stack.Screen name="Dashboard" component={DashboardScreen} />
|
||||||
<Stack.Screen name="Comparison" component={ComparisonScreen} />
|
|
||||||
<Stack.Screen name="PlantDetail" component={PlantDetailScreen} />
|
<Stack.Screen name="PlantDetail" component={PlantDetailScreen} />
|
||||||
</Stack.Navigator>
|
</Stack.Navigator>
|
||||||
</NavigationContainer>
|
</NavigationContainer>
|
||||||
|
|
@ -504,16 +449,6 @@ const styles = StyleSheet.create({
|
||||||
color: '#9CA3AF',
|
color: '#9CA3AF',
|
||||||
fontWeight: '300',
|
fontWeight: '300',
|
||||||
},
|
},
|
||||||
alertToggleContainer: {
|
|
||||||
flexDirection: 'row',
|
|
||||||
alignItems: 'center',
|
|
||||||
gap: 8,
|
|
||||||
},
|
|
||||||
alertToggleLabel: {
|
|
||||||
fontSize: 12,
|
|
||||||
color: '#6B7280',
|
|
||||||
fontWeight: '500',
|
|
||||||
},
|
|
||||||
cardBody: {
|
cardBody: {
|
||||||
gap: 8,
|
gap: 8,
|
||||||
},
|
},
|
||||||
|
|
@ -562,18 +497,4 @@ const styles = StyleSheet.create({
|
||||||
width: 40,
|
width: 40,
|
||||||
textAlign: 'right',
|
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',
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,432 +0,0 @@
|
||||||
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: () => (
|
|
||||||
<Text style={{ color: '#6B7280', fontSize: 10, marginBottom: 4 }}>
|
|
||||||
{item.generation.toFixed(0)}
|
|
||||||
</Text>
|
|
||||||
),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
const renderTabs = () => (
|
|
||||||
<View style={styles.tabContainer}>
|
|
||||||
{['day', 'month', 'year'].map((p) => (
|
|
||||||
<TouchableOpacity
|
|
||||||
key={p}
|
|
||||||
style={[styles.tab, period === p && styles.tabActive]}
|
|
||||||
onPress={() => setPeriod(p)}
|
|
||||||
>
|
|
||||||
<Text style={[styles.tabText, period === p && styles.tabTextActive]}>
|
|
||||||
{p === 'day' ? '일간' : p === 'month' ? '월간' : '연간'}
|
|
||||||
</Text>
|
|
||||||
</TouchableOpacity>
|
|
||||||
))}
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
|
|
||||||
const renderDateControl = () => (
|
|
||||||
<View style={styles.dateControl}>
|
|
||||||
<TouchableOpacity onPress={() => moveDate(-1)} style={styles.arrowButton}>
|
|
||||||
<Text style={styles.arrowText}>◀</Text>
|
|
||||||
</TouchableOpacity>
|
|
||||||
<Text style={styles.dateText}>{formatDateDisplay()}</Text>
|
|
||||||
<TouchableOpacity onPress={() => moveDate(1)} style={styles.arrowButton}>
|
|
||||||
<Text style={styles.arrowText}>▶</Text>
|
|
||||||
</TouchableOpacity>
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
|
|
||||||
const renderTable = () => (
|
|
||||||
<View style={styles.table}>
|
|
||||||
<View style={styles.tableHeader}>
|
|
||||||
<Text style={[styles.columnHeader, { flex: 2 }]}>발전소</Text>
|
|
||||||
<Text style={[styles.columnHeader, { flex: 1.5 }]}>발전량</Text>
|
|
||||||
<Text style={[styles.columnHeader, { flex: 1.5 }]}>용량</Text>
|
|
||||||
<Text style={[styles.columnHeader, { flex: 1.5 }]}>시간</Text>
|
|
||||||
</View>
|
|
||||||
{statsData.map((item, index) => (
|
|
||||||
<View key={item.plant_id} style={[styles.tableRow, index % 2 === 1 && styles.tableRowAlt]}>
|
|
||||||
<Text style={[styles.cell, { flex: 2, textAlign: 'left' }]}>{item.plant_name}</Text>
|
|
||||||
<Text style={[styles.cell, { flex: 1.5 }]}>{item.generation.toFixed(1)}</Text>
|
|
||||||
<Text style={[styles.cell, { flex: 1.5, color: '#9CA3AF' }]}>{item.capacity}</Text>
|
|
||||||
<Text style={[styles.cell, { flex: 1.5, fontWeight: 'bold', color: '#10B981' }]}>
|
|
||||||
{item.generation_hours.toFixed(2)}
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
))}
|
|
||||||
{statsData.length === 0 && (
|
|
||||||
<View style={styles.emptyRow}>
|
|
||||||
<Text style={styles.emptyText}>데이터가 없습니다</Text>
|
|
||||||
</View>
|
|
||||||
)}
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
|
|
||||||
const chartWidth = width - 40;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<SafeAreaView style={styles.container} edges={['top']}>
|
|
||||||
<View style={styles.header}>
|
|
||||||
<TouchableOpacity style={styles.backButton} onPress={() => navigation.goBack()}>
|
|
||||||
<Text style={styles.backButtonText}>← 뒤로</Text>
|
|
||||||
</TouchableOpacity>
|
|
||||||
<Text style={styles.headerTitle}>전체 발전소 비교</Text>
|
|
||||||
<View style={{ width: 60 }} />
|
|
||||||
</View>
|
|
||||||
|
|
||||||
<ScrollView style={styles.content} contentContainerStyle={styles.scrollContent}>
|
|
||||||
{renderDateControl()}
|
|
||||||
{renderTabs()}
|
|
||||||
|
|
||||||
{loading ? (
|
|
||||||
<View style={styles.loadingContainer}>
|
|
||||||
<ActivityIndicator size="large" color="#3B82F6" />
|
|
||||||
<Text style={styles.loadingText}>데이터 불러오는 중...</Text>
|
|
||||||
</View>
|
|
||||||
) : error ? (
|
|
||||||
<View style={styles.errorContainer}>
|
|
||||||
<Text style={styles.errorText}>데이터 조회 실패</Text>
|
|
||||||
<Text style={styles.errorDetail}>{error}</Text>
|
|
||||||
<TouchableOpacity style={styles.retryButton} onPress={fetchStats}>
|
|
||||||
<Text style={styles.retryButtonText}>다시 시도</Text>
|
|
||||||
</TouchableOpacity>
|
|
||||||
</View>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<View style={styles.chartSection}>
|
|
||||||
<Text style={styles.sectionTitle}>📊 발전량 비교 (kWh)</Text>
|
|
||||||
<View style={styles.chartContainer}>
|
|
||||||
{statsData.length > 0 ? (
|
|
||||||
<BarChart
|
|
||||||
data={chartData}
|
|
||||||
width={chartWidth}
|
|
||||||
height={200}
|
|
||||||
barWidth={20}
|
|
||||||
spacing={15}
|
|
||||||
barBorderRadius={4}
|
|
||||||
frontColor="#3B82F6"
|
|
||||||
yAxisThickness={0}
|
|
||||||
xAxisThickness={1}
|
|
||||||
yAxisTextStyle={{ color: '#9CA3AF', fontSize: 10 }}
|
|
||||||
xAxisLabelTextStyle={{ color: '#6B7280', fontSize: 10 }}
|
|
||||||
hideRules
|
|
||||||
showValuesAsTopLabel
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<Text style={styles.noDataText}>표시할 데이터가 없습니다.</Text>
|
|
||||||
)}
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
|
|
||||||
<View style={styles.tableSection}>
|
|
||||||
<View style={styles.tableHeaderRow}>
|
|
||||||
<Text style={styles.sectionTitle}>📋 상세 현황</Text>
|
|
||||||
<Text style={styles.unitText}>시간 = 발전량 / 설비용량 {period === 'year' ? '/ 365' : ''}</Text>
|
|
||||||
</View>
|
|
||||||
{renderTable()}
|
|
||||||
</View>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</ScrollView>
|
|
||||||
</SafeAreaView>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
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,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
Loading…
Reference in New Issue
Block a user