|
<!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"> |
|
|
|
<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"> |
|
|
|
</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"> |
|
|
|
</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> |
|
|
|
|
|
<div class="flex-1 overflow-auto"> |
|
|
|
<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> |
|
|
|
|
|
<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"> |
|
|
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
|
|
<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"> |
|
|
|
<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> |
|
|
|
|
|
<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"> |
|
|
|
</nav> |
|
</div> |
|
|
|
<div id="dataSourceContent"> |
|
|
|
<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> |
|
|
|
|
|
<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> |
|
|
|
|
|
<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> |
|
|
|
|
|
|
|
<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> |
|
|
|
|
|
<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> |
|
|
|
|
|
<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> |
|
|
|
|
|
<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> |
|
|
|
|
|
<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> |
|
|
|
|
|
<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"> |
|
|
|
</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> |