164 lines
5.3 KiB
React
164 lines
5.3 KiB
React
import { useRef } from 'react';
|
|
import { Download, Upload, Save, FolderOpen } from 'lucide-react';
|
|
import { formatWeekRange } from '../utils/dateUtils';
|
|
|
|
const ImportExport = ({ weekDays, timeEntries, customHomeowners, onImport }) => {
|
|
const fileInputRef = useRef(null);
|
|
|
|
const handleExport = () => {
|
|
try {
|
|
const exportData = {
|
|
version: '1.0',
|
|
exportDate: new Date().toISOString(),
|
|
weekRange: formatWeekRange(weekDays),
|
|
weekStart: weekDays[0].toISOString().split('T')[0],
|
|
weekEnd: weekDays[6].toISOString().split('T')[0],
|
|
timeEntries,
|
|
customHomeowners
|
|
};
|
|
|
|
const dataStr = JSON.stringify(exportData, null, 2);
|
|
const dataBlob = new Blob([dataStr], { type: 'application/json' });
|
|
|
|
const url = URL.createObjectURL(dataBlob);
|
|
const link = document.createElement('a');
|
|
link.href = url;
|
|
link.download = `Coastal_Timesheet_${formatWeekRange(weekDays).replace(/[^\w\s-]/g, '').replace(/\s+/g, '_')}.json`;
|
|
document.body.appendChild(link);
|
|
link.click();
|
|
document.body.removeChild(link);
|
|
URL.revokeObjectURL(url);
|
|
|
|
// Show success message
|
|
const notification = document.createElement('div');
|
|
notification.className = 'fixed top-4 left-1/2 transform -translate-x-1/2 bg-green-600 text-white px-4 py-2 rounded-md shadow-lg z-50';
|
|
notification.textContent = 'Timesheet exported successfully!';
|
|
document.body.appendChild(notification);
|
|
setTimeout(() => {
|
|
if (document.body.contains(notification)) {
|
|
document.body.removeChild(notification);
|
|
}
|
|
}, 3000);
|
|
|
|
} catch (error) {
|
|
console.error('Error exporting timesheet:', error);
|
|
alert('Error exporting timesheet. Please try again.');
|
|
}
|
|
};
|
|
|
|
const handleImportClick = () => {
|
|
fileInputRef.current?.click();
|
|
};
|
|
|
|
const handleFileSelect = (event) => {
|
|
const file = event.target.files?.[0];
|
|
if (!file) return;
|
|
|
|
if (file.type !== 'application/json') {
|
|
alert('Please select a valid JSON file.');
|
|
return;
|
|
}
|
|
|
|
const reader = new FileReader();
|
|
reader.onload = (e) => {
|
|
try {
|
|
const importData = JSON.parse(e.target.result);
|
|
|
|
// Validate the imported data structure
|
|
if (!importData.version || !importData.timeEntries) {
|
|
alert('Invalid timesheet file format.');
|
|
return;
|
|
}
|
|
|
|
// Validate data structure
|
|
const isValidStructure = Object.values(importData.timeEntries).every(dayEntries =>
|
|
Array.isArray(dayEntries) && dayEntries.every(entry =>
|
|
typeof entry === 'object' &&
|
|
'id' in entry &&
|
|
'workDescription' in entry &&
|
|
'hoursWorked' in entry &&
|
|
'homeowner' in entry
|
|
)
|
|
);
|
|
|
|
if (!isValidStructure) {
|
|
alert('Invalid timesheet data structure.');
|
|
return;
|
|
}
|
|
|
|
// Confirm import
|
|
const confirmImport = confirm(
|
|
`Import timesheet data from ${importData.weekRange || 'unknown week'}?\n\nThis will replace your current timesheet data for this week.`
|
|
);
|
|
|
|
if (confirmImport) {
|
|
onImport(importData);
|
|
|
|
// Show success message
|
|
const notification = document.createElement('div');
|
|
notification.className = 'fixed top-4 left-1/2 transform -translate-x-1/2 bg-blue-600 text-white px-4 py-2 rounded-md shadow-lg z-50';
|
|
notification.textContent = 'Timesheet imported successfully!';
|
|
document.body.appendChild(notification);
|
|
setTimeout(() => {
|
|
if (document.body.contains(notification)) {
|
|
document.body.removeChild(notification);
|
|
}
|
|
}, 3000);
|
|
}
|
|
|
|
} catch (error) {
|
|
console.error('Error importing timesheet:', error);
|
|
alert('Error reading timesheet file. Please check the file format.');
|
|
}
|
|
};
|
|
|
|
reader.readAsText(file);
|
|
|
|
// Reset file input
|
|
event.target.value = '';
|
|
};
|
|
|
|
const hasData = Object.values(timeEntries).some(dayEntries =>
|
|
dayEntries.some(entry => entry.workDescription || entry.hoursWorked || entry.homeowner)
|
|
);
|
|
|
|
return (
|
|
<div className="flex items-center gap-3">
|
|
{/* Export Button */}
|
|
<button
|
|
onClick={handleExport}
|
|
disabled={!hasData}
|
|
className={`flex items-center gap-2 px-4 py-2 rounded-lg font-medium transition-colors ${
|
|
hasData
|
|
? 'bg-blue-600 hover:bg-blue-700 text-white'
|
|
: 'bg-gray-300 dark:bg-gray-600 text-gray-500 dark:text-gray-400 cursor-not-allowed'
|
|
}`}
|
|
title={hasData ? 'Export timesheet data' : 'No data to export'}
|
|
>
|
|
<Save className="h-4 w-4" />
|
|
Export
|
|
</button>
|
|
|
|
{/* Import Button */}
|
|
<button
|
|
onClick={handleImportClick}
|
|
className="flex items-center gap-2 px-4 py-2 bg-purple-600 hover:bg-purple-700 text-white rounded-lg font-medium transition-colors"
|
|
title="Import timesheet data"
|
|
>
|
|
<FolderOpen className="h-4 w-4" />
|
|
Import
|
|
</button>
|
|
|
|
{/* Hidden File Input */}
|
|
<input
|
|
ref={fileInputRef}
|
|
type="file"
|
|
accept=".json"
|
|
onChange={handleFileSelect}
|
|
className="hidden"
|
|
/>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default ImportExport; |