smart-report / index.html
soiler01's picture
Aplikasi pembuat laporan yang dilengkapi dengan upload file, foto, dan dapat meringkas dengan keahlian yang luar biasa - Initial Deployment
76c0c40 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Smart Report Creator</title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
/* Custom CSS for elements that need more precise control */
.file-upload-input {
position: absolute;
width: 0.1px;
height: 0.1px;
opacity: 0;
overflow: hidden;
z-index: -1;
}
.file-upload-label {
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.3s;
}
.file-upload-label:hover {
transform: translateY(-2px);
}
.preview-image {
transition: all 0.3s;
}
.preview-image:hover {
transform: scale(1.03);
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.2);
}
#videoElement {
transform: scaleX(-1); /* Mirror the camera preview */
}
.summary-loading {
position: relative;
overflow: hidden;
}
.summary-loading::after {
content: "";
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255,255,255,0.7), transparent);
animation: loading 1.5s infinite;
}
@keyframes loading {
0% { transform: translateX(-100%); }
100% { transform: translateX(100%); }
}
/* Custom scrollbar */
::-webkit-scrollbar {
width: 8px;
}
::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 10px;
}
::-webkit-scrollbar-thumb {
background: #888;
border-radius: 10px;
}
::-webkit-scrollbar-thumb:hover {
background: #555;
}
</style>
</head>
<body class="bg-gray-50 min-h-screen">
<div class="container mx-auto px-4 py-8">
<!-- Header -->
<header class="mb-8 text-center">
<h1 class="text-4xl font-bold text-indigo-700 mb-2">Smart Report Creator</h1>
<p class="text-gray-600">Create professional reports with ease - upload files, capture photos, and get AI-powered summaries</p>
</header>
<!-- Main Content -->
<div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
<!-- Left Column - Report Form -->
<div class="lg:col-span-2 bg-white rounded-xl shadow-md overflow-hidden">
<div class="p-6">
<h2 class="text-2xl font-semibold text-gray-800 mb-6">Create New Report</h2>
<!-- Report Title -->
<div class="mb-6">
<label for="reportTitle" class="block text-sm font-medium text-gray-700 mb-2">Report Title</label>
<input type="text" id="reportTitle" placeholder="Enter report title"
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 transition">
</div>
<!-- Report Content -->
<div class="mb-6">
<label for="reportContent" class="block text-sm font-medium text-gray-700 mb-2">Report Content</label>
<textarea id="reportContent" rows="8" placeholder="Write your report content here..."
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 transition"></textarea>
</div>
<!-- File Upload Section -->
<div class="mb-6">
<label class="block text-sm font-medium text-gray-700 mb-2">Attachments</label>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
<!-- File Upload -->
<div>
<input type="file" id="fileUpload" class="file-upload-input" multiple>
<label for="fileUpload" class="file-upload-label bg-indigo-50 text-indigo-700 px-4 py-8 rounded-lg border-2 border-dashed border-indigo-300">
<div class="text-center">
<i class="fas fa-file-upload text-3xl mb-2"></i>
<p class="font-medium">Upload Files</p>
<p class="text-xs text-gray-500 mt-1">PDF, Word, Excel, etc.</p>
</div>
</label>
</div>
<!-- Photo Capture -->
<div>
<button id="openCameraBtn" class="file-upload-label w-full bg-blue-50 text-blue-700 px-4 py-8 rounded-lg border-2 border-dashed border-blue-300">
<div class="text-center">
<i class="fas fa-camera text-3xl mb-2"></i>
<p class="font-medium">Capture Photo</p>
<p class="text-xs text-gray-500 mt-1">Use your camera</p>
</div>
</button>
</div>
</div>
<!-- Preview Area -->
<div id="previewArea" class="mt-4 grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 gap-3 hidden">
<!-- Preview items will be added here dynamically -->
</div>
</div>
<!-- Submit Button -->
<div class="flex justify-end">
<button id="generateSummaryBtn" class="bg-indigo-600 hover:bg-indigo-700 text-white px-6 py-2 rounded-lg font-medium flex items-center transition">
<i class="fas fa-magic mr-2"></i> Generate Summary
</button>
</div>
</div>
</div>
<!-- Right Column - Summary and Actions -->
<div class="space-y-6">
<!-- AI Summary Card -->
<div class="bg-white rounded-xl shadow-md overflow-hidden">
<div class="bg-indigo-600 px-4 py-3">
<h3 class="text-white font-medium flex items-center">
<i class="fas fa-robot mr-2"></i> AI Summary
</h3>
</div>
<div class="p-4">
<div id="summaryContent" class="text-gray-700 min-h-40 summary-loading hidden">
<!-- Summary will appear here -->
</div>
<div id="emptySummary" class="text-gray-500 text-center py-8">
<i class="fas fa-comment-alt text-3xl mb-2 opacity-30"></i>
<p>Your AI-generated summary will appear here</p>
</div>
</div>
</div>
<!-- Quick Actions -->
<div class="bg-white rounded-xl shadow-md overflow-hidden">
<div class="bg-gray-800 px-4 py-3">
<h3 class="text-white font-medium flex items-center">
<i class="fas fa-bolt mr-2"></i> Quick Actions
</h3>
</div>
<div class="p-4 grid grid-cols-2 gap-3">
<button class="bg-green-50 text-green-700 px-3 py-2 rounded-lg flex items-center justify-center text-sm font-medium">
<i class="fas fa-save mr-2"></i> Save Draft
</button>
<button class="bg-purple-50 text-purple-700 px-3 py-2 rounded-lg flex items-center justify-center text-sm font-medium">
<i class="fas fa-share-alt mr-2"></i> Share
</button>
<button class="bg-yellow-50 text-yellow-700 px-3 py-2 rounded-lg flex items-center justify-center text-sm font-medium">
<i class="fas fa-print mr-2"></i> Print
</button>
<button class="bg-red-50 text-red-700 px-3 py-2 rounded-lg flex items-center justify-center text-sm font-medium">
<i class="fas fa-file-pdf mr-2"></i> Export PDF
</button>
</div>
</div>
<!-- Recent Reports -->
<div class="bg-white rounded-xl shadow-md overflow-hidden">
<div class="bg-gray-100 px-4 py-3 border-b">
<h3 class="text-gray-800 font-medium flex items-center">
<i class="fas fa-history mr-2"></i> Recent Reports
</h3>
</div>
<div class="p-4 space-y-3">
<div class="flex items-start">
<div class="bg-indigo-100 text-indigo-800 p-2 rounded-lg mr-3">
<i class="fas fa-file-alt"></i>
</div>
<div>
<p class="font-medium text-sm">Quarterly Sales Report</p>
<p class="text-xs text-gray-500">Yesterday, 3:45 PM</p>
</div>
</div>
<div class="flex items-start">
<div class="bg-blue-100 text-blue-800 p-2 rounded-lg mr-3">
<i class="fas fa-chart-line"></i>
</div>
<div>
<p class="font-medium text-sm">Marketing Analysis</p>
<p class="text-xs text-gray-500">Monday, 10:20 AM</p>
</div>
</div>
<div class="flex items-start">
<div class="bg-green-100 text-green-800 p-2 rounded-lg mr-3">
<i class="fas fa-clipboard-check"></i>
</div>
<div>
<p class="font-medium text-sm">Project Status Update</p>
<p class="text-xs text-gray-500">Last week</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Camera Modal -->
<div id="cameraModal" class="fixed inset-0 bg-black bg-opacity-75 flex items-center justify-center z-50 hidden">
<div class="bg-white rounded-lg overflow-hidden w-full max-w-md">
<div class="bg-gray-800 px-4 py-3 flex justify-between items-center">
<h3 class="text-white font-medium">Capture Photo</h3>
<button id="closeCameraBtn" class="text-white hover:text-gray-300">
<i class="fas fa-times"></i>
</button>
</div>
<div class="p-4">
<div class="relative bg-gray-200 rounded-lg overflow-hidden mb-4" style="padding-top: 56.25%;">
<video id="videoElement" autoplay playsinline class="absolute top-0 left-0 w-full h-full object-cover"></video>
<canvas id="canvasElement" class="hidden"></canvas>
</div>
<div class="flex justify-center">
<button id="captureBtn" class="bg-red-500 hover:bg-red-600 text-white p-3 rounded-full mx-2">
<i class="fas fa-camera text-xl"></i>
</button>
<button id="retakeBtn" class="bg-gray-500 hover:bg-gray-600 text-white px-4 py-2 rounded-lg hidden">
Retake
</button>
<button id="usePhotoBtn" class="bg-green-500 hover:bg-green-600 text-white px-4 py-2 rounded-lg ml-2 hidden">
Use Photo
</button>
</div>
</div>
</div>
</div>
<!-- Success Toast -->
<div id="successToast" class="fixed bottom-4 right-4 bg-green-500 text-white px-6 py-3 rounded-lg shadow-lg flex items-center transform translate-y-10 opacity-0 transition-all duration-300 z-50">
<i class="fas fa-check-circle mr-2"></i>
<span>Report saved successfully!</span>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
// DOM Elements
const fileUploadInput = document.getElementById('fileUpload');
const previewArea = document.getElementById('previewArea');
const openCameraBtn = document.getElementById('openCameraBtn');
const cameraModal = document.getElementById('cameraModal');
const closeCameraBtn = document.getElementById('closeCameraBtn');
const videoElement = document.getElementById('videoElement');
const canvasElement = document.getElementById('canvasElement');
const captureBtn = document.getElementById('captureBtn');
const retakeBtn = document.getElementById('retakeBtn');
const usePhotoBtn = document.getElementById('usePhotoBtn');
const generateSummaryBtn = document.getElementById('generateSummaryBtn');
const summaryContent = document.getElementById('summaryContent');
const emptySummary = document.getElementById('emptySummary');
const successToast = document.getElementById('successToast');
let stream = null;
let capturedImage = null;
// File Upload Handling
fileUploadInput.addEventListener('change', function(e) {
const files = e.target.files;
if (files.length > 0) {
previewArea.classList.remove('hidden');
for (let i = 0; i < files.length; i++) {
const file = files[i];
const reader = new FileReader();
reader.onload = function(e) {
const previewItem = document.createElement('div');
previewItem.className = 'preview-item bg-gray-100 rounded-lg overflow-hidden relative';
let previewContent = '';
let iconClass = 'fas fa-file';
let bgClass = 'bg-gray-200';
if (file.type.startsWith('image/')) {
previewContent = `<img src="${e.target.result}" alt="Preview" class="w-full h-24 object-cover preview-image">`;
iconClass = 'fas fa-image';
bgClass = 'bg-blue-200';
} else if (file.type.includes('pdf')) {
previewContent = `<div class="w-full h-24 flex items-center justify-center ${bgClass}">
<i class="${iconClass} text-3xl text-gray-600"></i>
</div>`;
iconClass = 'fas fa-file-pdf';
bgClass = 'bg-red-200';
} else if (file.type.includes('word') || file.type.includes('document')) {
previewContent = `<div class="w-full h-24 flex items-center justify-center ${bgClass}">
<i class="${iconClass} text-3xl text-gray-600"></i>
</div>`;
iconClass = 'fas fa-file-word';
bgClass = 'bg-blue-200';
} else if (file.type.includes('excel') || file.type.includes('spreadsheet')) {
previewContent = `<div class="w-full h-24 flex items-center justify-center ${bgClass}">
<i class="${iconClass} text-3xl text-gray-600"></i>
</div>`;
iconClass = 'fas fa-file-excel';
bgClass = 'bg-green-200';
} else {
previewContent = `<div class="w-full h-24 flex items-center justify-center ${bgClass}">
<i class="${iconClass} text-3xl text-gray-600"></i>
</div>`;
}
previewItem.innerHTML = `
${previewContent}
<div class="p-2">
<p class="text-xs font-medium truncate">${file.name}</p>
<p class="text-xs text-gray-500">${formatFileSize(file.size)}</p>
</div>
<button class="absolute top-1 right-1 bg-white bg-opacity-80 rounded-full p-1 text-xs hover:bg-red-100 hover:text-red-600 transition">
<i class="fas fa-times"></i>
</button>
`;
previewArea.appendChild(previewItem);
// Add click event for remove button
const removeBtn = previewItem.querySelector('button');
removeBtn.addEventListener('click', function() {
previewItem.remove();
if (previewArea.children.length === 0) {
previewArea.classList.add('hidden');
}
});
};
reader.readAsDataURL(file);
}
}
});
// Camera Modal Handling
openCameraBtn.addEventListener('click', openCameraModal);
closeCameraBtn.addEventListener('click', closeCameraModal);
function openCameraModal() {
cameraModal.classList.remove('hidden');
startCamera();
}
function closeCameraModal() {
cameraModal.classList.add('hidden');
stopCamera();
resetCameraUI();
}
// Camera Functions
async function startCamera() {
try {
stream = await navigator.mediaDevices.getUserMedia({
video: {
width: { ideal: 1280 },
height: { ideal: 720 },
facingMode: 'environment'
}
});
videoElement.srcObject = stream;
} catch (err) {
console.error("Error accessing camera: ", err);
alert("Could not access the camera. Please make sure you've granted camera permissions.");
}
}
function stopCamera() {
if (stream) {
stream.getTracks().forEach(track => track.stop());
stream = null;
}
}
// Capture Photo
captureBtn.addEventListener('click', function() {
const videoWidth = videoElement.videoWidth;
const videoHeight = videoElement.videoHeight;
canvasElement.width = videoWidth;
canvasElement.height = videoHeight;
const ctx = canvasElement.getContext('2d');
ctx.drawImage(videoElement, 0, 0, videoWidth, videoHeight);
capturedImage = canvasElement.toDataURL('image/jpeg');
// Update UI
videoElement.classList.add('hidden');
canvasElement.classList.remove('hidden');
captureBtn.classList.add('hidden');
retakeBtn.classList.remove('hidden');
usePhotoBtn.classList.remove('hidden');
});
// Retake Photo
retakeBtn.addEventListener('click', function() {
videoElement.classList.remove('hidden');
canvasElement.classList.add('hidden');
captureBtn.classList.remove('hidden');
retakeBtn.classList.add('hidden');
usePhotoBtn.classList.add('hidden');
});
// Use Photo
usePhotoBtn.addEventListener('click', function() {
if (capturedImage) {
previewArea.classList.remove('hidden');
const previewItem = document.createElement('div');
previewItem.className = 'preview-item bg-gray-100 rounded-lg overflow-hidden relative';
previewItem.innerHTML = `
<img src="${capturedImage}" alt="Captured Photo" class="w-full h-24 object-cover preview-image">
<div class="p-2">
<p class="text-xs font-medium">Photo ${new Date().toLocaleTimeString()}</p>
<p class="text-xs text-gray-500">JPEG Image</p>
</div>
<button class="absolute top-1 right-1 bg-white bg-opacity-80 rounded-full p-1 text-xs hover:bg-red-100 hover:text-red-600 transition">
<i class="fas fa-times"></i>
</button>
`;
previewArea.appendChild(previewItem);
// Add click event for remove button
const removeBtn = previewItem.querySelector('button');
removeBtn.addEventListener('click', function() {
previewItem.remove();
if (previewArea.children.length === 0) {
previewArea.classList.add('hidden');
}
});
closeCameraModal();
}
});
function resetCameraUI() {
videoElement.classList.remove('hidden');
canvasElement.classList.add('hidden');
captureBtn.classList.remove('hidden');
retakeBtn.classList.add('hidden');
usePhotoBtn.classList.add('hidden');
capturedImage = null;
}
// Generate Summary
generateSummaryBtn.addEventListener('click', function() {
const reportTitle = document.getElementById('reportTitle').value;
const reportContent = document.getElementById('reportContent').value;
if (!reportTitle && !reportContent) {
alert('Please enter some content to generate a summary');
return;
}
// Show loading state
emptySummary.classList.add('hidden');
summaryContent.classList.remove('hidden');
summaryContent.innerHTML = '<div class="animate-pulse space-y-2"><div class="h-4 bg-gray-200 rounded w-full"></div><div class="h-4 bg-gray-200 rounded w-5/6"></div><div class="h-4 bg-gray-200 rounded w-4/6"></div><div class="h-4 bg-gray-200 rounded w-5/6"></div></div>';
// Simulate AI processing (in a real app, this would be an API call)
setTimeout(() => {
const mockSummary = generateMockSummary(reportTitle, reportContent);
summaryContent.innerHTML = mockSummary;
// Show success toast
showToast();
}, 2000);
});
function generateMockSummary(title, content) {
if (!content) content = "No content provided";
const sentences = content.split(/[.!?]+/).filter(s => s.trim().length > 0);
const summarySentences = sentences.slice(0, Math.min(3, sentences.length));
let summary = `<h4 class="font-medium text-lg mb-2">Summary of "${title || 'Untitled Report'}"</h4>`;
if (summarySentences.length > 0) {
summary += '<ul class="list-disc pl-5 space-y-1">';
summarySentences.forEach(s => {
summary += `<li>${s.trim()}.</li>`;
});
summary += '</ul>';
} else {
summary += '<p>This report appears to be very short. Consider adding more details for a comprehensive summary.</p>';
}
// Add key points
const words = content.toLowerCase().split(/\s+/).filter(w => w.length > 3);
const wordCount = {};
words.forEach(w => {
wordCount[w] = (wordCount[w] || 0) + 1;
});
const sortedWords = Object.keys(wordCount).sort((a, b) => wordCount[b] - wordCount[a]);
const keyWords = sortedWords.slice(0, 5).filter(w => !['that', 'this', 'with', 'your', 'they', 'have'].includes(w));
if (keyWords.length > 0) {
summary += `<div class="mt-4">
<h5 class="font-medium mb-1">Key Topics:</h5>
<div class="flex flex-wrap gap-2">`;
keyWords.forEach(w => {
summary += `<span class="bg-indigo-100 text-indigo-800 text-xs px-2 py-1 rounded">${w}</span>`;
});
summary += `</div></div>`;
}
// Add sentiment analysis
const positiveWords = ['good', 'great', 'excellent', 'success', 'improve', 'happy'];
const negativeWords = ['bad', 'poor', 'failure', 'problem', 'issue', 'unhappy'];
let positiveCount = 0;
let negativeCount = 0;
words.forEach(w => {
if (positiveWords.includes(w)) positiveCount++;
if (negativeWords.includes(w)) negativeCount++;
});
summary += `<div class="mt-4">
<h5 class="font-medium mb-1">Sentiment Analysis:</h5>
<div class="flex items-center">
<div class="w-full bg-gray-200 rounded-full h-2.5">
<div class="bg-green-500 h-2.5 rounded-full" style="width: ${Math.min(100, (positiveCount / (positiveCount + negativeCount + 1)) * 100)}%"></div>
</div>
<span class="ml-2 text-sm text-gray-600">${positiveCount > negativeCount ? 'Mostly Positive' : positiveCount < negativeCount ? 'Mostly Negative' : 'Neutral'}</span>
</div>
</div>`;
return summary;
}
// Helper Functions
function formatFileSize(bytes) {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
}
function showToast() {
successToast.classList.remove('translate-y-10', 'opacity-0');
successToast.classList.add('translate-y-0', 'opacity-100');
setTimeout(() => {
successToast.classList.remove('translate-y-0', 'opacity-100');
successToast.classList.add('translate-y-10', 'opacity-0');
}, 3000);
}
// Close modal when clicking outside
cameraModal.addEventListener('click', function(e) {
if (e.target === cameraModal) {
closeCameraModal();
}
});
});
</script>
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://deepsite.hf.co/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://deepsite.hf.co" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://deepsite.hf.co?remix=soiler01/smart-report" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
</html>