Initial commit from gitea_uploader script
This commit is contained in:
@@ -0,0 +1,164 @@
|
||||
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;
|
||||
Reference in New Issue
Block a user