report-manager / index.html
sombochea's picture
Add 3 files
28cd06b verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Dynamic Report Manager</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>
.sidebar {
transition: all 0.3s ease;
}
.report-card:hover {
transform: translateY(-5px);
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.1);
}
.chart-container {
min-height: 300px;
}
.connection-status {
width: 12px;
height: 12px;
border-radius: 50%;
display: inline-block;
margin-right: 5px;
}
.connection-active {
background-color: #10B981;
}
.connection-inactive {
background-color: #EF4444;
}
.connection-pending {
background-color: #F59E0B;
}
.tab-content {
display: none;
}
.tab-content.active {
display: block;
}
.json-editor {
font-family: monospace;
min-height: 200px;
}
.draggable-item {
cursor: move;
}
</style>
</head>
<body class="bg-gray-50">
<div class="flex h-screen overflow-hidden">
<!-- Sidebar -->
<div class="sidebar bg-white w-64 border-r border-gray-200 flex flex-col">
<div class="p-4 border-b border-gray-200">
<h1 class="text-xl font-bold text-gray-800 flex items-center">
<i class="fas fa-chart-line text-blue-500 mr-2"></i>
Report Manager
</h1>
</div>
<div class="flex-1 overflow-y-auto">
<div class="p-4">
<button id="newReportBtn" class="w-full bg-blue-500 hover:bg-blue-600 text-white py-2 px-4 rounded-md flex items-center justify-center mb-4">
<i class="fas fa-plus mr-2"></i> New Report
</button>
<div class="mb-6">
<h3 class="text-sm font-semibold text-gray-500 uppercase tracking-wider mb-2">Reports</h3>
<div id="reportList" class="space-y-1">
<!-- Reports will be dynamically added here -->
</div>
</div>
<div class="mb-6">
<h3 class="text-sm font-semibold text-gray-500 uppercase tracking-wider mb-2">Connections</h3>
<div id="connectionList" class="space-y-1">
<!-- Connections will be dynamically added here -->
</div>
</div>
</div>
</div>
<div class="p-4 border-t border-gray-200">
<div class="flex items-center">
<img src="https://ui-avatars.com/api/?name=User+Name&background=random" alt="User" class="w-8 h-8 rounded-full mr-2">
<div>
<p class="text-sm font-medium text-gray-700">User Name</p>
<p class="text-xs text-gray-500">Admin</p>
</div>
</div>
</div>
</div>
<!-- Main Content -->
<div class="flex-1 overflow-auto">
<!-- Header -->
<header class="bg-white border-b border-gray-200 p-4 flex justify-between items-center">
<h2 id="currentReportTitle" class="text-lg font-semibold text-gray-800">Dashboard</h2>
<div class="flex space-x-2">
<button id="saveReportBtn" class="bg-green-500 hover:bg-green-600 text-white py-2 px-4 rounded-md flex items-center">
<i class="fas fa-save mr-2"></i> Save
</button>
<button id="exportReportBtn" class="bg-purple-500 hover:bg-purple-600 text-white py-2 px-4 rounded-md flex items-center">
<i class="fas fa-file-export mr-2"></i> Export
</button>
</div>
</header>
<!-- Report Content -->
<main class="p-6">
<div id="dashboardView" class="space-y-6">
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
<div class="bg-white rounded-lg shadow p-6">
<div class="flex items-center justify-between mb-4">
<h3 class="font-medium text-gray-700">Total Reports</h3>
<i class="fas fa-file-alt text-blue-500"></i>
</div>
<p class="text-3xl font-bold text-gray-800" id="totalReportsCount">0</p>
</div>
<div class="bg-white rounded-lg shadow p-6">
<div class="flex items-center justify-between mb-4">
<h3 class="font-medium text-gray-700">Active Connections</h3>
<i class="fas fa-plug text-green-500"></i>
</div>
<p class="text-3xl font-bold text-gray-800" id="activeConnectionsCount">0</p>
</div>
<div class="bg-white rounded-lg shadow p-6">
<div class="flex items-center justify-between mb-4">
<h3 class="font-medium text-gray-700">Recent Activity</h3>
<i class="fas fa-clock text-purple-500"></i>
</div>
<p class="text-3xl font-bold text-gray-800">Now</p>
</div>
</div>
<div class="bg-white rounded-lg shadow overflow-hidden">
<div class="p-4 border-b border-gray-200">
<h3 class="font-medium text-gray-700">Recent Reports</h3>
</div>
<div class="p-4">
<div id="recentReports" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
<!-- Recent reports will be added here -->
</div>
</div>
</div>
</div>
<!-- Report Editor (hidden by default) -->
<div id="reportEditorView" class="hidden">
<div class="bg-white rounded-lg shadow overflow-hidden mb-6">
<div class="border-b border-gray-200">
<nav class="flex -mb-px">
<button data-tab="settings" class="tab-button py-4 px-6 text-sm font-medium border-b-2 border-transparent hover:text-blue-500 hover:border-blue-300">Settings</button>
<button data-tab="data" class="tab-button py-4 px-6 text-sm font-medium border-b-2 border-transparent hover:text-blue-500 hover:border-blue-300">Data Sources</button>
<button data-tab="visualization" class="tab-button py-4 px-6 text-sm font-medium border-b-2 border-transparent hover:text-blue-500 hover:border-blue-300">Visualization</button>
<button data-tab="preview" class="tab-button py-4 px-6 text-sm font-medium border-b-2 border-transparent hover:text-blue-500 hover:border-blue-300">Preview</button>
</nav>
</div>
<div class="p-6">
<!-- Settings Tab -->
<div id="settings" class="tab-content active">
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<label for="reportName" class="block text-sm font-medium text-gray-700 mb-1">Report Name</label>
<input type="text" id="reportName" class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500">
</div>
<div>
<label for="reportCategory" class="block text-sm font-medium text-gray-700 mb-1">Category</label>
<select id="reportCategory" class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500">
<option value="sales">Sales</option>
<option value="marketing">Marketing</option>
<option value="finance">Finance</option>
<option value="operations">Operations</option>
<option value="custom">Custom</option>
</select>
</div>
<div>
<label for="reportDescription" class="block text-sm font-medium text-gray-700 mb-1">Description</label>
<textarea id="reportDescription" rows="3" class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500"></textarea>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Options</label>
<div class="space-y-2">
<div class="flex items-center">
<input id="autoRefresh" type="checkbox" class="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded">
<label for="autoRefresh" class="ml-2 block text-sm text-gray-700">Auto-refresh data</label>
</div>
<div class="flex items-center">
<input id="publicReport" type="checkbox" class="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded">
<label for="publicReport" class="ml-2 block text-sm text-gray-700">Make report public</label>
</div>
</div>
</div>
</div>
</div>
<!-- Data Sources Tab -->
<div id="data" class="tab-content">
<div class="mb-6">
<div class="flex justify-between items-center mb-4">
<h3 class="text-lg font-medium text-gray-800">Data Connections</h3>
<button id="addDataSourceBtn" class="bg-blue-500 hover:bg-blue-600 text-white py-1 px-3 rounded-md text-sm flex items-center">
<i class="fas fa-plus mr-1"></i> Add Data Source
</button>
</div>
<div id="dataSourceTabs" class="border-b border-gray-200 mb-4">
<nav class="flex -mb-px space-x-4">
<!-- Data source tabs will be added here dynamically -->
</nav>
</div>
<div id="dataSourceContent">
<!-- Data source content will be shown here -->
<div class="bg-gray-50 rounded-lg p-4 text-center text-gray-500">
<i class="fas fa-database text-3xl mb-2"></i>
<p>No data sources added yet</p>
</div>
</div>
</div>
</div>
<!-- Visualization Tab -->
<div id="visualization" class="tab-content">
<div class="grid grid-cols-1 lg:grid-cols-4 gap-6">
<div class="lg:col-span-1">
<div class="bg-gray-50 rounded-lg p-4">
<h3 class="font-medium text-gray-700 mb-3">Visual Components</h3>
<div class="space-y-2">
<div class="draggable-item bg-white p-3 rounded border border-gray-200 cursor-move flex items-center" data-type="chart">
<i class="fas fa-chart-bar text-blue-500 mr-2"></i>
<span>Chart</span>
</div>
<div class="draggable-item bg-white p-3 rounded border border-gray-200 cursor-move flex items-center" data-type="table">
<i class="fas fa-table text-green-500 mr-2"></i>
<span>Data Table</span>
</div>
<div class="draggable-item bg-white p-3 rounded border border-gray-200 cursor-move flex items-center" data-type="metric">
<i class="fas fa-hashtag text-purple-500 mr-2"></i>
<span>Metric</span>
</div>
<div class="draggable-item bg-white p-3 rounded border border-gray-200 cursor-move flex items-center" data-type="text">
<i class="fas fa-paragraph text-yellow-500 mr-2"></i>
<span>Text Block</span>
</div>
</div>
</div>
</div>
<div class="lg:col-span-3">
<div id="reportCanvas" class="bg-gray-50 rounded-lg p-4 min-h-[500px] border-2 border-dashed border-gray-300">
<p class="text-center text-gray-500">Drag and drop components here to build your report</p>
</div>
</div>
</div>
</div>
<!-- Preview Tab -->
<div id="preview" class="tab-content">
<div class="bg-white rounded-lg shadow p-6">
<div class="flex justify-between items-center mb-4">
<h3 class="text-lg font-medium text-gray-800">Report Preview</h3>
<button id="refreshPreviewBtn" class="bg-gray-100 hover:bg-gray-200 text-gray-700 py-1 px-3 rounded-md text-sm flex items-center">
<i class="fas fa-sync-alt mr-1"></i> Refresh
</button>
</div>
<div id="reportPreview" class="border border-gray-200 rounded-lg p-4">
<p class="text-center text-gray-500">Preview will appear here</p>
</div>
</div>
</div>
</div>
</div>
</div>
</main>
</div>
</div>
<!-- Modals -->
<!-- New Connection Modal -->
<div id="newConnectionModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden">
<div class="bg-white rounded-lg shadow-xl w-full max-w-md">
<div class="p-4 border-b border-gray-200">
<h3 class="text-lg font-medium text-gray-800">New Data Connection</h3>
</div>
<div class="p-4">
<div class="mb-4">
<label class="block text-sm font-medium text-gray-700 mb-1">Connection Type</label>
<div class="grid grid-cols-2 gap-2">
<button data-type="rest" class="connection-type-btn bg-white border border-gray-300 rounded-md p-3 hover:bg-gray-50 flex flex-col items-center">
<i class="fas fa-cloud text-blue-500 text-2xl mb-2"></i>
<span>REST API</span>
</button>
<button data-type="json" class="connection-type-btn bg-white border border-gray-300 rounded-md p-3 hover:bg-gray-50 flex flex-col items-center">
<i class="fas fa-file-code text-green-500 text-2xl mb-2"></i>
<span>JSON</span>
</button>
</div>
</div>
</div>
<div class="p-4 border-t border-gray-200 flex justify-end space-x-2">
<button id="cancelConnectionBtn" class="px-4 py-2 border border-gray-300 rounded-md text-sm font-medium text-gray-700 hover:bg-gray-50">Cancel</button>
</div>
</div>
</div>
<!-- REST API Connection Modal -->
<div id="restConnectionModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden">
<div class="bg-white rounded-lg shadow-xl w-full max-w-2xl">
<div class="p-4 border-b border-gray-200">
<h3 class="text-lg font-medium text-gray-800">REST API Connection</h3>
</div>
<div class="p-4">
<div class="grid grid-cols-1 gap-4">
<div>
<label for="restConnectionName" class="block text-sm font-medium text-gray-700 mb-1">Connection Name</label>
<input type="text" id="restConnectionName" class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500">
</div>
<div>
<label for="restEndpoint" class="block text-sm font-medium text-gray-700 mb-1">Endpoint URL</label>
<input type="text" id="restEndpoint" placeholder="https://api.example.com/data" class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500">
</div>
<div class="grid grid-cols-2 gap-4">
<div>
<label for="restMethod" class="block text-sm font-medium text-gray-700 mb-1">HTTP Method</label>
<select id="restMethod" class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500">
<option value="GET">GET</option>
<option value="POST">POST</option>
<option value="PUT">PUT</option>
<option value="DELETE">DELETE</option>
</select>
</div>
<div>
<label for="restAuthType" class="block text-sm font-medium text-gray-700 mb-1">Authentication</label>
<select id="restAuthType" class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500">
<option value="none">None</option>
<option value="basic">Basic Auth</option>
<option value="bearer">Bearer Token</option>
<option value="apiKey">API Key</option>
<option value="oauth">OAuth</option>
</select>
</div>
</div>
<!-- Authentication fields (shown based on selection) -->
<div id="authFields" class="hidden bg-gray-50 p-4 rounded-md">
<div id="basicAuthFields" class="hidden">
<div class="grid grid-cols-2 gap-4 mb-4">
<div>
<label for="restUsername" class="block text-sm font-medium text-gray-700 mb-1">Username</label>
<input type="text" id="restUsername" class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500">
</div>
<div>
<label for="restPassword" class="block text-sm font-medium text-gray-700 mb-1">Password</label>
<input type="password" id="restPassword" class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500">
</div>
</div>
</div>
<div id="bearerAuthFields" class="hidden">
<div>
<label for="restToken" class="block text-sm font-medium text-gray-700 mb-1">Bearer Token</label>
<input type="text" id="restToken" class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500">
</div>
</div>
<div id="apiKeyAuthFields" class="hidden">
<div class="grid grid-cols-2 gap-4 mb-4">
<div>
<label for="restApiKey" class="block text-sm font-medium text-gray-700 mb-1">API Key</label>
<input type="text" id="restApiKey" class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500">
</div>
<div>
<label for="restApiKeyLocation" class="block text-sm font-medium text-gray-700 mb-1">Key Location</label>
<select id="restApiKeyLocation" class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500">
<option value="header">Header</option>
<option value="query">Query Parameter</option>
</select>
</div>
</div>
<div>
<label for="restApiKeyName" class="block text-sm font-medium text-gray-700 mb-1">Key Name</label>
<input type="text" id="restApiKeyName" value="X-API-KEY" class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500">
</div>
</div>
</div>
<div>
<label for="restHeaders" class="block text-sm font-medium text-gray-700 mb-1">Headers (JSON)</label>
<textarea id="restHeaders" rows="3" class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 json-editor">{
"Content-Type": "application/json",
"Accept": "application/json"
}</textarea>
</div>
<div>
<label for="restParams" class="block text-sm font-medium text-gray-700 mb-1">Query Parameters (JSON)</label>
<textarea id="restParams" rows="3" class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 json-editor">{}</textarea>
</div>
<div id="restBodyContainer" class="hidden">
<label for="restBody" class="block text-sm font-medium text-gray-700 mb-1">Request Body (JSON)</label>
<textarea id="restBody" rows="5" class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 json-editor">{}</textarea>
</div>
</div>
</div>
<div class="p-4 border-t border-gray-200 flex justify-end space-x-2">
<button id="testRestConnectionBtn" class="px-4 py-2 bg-yellow-500 hover:bg-yellow-600 text-white rounded-md text-sm font-medium flex items-center">
<i class="fas fa-bolt mr-1"></i> Test Connection
</button>
<button id="saveRestConnectionBtn" class="px-4 py-2 bg-blue-500 hover:bg-blue-600 text-white rounded-md text-sm font-medium flex items-center">
<i class="fas fa-save mr-1"></i> Save Connection
</button>
</div>
</div>
</div>
<!-- JSON Connection Modal -->
<div id="jsonConnectionModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden">
<div class="bg-white rounded-lg shadow-xl w-full max-w-2xl">
<div class="p-4 border-b border-gray-200">
<h3 class="text-lg font-medium text-gray-800">JSON Data Connection</h3>
</div>
<div class="p-4">
<div class="grid grid-cols-1 gap-4">
<div>
<label for="jsonConnectionName" class="block text-sm font-medium text-gray-700 mb-1">Connection Name</label>
<input type="text" id="jsonConnectionName" class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Data Source</label>
<div class="grid grid-cols-2 gap-2">
<button data-source="url" class="json-source-btn bg-white border border-gray-300 rounded-md p-3 hover:bg-gray-50 flex flex-col items-center">
<i class="fas fa-link text-blue-500 text-2xl mb-2"></i>
<span>URL</span>
</button>
<button data-source="file" class="json-source-btn bg-white border border-gray-300 rounded-md p-3 hover:bg-gray-50 flex flex-col items-center">
<i class="fas fa-file-upload text-green-500 text-2xl mb-2"></i>
<span>File Upload</span>
</button>
<button data-source="text" class="json-source-btn bg-white border border-gray-300 rounded-md p-3 hover:bg-gray-50 flex flex-col items-center">
<i class="fas fa-keyboard text-purple-500 text-2xl mb-2"></i>
<span>Direct Input</span>
</button>
</div>
</div>
<div id="jsonUrlContainer" class="hidden">
<label for="jsonUrl" class="block text-sm font-medium text-gray-700 mb-1">JSON URL</label>
<input type="text" id="jsonUrl" placeholder="https://example.com/data.json" class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500">
</div>
<div id="jsonFileContainer" class="hidden">
<label for="jsonFile" class="block text-sm font-medium text-gray-700 mb-1">Upload JSON File</label>
<div class="mt-1 flex justify-center px-6 pt-5 pb-6 border-2 border-gray-300 border-dashed rounded-md">
<div class="space-y-1 text-center">
<div class="flex text-sm text-gray-600">
<label for="jsonFile" class="relative cursor-pointer bg-white rounded-md font-medium text-blue-600 hover:text-blue-500 focus-within:outline-none">
<span>Upload a file</span>
<input id="jsonFile" type="file" class="sr-only">
</label>
<p class="pl-1">or drag and drop</p>
</div>
<p class="text-xs text-gray-500">JSON files up to 10MB</p>
</div>
</div>
</div>
<div id="jsonTextContainer" class="hidden">
<label for="jsonText" class="block text-sm font-medium text-gray-700 mb-1">JSON Data</label>
<textarea id="jsonText" rows="10" class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 json-editor">{
"data": [
{
"id": 1,
"name": "Example Item",
"value": 42
}
]
}</textarea>
</div>
<div>
<label for="jsonRootPath" class="block text-sm font-medium text-gray-700 mb-1">Root Path (optional)</label>
<input type="text" id="jsonRootPath" placeholder="data.items" class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500">
<p class="mt-1 text-xs text-gray-500">Use dot notation to specify the path to your data array</p>
</div>
</div>
</div>
<div class="p-4 border-t border-gray-200 flex justify-end space-x-2">
<button id="testJsonConnectionBtn" class="px-4 py-2 bg-yellow-500 hover:bg-yellow-600 text-white rounded-md text-sm font-medium flex items-center">
<i class="fas fa-bolt mr-1"></i> Test Connection
</button>
<button id="saveJsonConnectionBtn" class="px-4 py-2 bg-blue-500 hover:bg-blue-600 text-white rounded-md text-sm font-medium flex items-center">
<i class="fas fa-save mr-1"></i> Save Connection
</button>
</div>
</div>
</div>
<!-- Test Connection Result Modal -->
<div id="testResultModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden">
<div class="bg-white rounded-lg shadow-xl w-full max-w-md">
<div class="p-4 border-b border-gray-200 flex justify-between items-center">
<h3 class="text-lg font-medium text-gray-800">Test Connection Result</h3>
<button id="closeTestResultBtn" class="text-gray-400 hover:text-gray-500">
<i class="fas fa-times"></i>
</button>
</div>
<div class="p-4">
<div id="testSuccess" class="hidden">
<div class="flex items-center justify-center mb-4">
<div class="bg-green-100 p-3 rounded-full">
<i class="fas fa-check-circle text-green-500 text-3xl"></i>
</div>
</div>
<p class="text-center text-gray-700 mb-2">Connection test successful!</p>
<div class="bg-gray-50 p-3 rounded-md">
<p class="text-sm text-gray-600"><span class="font-medium">Status:</span> <span id="testStatus" class="text-green-600">200 OK</span></p>
<p class="text-sm text-gray-600"><span class="font-medium">Records Found:</span> <span id="testRecordsCount">10</span></p>
</div>
</div>
<div id="testError" class="hidden">
<div class="flex items-center justify-center mb-4">
<div class="bg-red-100 p-3 rounded-full">
<i class="fas fa-exclamation-circle text-red-500 text-3xl"></i>
</div>
</div>
<p class="text-center text-gray-700 mb-2">Connection test failed!</p>
<div class="bg-gray-50 p-3 rounded-md">
<p class="text-sm text-gray-600"><span class="font-medium">Error:</span> <span id="testErrorMessage" class="text-red-600">Could not connect to server</span></p>
</div>
</div>
</div>
<div class="p-4 border-t border-gray-200 flex justify-end">
<button id="confirmTestResultBtn" class="px-4 py-2 bg-blue-500 hover:bg-blue-600 text-white rounded-md text-sm font-medium">OK</button>
</div>
</div>
</div>
<!-- Component Configuration Modal -->
<div id="componentConfigModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden">
<div class="bg-white rounded-lg shadow-xl w-full max-w-2xl">
<div class="p-4 border-b border-gray-200">
<h3 id="componentConfigTitle" class="text-lg font-medium text-gray-800">Configure Component</h3>
</div>
<div class="p-4">
<div id="componentConfigContent">
<!-- Configuration content will be loaded here -->
</div>
</div>
<div class="p-4 border-t border-gray-200 flex justify-end space-x-2">
<button id="cancelComponentConfigBtn" class="px-4 py-2 border border-gray-300 rounded-md text-sm font-medium text-gray-700 hover:bg-gray-50">Cancel</button>
<button id="saveComponentConfigBtn" class="px-4 py-2 bg-blue-500 hover:bg-blue-600 text-white rounded-md text-sm font-medium">Save</button>
</div>
</div>
</div>
<script>
// Sample data for demo purposes
const sampleReports = [
{ id: 1, name: 'Sales Overview', category: 'sales', lastModified: '2023-05-15', starred: true },
{ id: 2, name: 'Marketing Campaigns', category: 'marketing', lastModified: '2023-05-10', starred: false },
{ id: 3, name: 'Financial Summary', category: 'finance', lastModified: '2023-05-05', starred: true },
{ id: 4, name: 'Operations Dashboard', category: 'operations', lastModified: '2023-04-28', starred: false },
{ id: 5, name: 'Customer Analytics', category: 'custom', lastModified: '2023-04-20', starred: false }
];
const sampleConnections = [
{ id: 1, name: 'Sales API', type: 'rest', status: 'active', lastUsed: '2023-05-15' },
{ id: 2, name: 'Marketing Data', type: 'json', status: 'active', lastUsed: '2023-05-10' },
{ id: 3, name: 'Finance System', type: 'rest', status: 'inactive', lastUsed: '2023-04-01' },
{ id: 4, name: 'Operations DB', type: 'rest', status: 'pending', lastUsed: '2023-05-01' }
];
// Current state
let currentView = 'dashboard'; // 'dashboard' or 'editor'
let currentReport = null;
let connections = [...sampleConnections];
let reports = [...sampleReports];
let dataSources = [];
let components = [];
// DOM Elements
const dashboardView = document.getElementById('dashboardView');
const reportEditorView = document.getElementById('reportEditorView');
const reportList = document.getElementById('reportList');
const connectionList = document.getElementById('connectionList');
const recentReports = document.getElementById('recentReports');
const totalReportsCount = document.getElementById('totalReportsCount');
const activeConnectionsCount = document.getElementById('activeConnectionsCount');
const currentReportTitle = document.getElementById('currentReportTitle');
const newReportBtn = document.getElementById('newReportBtn');
const saveReportBtn = document.getElementById('saveReportBtn');
const exportReportBtn = document.getElementById('exportReportBtn');
const addDataSourceBtn = document.getElementById('addDataSourceBtn');
const dataSourceContent = document.getElementById('dataSourceContent');
const reportCanvas = document.getElementById('reportCanvas');
const reportPreview = document.getElementById('reportPreview');
const refreshPreviewBtn = document.getElementById('refreshPreviewBtn');
// Modals
const newConnectionModal = document.getElementById('newConnectionModal');
const restConnectionModal = document.getElementById('restConnectionModal');
const jsonConnectionModal = document.getElementById('jsonConnectionModal');
const testResultModal = document.getElementById('testResultModal');
const componentConfigModal = document.getElementById('componentConfigModal');
// Tab functionality
const tabButtons = document.querySelectorAll('.tab-button');
tabButtons.forEach(button => {
button.addEventListener('click', () => {
const tabId = button.getAttribute('data-tab');
switchTab(tabId);
});
});
function switchTab(tabId) {
// Hide all tab contents
document.querySelectorAll('.tab-content').forEach(content => {
content.classList.remove('active');
});
// Show selected tab content
document.getElementById(tabId).classList.add('active');
// Update active tab button
document.querySelectorAll('.tab-button').forEach(btn => {
btn.classList.remove('border-blue-500', 'text-blue-600');
});
const activeButton = document.querySelector(`.tab-button[data-tab="${tabId}"]`);
activeButton.classList.add('border-blue-500', 'text-blue-600');
}
// Initialize the dashboard
function initDashboard() {
updateReportList();
updateConnectionList();
updateRecentReports();
updateCounts();
}
function updateReportList() {
reportList.innerHTML = '';
reports.forEach(report => {
const reportItem = document.createElement('div');
reportItem.className = `flex items-center justify-between p-2 rounded-md hover:bg-gray-100 cursor-pointer ${currentReport?.id === report.id ? 'bg-blue-50' : ''}`;
reportItem.innerHTML = `
<div class="flex items-center">
<i class="fas fa-file-alt text-gray-500 mr-2"></i>
<span class="text-sm font-medium">${report.name}</span>
</div>
${report.starred ? '<i class="fas fa-star text-yellow-400"></i>' : ''}
`;
reportItem.addEventListener('click', () => openReportEditor(report));
reportList.appendChild(reportItem);
});
}
function updateConnectionList() {
connectionList.innerHTML = '';
connections.forEach(conn => {
const connItem = document.createElement('div');
connItem.className = `flex items-center justify-between p-2 rounded-md hover:bg-gray-100 cursor-pointer`;
connItem.innerHTML = `
<div class="flex items-center">
<span class="connection-status connection-${conn.status}"></span>
<i class="fas ${conn.type === 'rest' ? 'fa-cloud' : 'fa-file-code'} text-gray-500 mr-2"></i>
<span class="text-sm font-medium">${conn.name}</span>
</div>
<span class="text-xs text-gray-500">${formatDate(conn.lastUsed)}</span>
`;
connectionList.appendChild(connItem);
});
}
function updateRecentReports() {
recentReports.innerHTML = '';
// Sort by last modified and take first 3
const recent = [...reports].sort((a, b) => new Date(b.lastModified) - new Date(a.lastModified)).slice(0, 3);
recent.forEach(report => {
const reportCard = document.createElement('div');
reportCard.className = 'report-card bg-white rounded-lg shadow p-4 transition-all duration-200';
reportCard.innerHTML = `
<div class="flex justify-between items-start mb-2">
<h4 class="font-medium text-gray-800">${report.name}</h4>
<span class="text-xs text-gray-500">${formatDate(report.lastModified)}</span>
</div>
<div class="flex items-center text-xs text-gray-500 mb-3">
<i class="fas fa-tag mr-1"></i>
<span>${report.category}</span>
</div>
<button class="w-full bg-gray-100 hover:bg-gray-200 text-gray-700 py-1 px-3 rounded-md text-sm">
Open Report
</button>
`;
reportCard.addEventListener('click', () => openReportEditor(report));
recentReports.appendChild(reportCard);
});
}
function updateCounts() {
totalReportsCount.textContent = reports.length;
activeConnectionsCount.textContent = connections.filter(c => c.status === 'active').length;
}
function formatDate(dateString) {
const date = new Date(dateString);
return date.toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
}
// Report editor functions
function openReportEditor(report) {
currentReport = report;
currentView = 'editor';
// Update UI
dashboardView.classList.add('hidden');
reportEditorView.classList.remove('hidden');
currentReportTitle.textContent = report.name;
// Highlight selected report in sidebar
updateReportList();
// Load report data (in a real app, this would come from an API)
loadReportData(report.id);
}
function loadReportData(reportId) {
// In a real app, this would fetch report data from an API
console.log(`Loading data for report ${reportId}`);
// For demo, just show some sample components
if (reportId === 1) {
// Sales Overview report
showSampleComponents();
} else {
// Empty report
reportCanvas.innerHTML = '<p class="text-center text-gray-500">Drag and drop components here to build your report</p>';
}
}
function showSampleComponents() {
reportCanvas.innerHTML = `
<div class="bg-white rounded-lg shadow p-4 mb-4">
<div class="flex justify-between items-center mb-2">
<h4 class="font-medium text-gray-800">Sales Performance</h4>
<div class="flex space-x-2">
<button class="text-gray-400 hover:text-gray-600">
<i class="fas fa-cog"></i>
</button>
<button class="text-gray-400 hover:text-gray-600">
<i class="fas fa-times"></i>
</button>
</div>
</div>
<div class="chart-container">
<div class="bg-gray-100 rounded h-64 flex items-center justify-center">
<p class="text-gray-500">Bar Chart Visualization</p>
</div>
</div>
</div>
<div class="grid grid-cols-3 gap-4 mb-4">
<div class="bg-white rounded-lg shadow p-4">
<div class="text-sm text-gray-500 mb-1">Total Revenue</div>
<div class="text-2xl font-bold text-gray-800">$24,589</div>
<div class="text-xs text-green-500 mt-1">
<i class="fas fa-arrow-up mr-1"></i> 12.5%
</div>
</div>
<div class="bg-white rounded-lg shadow p-4">
<div class="text-sm text-gray-500 mb-1">New Customers</div>
<div class="text-2xl font-bold text-gray-800">1,245</div>
<div class="text-xs text-green-500 mt-1">
<i class="fas fa-arrow-up mr-1"></i> 8.3%
</div>
</div>
<div class="bg-white rounded-lg shadow p-4">
<div class="text-sm text-gray-500 mb-1">Conversion Rate</div>
<div class="text-2xl font-bold text-gray-800">3.2%</div>
<div class="text-xs text-red-500 mt-1">
<i class="fas fa-arrow-down mr-1"></i> 0.8%
</div>
</div>
</div>
<div class="bg-white rounded-lg shadow p-4">
<div class="flex justify-between items-center mb-2">
<h4 class="font-medium text-gray-800">Recent Transactions</h4>
<div class="flex space-x-2">
<button class="text-gray-400 hover:text-gray-600">
<i class="fas fa-cog"></i>
</button>
<button class="text-gray-400 hover:text-gray-600">
<i class="fas fa-times"></i>
</button>
</div>
</div>
<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50">
<tr>
<th class="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">ID</th>
<th class="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Customer</th>
<th class="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Amount</th>
<th class="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Date</th>
</tr>
</thead>
<tbody class="bg-white divide-y divide-gray-200">
<tr>
<td class="px-4 py-2 whitespace-nowrap text-sm text-gray-500">#1001</td>
<td class="px-4 py-2 whitespace-nowrap text-sm text-gray-500">Acme Corp</td>
<td class="px-4 py-2 whitespace-nowrap text-sm text-gray-500">$1,200</td>
<td class="px-4 py-2 whitespace-nowrap text-sm text-gray-500">May 15, 2023</td>
</tr>
<tr>
<td class="px-4 py-2 whitespace-nowrap text-sm text-gray-500">#1002</td>
<td class="px-4 py-2 whitespace-nowrap text-sm text-gray-500">Globex Inc</td>
<td class="px-4 py-2 whitespace-nowrap text-sm text-gray-500">$850</td>
<td class="px-4 py-2 whitespace-nowrap text-sm text-gray-500">May 14, 2023</td>
</tr>
<tr>
<td class="px-4 py-2 whitespace-nowrap text-sm text-gray-500">#1003</td>
<td class="px-4 py-2 whitespace-nowrap text-sm text-gray-500">Initech LLC</td>
<td class="px-4 py-2 whitespace-nowrap text-sm text-gray-500">$2,450</td>
<td class="px-4 py-2 whitespace-nowrap text-sm text-gray-500">May 14, 2023</td>
</tr>
</tbody>
</table>
</div>
</div>
`;
}
// New report functionality
newReportBtn.addEventListener('click', () => {
currentReport = {
id: reports.length + 1,
name: 'New Report',
category: 'custom',
lastModified: new Date().toISOString().split('T')[0],
starred: false
};
reports.unshift(currentReport);
updateReportList();
updateRecentReports();
updateCounts();
openReportEditor(currentReport);
});
// Save report functionality
saveReportBtn.addEventListener('click', () => {
if (!currentReport) return;
// In a real app, this would save to an API
console.log('Saving report:', currentReport);
// Update last modified date
currentReport.lastModified = new Date().toISOString().split('T')[0];
// Update UI
updateReportList();
updateRecentReports();
// Show success message
alert('Report saved successfully!');
});
// Export report functionality
exportReportBtn.addEventListener('click', () => {
if (!currentReport) return;
// In a real app, this would generate an export file
console.log('Exporting report:', currentReport);
// Show export options
alert('Export options would appear here (PDF, Excel, etc.)');
});
// Data source functionality
addDataSourceBtn.addEventListener('click', () => {
newConnectionModal.classList.remove('hidden');
});
// Connection type selection
document.querySelectorAll('.connection-type-btn').forEach(btn => {
btn.addEventListener('click', () => {
const type = btn.getAttribute('data-type');
newConnectionModal.classList.add('hidden');
if (type === 'rest') {
restConnectionModal.classList.remove('hidden');
} else if (type === 'json') {
jsonConnectionModal.classList.remove('hidden');
}
});
});
// REST API connection form
const restMethodSelect = document.getElementById('restMethod');
restMethodSelect.addEventListener('change', (e) => {
const method = e.target.value;
const bodyContainer = document.getElementById('restBodyContainer');
if (method === 'GET' || method === 'DELETE') {
bodyContainer.classList.add('hidden');
} else {
bodyContainer.classList.remove('hidden');
}
});
const restAuthTypeSelect = document.getElementById('restAuthType');
restAuthTypeSelect.addEventListener('change', (e) => {
const authType = e.target.value;
const authFields = document.getElementById('authFields');
// Hide all auth fields
document.querySelectorAll('#authFields > div').forEach(field => {
field.classList.add('hidden');
});
if (authType === 'none') {
authFields.classList.add('hidden');
} else {
authFields.classList.remove('hidden');
document.getElementById(`${authType}AuthFields`).classList.remove('hidden');
}
});
// JSON source selection
document.querySelectorAll('.json-source-btn').forEach(btn => {
btn.addEventListener('click', () => {
const source = btn.getAttribute('data-source');
// Hide all containers
document.getElementById('jsonUrlContainer').classList.add('hidden');
document.getElementById('jsonFileContainer').classList.add('hidden');
document.getElementById('jsonTextContainer').classList.add('hidden');
// Show selected container
document.getElementById(`json${source.charAt(0).toUpperCase() + source.slice(1)}Container`).classList.remove('hidden');
});
});
// Test connection buttons
document.getElementById('testRestConnectionBtn').addEventListener('click', () => {
// Simulate testing a REST connection
showTestResult(true, '200 OK', 15);
});
document.getElementById('testJsonConnectionBtn').addEventListener('click', () => {
// Simulate testing a JSON connection
showTestResult(true, '200 OK', 8);
});
function showTestResult(success, message, recordsCount = 0) {
testResultModal.classList.remove('hidden');
if (success) {
document.getElementById('testSuccess').classList.remove('hidden');
document.getElementById('testError').classList.add('hidden');
document.getElementById('testStatus').textContent = message;
document.getElementById('testRecordsCount').textContent = recordsCount;
} else {
document.getElementById('testSuccess').classList.add('hidden');
document.getElementById('testError').classList.remove('hidden');
document.getElementById('testErrorMessage').textContent = message;
}
}
// Save connection buttons
document.getElementById('saveRestConnectionBtn').addEventListener('click', () => {
const name = document.getElementById('restConnectionName').value;
const endpoint = document.getElementById('restEndpoint').value;
if (!name || !endpoint) {
alert('Please fill in all required fields');
return;
}
const newConnection = {
id: connections.length + 1,
name: name,
type: 'rest',
status: 'active',
lastUsed: new Date().toISOString().split('T')[0]
};
connections.unshift(newConnection);
updateConnectionList();
updateCounts();
restConnectionModal.classList.add('hidden');
showTestResult(true, '200 OK', 15); // Show success for demo
});
document.getElementById('saveJsonConnectionBtn').addEventListener('click', () => {
const name = document.getElementById('jsonConnectionName').value;
if (!name) {
alert('Please fill in all required fields');
return;
}
const newConnection = {
id: connections.length + 1,
name: name,
type: 'json',
status: 'active',
lastUsed: new Date().toISOString().split('T')[0]
};
connections.unshift(newConnection);
updateConnectionList();
updateCounts();
jsonConnectionModal.classList.add('hidden');
showTestResult(true, '200 OK', 8); // Show success for demo
});
// Close modals
document.getElementById('cancelConnectionBtn').addEventListener('click', () => {
newConnectionModal.classList.add('hidden');
});
document.getElementById('closeTestResultBtn').addEventListener('click', () => {
testResultModal.classList.add('hidden');
});
document.getElementById('confirmTestResultBtn').addEventListener('click', () => {
testResultModal.classList.add('hidden');
});
// Drag and drop functionality for report builder
const draggableItems = document.querySelectorAll('.draggable-item');
draggableItems.forEach(item => {
item.addEventListener('dragstart', (e) => {
e.dataTransfer.setData('text/plain', item.getAttribute('data-type'));
e.dataTransfer.effectAllowed = 'copy';
});
});
reportCanvas.addEventListener('dragover', (e) => {
e.preventDefault();
e.dataTransfer.dropEffect = 'copy';
reportCanvas.classList.add('border-blue-400', 'bg-blue-50');
});
reportCanvas.addEventListener('dragleave', () => {
reportCanvas.classList.remove('border-blue-400', 'bg-blue-50');
});
reportCanvas.addEventListener('drop', (e) => {
e.preventDefault();
reportCanvas.classList.remove('border-blue-400', 'bg-blue-50');
const componentType = e.dataTransfer.getData('text/plain');
addComponentToCanvas(componentType);
});
function addComponentToCanvas(type) {
// In a real app, this would add a proper component with configuration
const componentId = Date.now();
let componentHtml = '';
let configOptions = '';
switch (type) {
case 'chart':
componentHtml = `
<div class="bg-white rounded-lg shadow p-4 mb-4" data-component-id="${componentId}">
<div class="flex justify-between items-center mb-2">
<h4 class="font-medium text-gray-800">New Chart</h4>
<div class="flex space-x-2">
<button class="text-gray-400 hover:text-gray-600 configure-btn">
<i class="fas fa-cog"></i>
</button>
<button class="text-gray-400 hover:text-gray-600 delete-btn">
<i class="fas fa-times"></i>
</button>
</div>
</div>
<div class="chart-container">
<div class="bg-gray-100 rounded h-64 flex items-center justify-center">
<p class="text-gray-500">Chart Visualization</p>
</div>
</div>
</div>
`;
configOptions = `
<div class="grid grid-cols-1 gap-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Chart Type</label>
<select class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500">
<option value="bar">Bar Chart</option>
<option value="line">Line Chart</option>
<option value="pie">Pie Chart</option>
<option value="area">Area Chart</option>
</select>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Data Source</label>
<select class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500">
<option value="">Select a data source</option>
${connections.map(conn => `<option value="${conn.id}">${conn.name}</option>`).join('')}
</select>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">X-Axis Field</label>
<input type="text" class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Y-Axis Field</label>
<input type="text" class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500">
</div>
</div>
`;
break;
case 'table':
componentHtml = `
<div class="bg-white rounded-lg shadow p-4 mb-4" data-component-id="${componentId}">
<div class="flex justify-between items-center mb-2">
<h4 class="font-medium text-gray-800">New Data Table</h4>
<div class="flex space-x-2">
<button class="text-gray-400 hover:text-gray-600 configure-btn">
<i class="fas fa-cog"></i>
</button>
<button class="text-gray-400 hover:text-gray-600 delete-btn">
<i class="fas fa-times"></i>
</button>
</div>
</div>
<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50">
<tr>
<th class="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Column 1</th>
<th class="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Column 2</th>
<th class="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Column 3</th>
</tr>
</thead>
<tbody class="bg-white divide-y divide-gray-200">
<tr>
<td class="px-4 py-2 whitespace-nowrap text-sm text-gray-500">Sample</td>
<td class="px-4 py-2 whitespace-nowrap text-sm text-gray-500">Data</td>
<td class="px-4 py-2 whitespace-nowrap text-sm text-gray-500">Row</td>
</tr>
</tbody>
</table>
</div>
</div>
`;
configOptions = `
<div class="grid grid-cols-1 gap-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Data Source</label>
<select class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500">
<option value="">Select a data source</option>
${connections.map(conn => `<option value="${conn.id}">${conn.name}</option>`).join('')}
</select>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Fields to Display</label>
<div class="space-y-2">
${['Field 1', 'Field 2', 'Field 3'].map(field => `
<div class="flex items-center">
<input type="checkbox" id="field-${field}" class="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded">
<label for="field-${field}" class="ml-2 block text-sm text-gray-700">${field}</label>
</div>
`).join('')}
</div>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Rows per Page</label>
<input type="number" value="10" class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500">
</div>
</div>
`;
break;
case 'metric':
componentHtml = `
<div class="bg-white rounded-lg shadow p-4 mb-4" data-component-id="${componentId}">
<div class="flex justify-between items-center mb-2">
<h4 class="font-medium text-gray-800">New Metric</h4>
<div class="flex space-x-2">
<button class="text-gray-400 hover:text-gray-600 configure-btn">
<i class="fas fa-cog"></i>
</button>
<button class="text-gray-400 hover:text-gray-600 delete-btn">
<i class="fas fa-times"></i>
</button>
</div>
</div>
<div class="text-sm text-gray-500 mb-1">Metric Title</div>
<div class="text-2xl font-bold text-gray-800">0</div>
<div class="text-xs text-gray-500 mt-1">No change</div>
</div>
`;
configOptions = `
<div class="grid grid-cols-1 gap-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Title</label>
<input type="text" value="Metric Title" class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Data Source</label>
<select class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500">
<option value="">Select a data source</option>
${connections.map(conn => `<option value="${conn.id}">${conn.name}</option>`).join('')}
</select>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Value Field</label>
<input type="text" class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Aggregation</label>
<select class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500">
<option value="sum">Sum</option>
<option value="avg">Average</option>
<option value="count">Count</option>
<option value="min">Minimum</option>
<option value="max">Maximum</option>
</select>
</div>
</div>
`;
break;
case 'text':
componentHtml = `
<div class="bg-white rounded-lg shadow p-4 mb-4" data-component-id="${componentId}">
<div class="flex justify-between items-center mb-2">
<h4 class="font-medium text-gray-800">New Text Block</h4>
<div class="flex space-x-2">
<button class="text-gray-400 hover:text-gray-600 configure-btn">
<i class="fas fa-cog"></i>
</button>
<button class="text-gray-400 hover:text-gray-600 delete-btn">
<i class="fas fa-times"></i>
</button>
</div>
</div>
<div class="prose max-w-none">
<p>This is a sample text block. You can edit this content.</p>
</div>
</div>
`;
configOptions = `
<div class="grid grid-cols-1 gap-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Title</label>
<input type="text" value="New Text Block" class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Content</label>
<textarea rows="5" class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500">This is a sample text block. You can edit this content.</textarea>
</div>
</div>
`;
break;
}
// Add to canvas
if (reportCanvas.innerHTML.includes('Drag and drop components')) {
reportCanvas.innerHTML = componentHtml;
} else {
reportCanvas.insertAdjacentHTML('beforeend', componentHtml);
}
// Store component data
components.push({
id: componentId,
type: type,
config: {}
});
// Set up event listeners for the new component
const newComponent = document.querySelector(`[data-component-id="${componentId}"]`);
// Configure button
newComponent.querySelector('.configure-btn').addEventListener('click', () => {
document.getElementById('componentConfigTitle').textContent = `Configure ${type.charAt(0).toUpperCase() + type.slice(1)}`;
document.getElementById('componentConfigContent').innerHTML = configOptions;
componentConfigModal.classList.remove('hidden');
});
// Delete button
newComponent.querySelector('.delete-btn').addEventListener('click', () => {
if (confirm('Are you sure you want to delete this component?')) {
newComponent.remove();
components = components.filter(c => c.id !== componentId);
if (reportCanvas.querySelectorAll('[data-component-id]').length === 0) {
reportCanvas.innerHTML = '<p class="text-center text-gray-500">Drag and drop components here to build your report</p>';
}
}
});
}
// Component configuration
document.getElementById('saveComponentConfigBtn').addEventListener('click', () => {
// In a real app, this would save the component configuration
componentConfigModal.classList.add('hidden');
alert('Component configuration saved!');
});
document.getElementById('cancelComponentConfigBtn').addEventListener('click', () => {
componentConfigModal.classList.add('hidden');
});
// Preview functionality
refreshPreviewBtn.addEventListener('click', () => {
// In a real app, this would refresh the preview with current data
reportPreview.innerHTML = `
<div class="bg-white rounded-lg shadow p-6">
<h3 class="text-xl font-bold text-gray-800 mb-4">${currentReport.name}</h3>
<div class="grid grid-cols-3 gap-4 mb-6">
<div class="bg-blue-50 p-4 rounded-lg">
<div class="text-sm text-blue-600 mb-1">Total Metrics</div>
<div class="text-2xl font-bold text-blue-800">3</div>
</div>
<div class="bg-green-50 p-4 rounded-lg">
<div class="text-sm text-green-600 mb-1">Data Sources</div>
<div class="text-2xl font-bold text-green-800">2</div>
</div>
<div class="bg-purple-50 p-4 rounded-lg">
<div class="text-sm text-purple-600 mb-1">Last Updated</div>
<div class="text-2xl font-bold text-purple-800">Now</div>
</div>
</div>
<div class="bg-gray-100 rounded-lg h-64 flex items-center justify-center mb-6">
<p class="text-gray-500">Sample Chart Visualization</p>
</div>
<div class="bg-white border border-gray-200 rounded-lg overflow-hidden">
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50">
<tr>
<th class="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Field 1</th>
<th class="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Field 2</th>
<th class="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Field 3</th>
</tr>
</thead>
<tbody class="bg-white divide-y divide-gray-200">
<tr>
<td class="px-4 py-2 whitespace-nowrap text-sm text-gray-500">Data 1</td>
<td class="px-4 py-2 whitespace-nowrap text-sm text-gray-500">Data 2</td>
<td class="px-4 py-2 whitespace-nowrap text-sm text-gray-500">Data 3</td>
</tr>
<tr>
<td class="px-4 py-2 whitespace-nowrap text-sm text-gray-500">Data 4</td>
<td class="px-4 py-2 whitespace-nowrap text-sm text-gray-500">Data 5</td>
<td class="px-4 py-2 whitespace-nowrap text-sm text-gray-500">Data 6</td>
</tr>
</tbody>
</table>
</div>
</div>
`;
});
// Initialize the app
document.addEventListener('DOMContentLoaded', () => {
initDashboard();
switchTab('settings'); // Start with settings tab active
});
</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://enzostvs-deepsite.hf.space/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://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=sombochea/report-manager" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
</html>