| | <!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> |