326 lines
11 KiB
JavaScript
326 lines
11 KiB
JavaScript
/**
|
|
* UploadModal.js - 과거 발전 데이터 엑셀 업로드 모달
|
|
*/
|
|
|
|
import React, { useState } from 'react';
|
|
// Force Update Check
|
|
import {
|
|
Modal,
|
|
View,
|
|
Text,
|
|
TouchableOpacity,
|
|
StyleSheet,
|
|
ActivityIndicator,
|
|
Alert,
|
|
} from 'react-native';
|
|
import * as DocumentPicker from 'expo-document-picker';
|
|
|
|
const API_URL = 'https://solorpower.dadot.net';
|
|
|
|
export default function UploadModal({ visible, onClose, plantId, onUploadSuccess }) {
|
|
const [uploading, setUploading] = useState(false);
|
|
const [selectedFile, setSelectedFile] = useState(null);
|
|
const [uploadType, setUploadType] = useState('daily'); // 'daily' | 'monthly'
|
|
|
|
// 파일 선택
|
|
const pickDocument = async () => {
|
|
try {
|
|
const result = await DocumentPicker.getDocumentAsync({
|
|
type: [
|
|
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', // .xlsx
|
|
'application/vnd.ms-excel', // .xls
|
|
],
|
|
copyToCacheDirectory: true,
|
|
});
|
|
|
|
if (!result.canceled && result.assets && result.assets.length > 0) {
|
|
setSelectedFile(result.assets[0]);
|
|
}
|
|
} catch (error) {
|
|
Alert.alert('오류', '파일 선택 중 오류가 발생했습니다.');
|
|
}
|
|
};
|
|
|
|
// 업로드 실행
|
|
const handleUpload = async () => {
|
|
if (!selectedFile) {
|
|
Alert.alert('알림', '먼저 파일을 선택해주세요.');
|
|
return;
|
|
}
|
|
|
|
if (!plantId) {
|
|
Alert.alert('알림', '발전소를 선택해주세요.');
|
|
return;
|
|
}
|
|
|
|
setUploading(true);
|
|
|
|
try {
|
|
const formData = new FormData();
|
|
|
|
// 파일 추가
|
|
formData.append('file', {
|
|
uri: selectedFile.uri,
|
|
name: selectedFile.name,
|
|
type: selectedFile.mimeType || 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
});
|
|
|
|
// uploadType에 따라 엔드포인트 분기
|
|
const endpoint = uploadType === 'daily'
|
|
? `/plants/${plantId}/upload`
|
|
: `/plants/${plantId}/upload/monthly`;
|
|
|
|
console.log(`🚀 Uploading ${uploadType} data`);
|
|
console.log(" URL:", `${API_URL}${endpoint}`);
|
|
|
|
const response = await fetch(`${API_URL}${endpoint}`, {
|
|
method: 'POST',
|
|
body: formData,
|
|
headers: {
|
|
'Content-Type': 'multipart/form-data',
|
|
},
|
|
});
|
|
|
|
const result = await response.json();
|
|
|
|
if (response.ok) {
|
|
Alert.alert('성공', result.message || '업로드가 완료되었습니다.');
|
|
setSelectedFile(null);
|
|
onUploadSuccess?.();
|
|
onClose();
|
|
} else {
|
|
Alert.alert('오류', result.detail || '업로드에 실패했습니다.');
|
|
}
|
|
} catch (error) {
|
|
Alert.alert('오류', `업로드 중 오류가 발생했습니다: ${error.message}`);
|
|
} finally {
|
|
setUploading(false);
|
|
}
|
|
};
|
|
|
|
// 모달 닫기
|
|
const handleClose = () => {
|
|
setSelectedFile(null);
|
|
setUploadType('daily');
|
|
onClose();
|
|
};
|
|
|
|
return (
|
|
<Modal
|
|
visible={visible}
|
|
animationType="slide"
|
|
transparent={true}
|
|
onRequestClose={handleClose}
|
|
>
|
|
<View style={styles.overlay}>
|
|
<View style={styles.modalContainer}>
|
|
{/* 헤더 */}
|
|
<View style={styles.header}>
|
|
<Text style={styles.title}>📂 과거 데이터 업로드</Text>
|
|
<TouchableOpacity onPress={handleClose} style={styles.closeButton}>
|
|
<Text style={styles.closeButtonText}>✕</Text>
|
|
</TouchableOpacity>
|
|
</View>
|
|
|
|
{/* 타입 선택 탭 */}
|
|
<View style={{ flexDirection: 'row', marginBottom: 16, backgroundColor: '#F3F4F6', borderRadius: 8, padding: 4 }}>
|
|
<TouchableOpacity
|
|
style={{
|
|
flex: 1,
|
|
paddingVertical: 8,
|
|
alignItems: 'center',
|
|
borderRadius: 6,
|
|
backgroundColor: uploadType === 'daily' ? '#FFFFFF' : 'transparent',
|
|
shadowColor: uploadType === 'daily' ? '#000' : 'transparent',
|
|
shadowOpacity: uploadType === 'daily' ? 0.1 : 0,
|
|
shadowRadius: 2,
|
|
elevation: uploadType === 'daily' ? 2 : 0,
|
|
}}
|
|
onPress={() => setUploadType('daily')}
|
|
>
|
|
<Text style={{ fontWeight: uploadType === 'daily' ? '600' : '400', color: '#374151' }}>일간 (Daily)</Text>
|
|
</TouchableOpacity>
|
|
<TouchableOpacity
|
|
style={{
|
|
flex: 1,
|
|
paddingVertical: 8,
|
|
alignItems: 'center',
|
|
borderRadius: 6,
|
|
backgroundColor: uploadType === 'monthly' ? '#FFFFFF' : 'transparent',
|
|
shadowColor: uploadType === 'monthly' ? '#000' : 'transparent',
|
|
shadowOpacity: uploadType === 'monthly' ? 0.1 : 0,
|
|
shadowRadius: 2,
|
|
elevation: uploadType === 'monthly' ? 2 : 0,
|
|
}}
|
|
onPress={() => setUploadType('monthly')}
|
|
>
|
|
<Text style={{ fontWeight: uploadType === 'monthly' ? '600' : '400', color: '#374151' }}>월간 (Monthly)</Text>
|
|
</TouchableOpacity>
|
|
</View>
|
|
|
|
{/* 안내 */}
|
|
<Text style={styles.description}>
|
|
{uploadType === 'daily'
|
|
? "필수 컬럼: date (날짜), generation (발전량) 또는 year, month, day, kwh"
|
|
: "필수 컬럼: year (연도), month (월), kwh (발전량)\n* 합계/평균 행 자동 제외"
|
|
}
|
|
</Text>
|
|
|
|
{/* 발전소 ID 표시 */}
|
|
<View style={styles.infoBox}>
|
|
<Text style={styles.infoLabel}>발전소 ID:</Text>
|
|
<Text style={styles.infoValue}>{plantId || '선택 안됨'}</Text>
|
|
</View>
|
|
|
|
{/* 파일 선택 */}
|
|
<TouchableOpacity style={styles.fileButton} onPress={pickDocument}>
|
|
<Text style={styles.fileButtonText}>
|
|
{selectedFile ? '📄 ' + selectedFile.name : '📁 엑셀 파일 선택 (.xlsx)'}
|
|
</Text>
|
|
</TouchableOpacity>
|
|
|
|
{/* 선택된 파일 정보 */}
|
|
{selectedFile && (
|
|
<View style={styles.fileInfo}>
|
|
<Text style={styles.fileInfoText}>
|
|
크기: {(selectedFile.size / 1024).toFixed(1)} KB
|
|
</Text>
|
|
</View>
|
|
)}
|
|
|
|
{/* 업로드 버튼 */}
|
|
<TouchableOpacity
|
|
style={[
|
|
styles.uploadButton,
|
|
(!selectedFile || uploading) && styles.uploadButtonDisabled,
|
|
]}
|
|
onPress={handleUpload}
|
|
disabled={!selectedFile || uploading}
|
|
>
|
|
{uploading ? (
|
|
<ActivityIndicator color="#FFFFFF" />
|
|
) : (
|
|
<Text style={styles.uploadButtonText}>
|
|
{uploadType === 'daily' ? '일간 데이터 업로드' : '월간 데이터 업로드'}
|
|
</Text>
|
|
)}
|
|
</TouchableOpacity>
|
|
|
|
{/* 취소 버튼 */}
|
|
<TouchableOpacity style={styles.cancelButton} onPress={handleClose}>
|
|
<Text style={styles.cancelButtonText}>취소</Text>
|
|
</TouchableOpacity>
|
|
</View>
|
|
</View>
|
|
</Modal>
|
|
);
|
|
}
|
|
|
|
const styles = StyleSheet.create({
|
|
overlay: {
|
|
flex: 1,
|
|
backgroundColor: 'rgba(0, 0, 0, 0.5)',
|
|
justifyContent: 'center',
|
|
alignItems: 'center',
|
|
padding: 20,
|
|
},
|
|
modalContainer: {
|
|
backgroundColor: '#FFFFFF',
|
|
borderRadius: 16,
|
|
padding: 24,
|
|
width: '100%',
|
|
maxWidth: 400,
|
|
shadowColor: '#000',
|
|
shadowOffset: { width: 0, height: 4 },
|
|
shadowOpacity: 0.25,
|
|
shadowRadius: 16,
|
|
elevation: 8,
|
|
},
|
|
header: {
|
|
flexDirection: 'row',
|
|
justifyContent: 'space-between',
|
|
alignItems: 'center',
|
|
marginBottom: 16,
|
|
},
|
|
title: {
|
|
fontSize: 20,
|
|
fontWeight: '700',
|
|
color: '#1F2937',
|
|
},
|
|
closeButton: {
|
|
padding: 4,
|
|
},
|
|
closeButtonText: {
|
|
fontSize: 20,
|
|
color: '#6B7280',
|
|
},
|
|
description: {
|
|
fontSize: 14,
|
|
color: '#6B7280',
|
|
marginBottom: 16,
|
|
lineHeight: 20,
|
|
},
|
|
infoBox: {
|
|
flexDirection: 'row',
|
|
backgroundColor: '#F3F4F6',
|
|
padding: 12,
|
|
borderRadius: 8,
|
|
marginBottom: 16,
|
|
},
|
|
infoLabel: {
|
|
fontSize: 14,
|
|
color: '#6B7280',
|
|
marginRight: 8,
|
|
},
|
|
infoValue: {
|
|
fontSize: 14,
|
|
fontWeight: '600',
|
|
color: '#1F2937',
|
|
},
|
|
fileButton: {
|
|
backgroundColor: '#E5E7EB',
|
|
padding: 16,
|
|
borderRadius: 8,
|
|
alignItems: 'center',
|
|
marginBottom: 8,
|
|
borderWidth: 2,
|
|
borderColor: '#D1D5DB',
|
|
borderStyle: 'dashed',
|
|
},
|
|
fileButtonText: {
|
|
fontSize: 16,
|
|
color: '#374151',
|
|
},
|
|
fileInfo: {
|
|
alignItems: 'center',
|
|
marginBottom: 16,
|
|
},
|
|
fileInfoText: {
|
|
fontSize: 12,
|
|
color: '#6B7280',
|
|
},
|
|
uploadButton: {
|
|
backgroundColor: '#3B82F6',
|
|
padding: 16,
|
|
borderRadius: 8,
|
|
alignItems: 'center',
|
|
marginBottom: 8,
|
|
},
|
|
uploadButtonDisabled: {
|
|
backgroundColor: '#9CA3AF',
|
|
},
|
|
uploadButtonText: {
|
|
fontSize: 16,
|
|
fontWeight: '600',
|
|
color: '#FFFFFF',
|
|
},
|
|
cancelButton: {
|
|
padding: 12,
|
|
alignItems: 'center',
|
|
},
|
|
cancelButtonText: {
|
|
fontSize: 14,
|
|
color: '#6B7280',
|
|
},
|
|
});
|