import React, { useState, useRef } from 'react';
import {
FileText,
Upload,
Search,
File,
Loader2,
CheckCircle,
Link,
Plus,
MoreVertical,
Trash2,
Download,
X,
Camera,
Image as ImageIcon,
Palette,
ChevronDown,
FileSpreadsheet
} from 'lucide-react';
import { jsPDF } from 'jspdf';
import { Document, Appliance, BrandProfile, PaintPaletteSlot } from '../types';
import { parseDocument } from '../geminiService';
import { isDarkColor } from '../constants';
interface DocumentVaultProps {
docs: Document[];
setDocs: React.Dispatch
>;
appliances: Appliance[];
brand: BrandProfile;
interiorPaint: PaintPaletteSlot[];
exteriorPaint: PaintPaletteSlot[];
onUpdatePaint: (type: 'Interior' | 'Exterior', updated: PaintPaletteSlot[]) => void;
}
const DocumentVault: React.FC
= ({
docs,
setDocs,
appliances,
brand,
interiorPaint,
exteriorPaint,
onUpdatePaint
}) => {
const [isUploading, setIsUploading] = useState(false);
const [searchTerm, setSearchTerm] = useState('');
const [selectedType, setSelectedType] = useState
('All');
const [activeMenu, setActiveMenu] = useState
(null);
const [showExportMenu, setShowExportMenu] = useState(false);
const [paintPaletteView, setPaintPaletteView] = useState<'Interior' | 'Exterior'>('Interior');
const [editingSlot, setEditingSlot] = useState<{ type: 'Interior' | 'Exterior', index: number } | null>(null);
const isDark = isDarkColor(brand.backgroundColor);
const headingColor = isDark ? 'text-white' : 'text-gray-800';
const subtextColor = isDark ? 'text-gray-300' : 'text-gray-500';
const fileInputRef = useRef
(null);
const paintPhotoInputRef = useRef
(null);
const handleUpload = async (e: React.ChangeEvent
) => {
const file = e.target.files?.[0];
if (!file) return;
setIsUploading(true);
const reader = new FileReader();
reader.onloadend = async () => {
const base64 = (reader.result as string).split(',')[1];
const mimeType = file.type || 'image/jpeg';
try {
const parsed = await parseDocument(base64, mimeType);
const newDoc: Document = {
id: `doc-${Date.now()}`,
name: file.name,
type: (parsed.type || 'Other') as any,
uploadDate: new Date().toISOString(),
fileUrl: URL.createObjectURL(file),
parsedData: parsed
};
setDocs(prev => [newDoc, ...prev]);
} catch (err) {
alert("Upload failed. Please try again.");
} finally {
setIsUploading(false);
if (fileInputRef.current) fileInputRef.current.value = '';
}
};
reader.readAsDataURL(file);
};
const handleDelete = (id: string, fileUrl: string) => {
if (confirm("Permanently delete this document from your vault?")) {
if (fileUrl.startsWith('blob:')) {
URL.revokeObjectURL(fileUrl);
}
setDocs(prev => prev.filter(doc => doc.id !== id));
setActiveMenu(null);
}
};
const handleUpdateSlotPhoto = (e: React.ChangeEvent
) => {
const file = e.target.files?.[0];
if (!file || !editingSlot) return;
const reader = new FileReader();
reader.onloadend = () => {
const { type, index } = editingSlot;
const palette = type === 'Interior' ? [...interiorPaint] : [...exteriorPaint];
palette[index] = { ...palette[index], photoUrl: reader.result as string };
onUpdatePaint(type, palette);
};
reader.readAsDataURL(file);
};
const updateSlotField = (field: 'name' | 'color', value: string) => {
if (!editingSlot) return;
const { type, index } = editingSlot;
const palette = type === 'Interior' ? [...interiorPaint] : [...exteriorPaint];
palette[index] = { ...palette[index], [field]: value };
onUpdatePaint(type, palette);
};
const filteredDocs = docs.filter(doc => {
const matchesSearch = doc.name.toLowerCase().includes(searchTerm.toLowerCase());
const matchesType = selectedType === 'All' || doc.type === selectedType;
return matchesSearch && matchesType;
});
const exportToCSV = () => {
let csvContent = "data:text/csv;charset=utf-8,";
csvContent += "CasaKeeper Branded Report\n";
csvContent += `Company: ${brand.companyName}\n`;
csvContent += `Generated: ${new Date().toLocaleString()}\n\n`;
csvContent += "APPLIANCE & SYSTEM INVENTORY\n";
csvContent += "Type,Name,Manufacturer,Model,Serial,Category,Location,Installation Date\n";
appliances.forEach(app => {
csvContent += `Appliance,"${app.name}","${app.manufacturer}","${app.modelNumber}","${app.serialNumber}","${app.category}","${app.location}","${app.installationDate}"\n`;
});
csvContent += "\nPAINT PALETTE (INTERIOR)\n";
csvContent += "Slot,Color Name,Hex Code\n";
interiorPaint.forEach(p => {
csvContent += `"${p.label}","${p.name}","${p.color}"\n`;
});
csvContent += "\nPAINT PALETTE (EXTERIOR)\n";
csvContent += "Slot,Color Name,Hex Code\n";
exteriorPaint.forEach(p => {
csvContent += `"${p.label}","${p.name}","${p.color}"\n`;
});
const encodedUri = encodeURI(csvContent);
const link = window.document.createElement("a");
link.setAttribute("href", encodedUri);
link.setAttribute("download", `CasaKeeper_Report_${new Date().toISOString().split('T')[0]}.csv`);
window.document.body.appendChild(link);
link.click();
window.document.body.removeChild(link);
setShowExportMenu(false);
};
const exportToPDF = () => {
try {
const doc = new jsPDF();
const timestamp = new Date().toLocaleString();
const dateStr = new Date().toISOString().split('T')[0];
// Branded Header
doc.setFontSize(22);
doc.setTextColor(brand.primaryColor);
doc.text(brand.companyName, 20, 20);
doc.setFontSize(10);
doc.setTextColor(100);
doc.text(brand.tagline, 20, 27);
doc.text(`Generated: ${timestamp}`, 20, 32);
doc.setDrawColor(brand.primaryColor);
doc.line(20, 35, 190, 35);
// Section 1: Appliances
doc.setFontSize(16);
doc.setTextColor(0);
doc.text("Appliance & System Inventory", 20, 45);
let y = 55;
doc.setFontSize(10);
appliances.forEach((app, idx) => {
if (y > 270) {
doc.addPage();
y = 20;
}
doc.setFont("helvetica", "bold");
doc.text(`${idx + 1}. ${app.name}`, 20, y);
doc.setFont("helvetica", "normal");
doc.text(`Manufacturer: ${app.manufacturer} | Model: ${app.modelNumber}`, 25, y + 5);
doc.text(`Serial: ${app.serialNumber} | Installed: ${app.installationDate}`, 25, y + 10);
y += 18;
});
// Section 2: Paint Palette
if (y > 230) {
doc.addPage();
y = 20;
} else {
y += 10;
}
doc.setFontSize(16);
doc.text("Property Paint Palette", 20, y);
y += 10;
doc.setFontSize(12);
doc.setFont("helvetica", "bold");
doc.text("Interior Colors", 20, y);
y += 8;
doc.setFontSize(10);
doc.setFont("helvetica", "normal");
interiorPaint.forEach(p => {
doc.text(`${p.label}: ${p.name} (${p.color})`, 25, y);
y += 6;
});
y += 5;
doc.setFontSize(12);
doc.setFont("helvetica", "bold");
doc.text("Exterior Colors", 20, y);
y += 8;
doc.setFontSize(10);
doc.setFont("helvetica", "normal");
exteriorPaint.forEach(p => {
doc.text(`${p.label}: ${p.name} (${p.color})`, 25, y);
y += 6;
});
// Footer
doc.setFontSize(8);
doc.setTextColor(150);
doc.text(brand.footerMessage, 20, 285);
doc.save(`CasaKeeper_Report_${dateStr}.pdf`);
setShowExportMenu(false);
} catch (error) {
console.error("PDF Generation Error:", error);
alert("There was an error generating the PDF. Try CSV export instead.");
}
};
const currentPalette = paintPaletteView === 'Interior' ? interiorPaint : exteriorPaint;
return (
{/* Search and Filters */}
setSearchTerm(e.target.value)}
/>
{/* Document Table */}
{/* Paint Color Palette */}
)}
{/* Branded Report Section */}
Branded Report
{activeMenu && (
);
};
export default DocumentVault;
Document Vault
Secure storage for manuals, warranties, and paint colors.
Document Name
Type
Date Uploaded
Linked Item
Action
{filteredDocs.length > 0 ? (
filteredDocs.map((doc) => (
))
) : (
)}
{doc.name}
{doc.parsedData && (
{doc.type}
{new Date(doc.uploadDate).toLocaleDateString()}
{doc.linkedApplianceId ? (
{appliances.find(a => a.id === doc.linkedApplianceId)?.name || 'Linked'}
) : (
)}
{activeMenu === doc.id && (
e.stopPropagation()}
>
)}
No documents stored.
Paint Color Palette
Property Asset Inventory
{['Interior', 'Exterior'].map((view) => (
))}
{currentPalette.map((paint, i) => (
{/* Edit Modal */}
{editingSlot && (
setEditingSlot({ type: paintPaletteView, index: i })}
className="group bg-blue-50/30 rounded-2xl p-4 text-center shadow-sm border border-blue-100/50 hover:bg-blue-50 transition-all cursor-pointer relative"
>
{paint.photoUrl && (
)}
))}
{paint.photoUrl && (
)}
{paint.label}
{paint.name || 'Empty'}
{paint.color || '#------'}
Edit {editingSlot.type} Color
{currentPalette[editingSlot.index].label} Slot
updateSlotField('name', e.target.value)}
className="w-full p-3 bg-blue-50/50 border border-blue-100 rounded-xl outline-none focus:ring-2"
style={{ '--tw-ring-color': brand.primaryColor } as any}
/>
updateSlotField('color', e.target.value)}
className="w-12 h-12 rounded-lg border-none p-0 cursor-pointer"
/>
updateSlotField('color', e.target.value)}
className="flex-1 p-3 bg-blue-50/50 border border-blue-100 rounded-xl font-mono text-sm"
/>
paintPhotoInputRef.current?.click()}
className="w-full h-40 border-2 border-dashed border-blue-100 bg-blue-50/20 rounded-2xl flex flex-col items-center justify-center cursor-pointer hover:bg-blue-50 transition-colors overflow-hidden"
>
{currentPalette[editingSlot.index].photoUrl ? (
) : (
<>
Upload Paint Can / QR Code
)}Branded Report
{showExportMenu && (
)}
Create a professional branded insurance or home sale report. Includes all appliance data, service history, and your property paint palette.
Recent Report
Generated {new Date().toLocaleDateString()}
setActiveMenu(null)}>
)}
{showExportMenu && (
setShowExportMenu(false)}>
)}