Initial commit from gitea_uploader script

This commit is contained in:
bizzle
2025-08-11 15:52:32 -04:00
commit c913946c08
32 changed files with 8813 additions and 0 deletions
+164
View File
@@ -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;