Created
April 1, 2025 11:38
-
-
Save dexit/2f9143a4e073b5f05d92026e71410484 to your computer and use it in GitHub Desktop.
Datamapper .html
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>Advanced Data Mapper Pro</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"> | |
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script> | |
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/styles/atom-one-dark.min.css"> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/highlight.min.js"></script> | |
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/autosize.min.js"></script> | |
<style> | |
.drop-zone { | |
border: 2px dashed #e2e8f0; | |
transition: all 0.3s; | |
min-height: 200px; | |
} | |
.drop-zone.active { | |
border-color: #6366f1; | |
background-color: #eef2ff; | |
} | |
.mapping-item { | |
transition: all 0.2s; | |
background: linear-gradient(to right, #f9fafb, #fff); | |
} | |
.mapping-item:hover { | |
transform: translateY(-2px); | |
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.05); | |
background: linear-gradient(to right, #f3f4f6, #fff); | |
} | |
.json-editor { | |
font-family: "Fira Code", "Courier New", monospace; | |
font-size: 0.9rem; | |
line-height: 1.5; | |
} | |
.tab-button { | |
transition: all 0.2s; | |
position: relative; | |
} | |
.tab-button.active:after { | |
content: ''; | |
position: absolute; | |
bottom: -1px; | |
left: 0; | |
width: 100%; | |
height: 2px; | |
background: #6366f1; | |
} | |
.hljs { | |
background: #1e293b; | |
border-radius: 0.375rem; | |
} | |
.transform-card { | |
transition: all 0.2s; | |
border-left: 4px solid transparent; | |
} | |
.transform-card.active { | |
border-left-color: #6366f1; | |
background-color: #f8fafc; | |
} | |
#left-sidebar { | |
transition: transform 0.3s ease; | |
} | |
#right-sidebar { | |
transition: transform 0.3s ease; | |
} | |
.sidebar-toggle { | |
right: -12px; | |
} | |
.sidebar-toggle.left { | |
left: -12px; | |
} | |
.dark .json-editor { | |
background-color: #1e1e1e; | |
color: #d4d4d4; | |
} | |
.dark .hljs { | |
background: #0f172a; | |
} | |
</style> | |
</head> | |
<body class="bg-gray-50 text-gray-800 min-h-screen"> | |
<div id="app" class="container mx-auto px-4 py-6"> | |
<!-- App Header --> | |
<header class="mb-8 flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4"> | |
<div> | |
<h1 class="text-3xl font-bold text-gray-800 flex items-center"> | |
<i class="fas fa-project-diagram text-indigo-600 mr-3"></i> | |
<span>Advanced Data Mapper Pro</span> | |
<span class="ml-2 text-xs bg-indigo-100 text-indigo-800 px-2 py-1 rounded-full">v3.2.0</span> | |
</h1> | |
<p class="text-gray-600 mt-2">Transform and map data between any formats with advanced rules</p> | |
</div> | |
<div class="flex flex-wrap gap-2"> | |
<button id="toggle-dark-mode" class="px-3 py-1.5 bg-gray-100 hover:bg-gray-200 text-gray-700 rounded-lg transition flex items-center"> | |
<i class="fas fa-moon mr-2"></i> Dark Mode | |
</button> | |
<button id="open-saved-modal" class="px-4 py-1.5 bg-indigo-600 hover:bg-indigo-700 text-white rounded-lg transition flex items-center"> | |
<i class="fas fa-folder-open mr-2"></i> Saved Mappings | |
</button> | |
<button id="help-btn" class="px-3 py-1.5 bg-blue-100 hover:bg-blue-200 text-blue-600 rounded-lg transition flex items-center"> | |
<i class="fas fa-question-circle mr-2"></i> Help | |
</button> | |
</div> | |
</header> | |
<div class="flex gap-4"> | |
<!-- Left Sidebar --> | |
<div id="left-sidebar" class="w-64 bg-white rounded-lg shadow p-4 shrink-0 relative"> | |
<button id="toggle-left-sidebar" class="sidebar-toggle left absolute top-1/2 -translate-y-1/2 bg-white border border-gray-200 rounded-full w-6 h-6 flex items-center justify-center shadow-sm hover:bg-gray-50"> | |
<i class="fas fa-chevron-left text-gray-500 text-xs"></i> | |
</button> | |
<div class="mb-4 pb-4 border-b border-gray-200"> | |
<h3 class="font-medium text-gray-700 mb-3 flex items-center"> | |
<i class="fas fa-layer-group text-indigo-500 mr-2"></i> Project Mappings | |
</h3> | |
<div class="space-y-2"> | |
<button class="w-full text-left px-3 py-1.5 bg-indigo-50 text-indigo-700 rounded-md text-sm flex items-center"> | |
<i class="fas fa-object-group mr-2"></i> Customer Mapping | |
</button> | |
<button class="w-full text-left px-3 py-1.5 hover:bg-gray-100 rounded-md text-sm flex items-center"> | |
<i class="fas fa-boxes mr-2"></i> Product Catalog | |
</button> | |
<button class="w-full text-left px-3 py-1.5 hover:bg-gray-100 rounded-md text-sm flex items-center"> | |
<i class="fas fa-file-invoice-dollar mr-2"></i> Orders Import | |
</button> | |
</div> | |
</div> | |
<div> | |
<h3 class="font-medium text-gray-700 mb-3 flex items-center"> | |
<i class="fas fa-history text-blue-500 mr-2"></i> Recent Activity | |
</h3> | |
<div class="space-y-2"> | |
<div class="text-xs p-2 bg-blue-50 rounded-md"> | |
<div class="flex justify-between"> | |
<span class="font-medium">Customer Mapping</span> | |
<span class="text-gray-500">2h ago</span> | |
</div> | |
<p class="text-gray-600 truncate">Updated email transformation</p> | |
</div> | |
<div class="text-xs p-2 hover:bg-gray-50 rounded-md"> | |
<div class="flex justify-between"> | |
<span class="font-medium">Product Catalog</span> | |
<span class="text-gray-500">1d ago</span> | |
</div> | |
<p class="text-gray-600 truncate">Added new product fields</p> | |
</div> | |
</div> | |
</div> | |
</div> | |
<!-- Main Content --> | |
<div class="flex-1"> | |
<!-- Data Panels Section --> | |
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-8"> | |
<!-- Source Data Panel --> | |
<div class="bg-white rounded-lg shadow overflow-hidden"> | |
<div class="bg-gradient-to-r from-indigo-50 to-indigo-100 px-6 py-4 border-b border-indigo-100 flex justify-between items-center"> | |
<h2 class="text-xl font-semibold text-gray-800 flex items-center"> | |
<i class="fas fa-database text-indigo-600 mr-3"></i> Source Data | |
</h2> | |
<div class="flex space-x-2"> | |
<div class="relative"> | |
<button id="source-actions-btn" class="px-3 py-1 bg-white hover:bg-gray-50 text-indigo-600 rounded text-sm transition flex items-center border border-gray-200"> | |
<i class="fas fa-ellipsis-h"></i> | |
</button> | |
<div id="source-actions-menu" class="hidden absolute right-0 mt-1 w-40 bg-white rounded-md shadow-lg z-10 border border-gray-200"> | |
<div class="py-1"> | |
<button id="sample-source-btn" class="w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 flex items-center"> | |
<i class="fas fa-magic mr-2 text-purple-500"></i> Sample Data | |
</button> | |
<button id="mysql-source-btn" class="w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 flex items-center"> | |
<i class="fas fa-database mr-2 text-blue-500"></i> MySQL | |
</button> | |
<button id="api-source-btn" class="w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 flex items-center"> | |
<i class="fas fa-plug mr-2 text-green-500"></i> API | |
</button> | |
<button id="clear-source-btn" class="w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 flex items-center"> | |
<i class="fas fa-trash-alt mr-2 text-red-500"></i> Clear | |
</button> | |
</div> | |
</div> | |
</div> | |
<button id="update-source-btn" class="px-3 py-1 bg-indigo-600 hover:bg-indigo-700 text-white rounded text-sm transition flex items-center"> | |
<i class="fas fa-sync-alt mr-1"></i> Update | |
</button> | |
</div> | |
</div> | |
<div class="p-4"> | |
<div class="flex mb-3 border-b border-gray-200"> | |
<button class="tab-button active mr-2" data-tab="json-source">JSON Editor</button> | |
<button class="tab-button mr-2" data-tab="upload-source">File Upload</button> | |
<button class="tab-button mr-2" data-tab="mysql-source">MySQL</button> | |
<button class="tab-button" data-tab="api-source">API</button> | |
</div> | |
<div id="source-drop-zone" class="drop-zone rounded-lg overflow-hidden"> | |
<!-- JSON Editor Tab --> | |
<div id="json-source" class="tab-content active h-full"> | |
<textarea id="source-data-json" class="json-editor w-full h-60 border border-gray-300 rounded-md p-3" spellcheck="false" placeholder='{ | |
"example": { | |
"id": 1, | |
"name": "Sample Data", | |
"details": { | |
"active": true, | |
"createdAt": "2023-01-01" | |
}, | |
"items": [ | |
{"id": 1, "name": "Item 1"}, | |
{"id": 2, "name": "Item 2"} | |
] | |
} | |
}'></textarea> | |
</div> | |
<!-- Upload Tab --> | |
<div id="upload-source" class="tab-content hidden h-full"> | |
<div class="text-center py-12 px-4"> | |
<div class="mx-auto w-16 h-16 flex items-center justify-center bg-indigo-50 rounded-full mb-4"> | |
<i class="fas fa-cloud-upload-alt text-2xl text-indigo-500"></i> | |
</div> | |
<p class="text-gray-600 mb-1">Drag & drop a JSON/CSV/XML file here</p> | |
<p class="text-xs text-gray-400 mb-4">Supports JSON, CSV, Excel, XML formats</p> | |
<input type="file" id="source-file-input" class="hidden" accept=".json,.csv,.xls,.xlsx,.xml"> | |
<button id="source-file-btn" class="px-4 py-2 bg-indigo-600 hover:bg-indigo-700 text-white rounded-md transition flex items-center mx-auto"> | |
<i class="fas fa-file-upload mr-2"></i> Select File | |
</button> | |
</div> | |
</div> | |
<!-- MySQL Tab --> | |
<div id="mysql-source" class="tab-content hidden"> | |
<div class="space-y-3"> | |
<div> | |
<label class="block text-sm font-medium text-gray-700 mb-1">MySQL Connection</label> | |
<input type="text" id="mysql-host" class="w-full border-gray-300 rounded-md shadow-sm focus:border-indigo-500 focus:ring-indigo-500" placeholder="Host" value="localhost"> | |
</div> | |
<div class="grid grid-cols-2 gap-3"> | |
<div> | |
<label class="block text-sm font-medium text-gray-700 mb-1">Username</label> | |
<input type="text" id="mysql-username" class="w-full border-gray-300 rounded-md shadow-sm focus:border-indigo-500 focus:ring-indigo-500" placeholder="Username" value="root"> | |
</div> | |
<div> | |
<label class="block text-sm font-medium text-gray-700 mb-1">Password</label> | |
<input type="password" id="mysql-password" class="w-full border-gray-300 rounded-md shadow-sm focus:border-indigo-500 focus:ring-indigo-500" placeholder="Password"> | |
</div> | |
</div> | |
<div> | |
<label class="block text-sm font-medium text-gray-700 mb-1">Database</label> | |
<input type="text" id="mysql-database" class="w-full border-gray-300 rounded-md shadow-sm focus:border-indigo-500 focus:ring-indigo-500" placeholder="Database"> | |
</div> | |
<div> | |
<label class="block text-sm font-medium text-gray-700 mb-1">Query</label> | |
<textarea id="mysql-query" class="w-full border-gray-300 rounded-md shadow-sm focus:border-indigo-500 focus:ring-indigo-500 h-20" placeholder="SELECT * FROM table"></textarea> | |
</div> | |
<button id="execute-mysql" class="w-full bg-indigo-600 hover:bg-indigo-700 text-white py-2 px-4 rounded-md transition flex items-center justify-center"> | |
<i class="fas fa-play mr-2"></i> Execute Query | |
</button> | |
</div> | |
</div> | |
<!-- API Tab --> | |
<div id="api-source" class="tab-content hidden"> | |
<div class="space-y-3"> | |
<div> | |
<label class="block text-sm font-medium text-gray-700 mb-1">API Endpoint</label> | |
<input type="text" id="api-endpoint" class="w-full border-gray-300 rounded-md shadow-sm focus:border-indigo-500 focus:ring-indigo-500" placeholder="https://api.example.com/data"> | |
</div> | |
<div> | |
<label class="block text-sm font-medium text-gray-700 mb-1">Method</label> | |
<select id="api-method" class="w-full border-gray-300 rounded-md shadow-sm focus:border-indigo-500 focus:ring-indigo-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 class="block text-sm font-medium text-gray-700 mb-1">Headers (JSON)</label> | |
<textarea id="api-headers" class="w-full border-gray-300 rounded-md shadow-sm focus:border-indigo-500 focus:ring-indigo-500 h-16 font-mono text-sm" placeholder='{"Authorization": "Bearer token"}'></textarea> | |
</div> | |
<div> | |
<label class="block text-sm font-medium text-gray-700 mb-1">Body (JSON)</label> | |
<textarea id="api-body" class="w-full border-gray-300 rounded-md shadow-sm focus:border-indigo-500 focus:ring-indigo-500 h-16 font-mono text-sm" placeholder='{"param1": "value1"}'></textarea> | |
</div> | |
<button id="execute-api" class="w-full bg-indigo-600 hover:bg-indigo-700 text-white py-2 px-4 rounded-md transition flex items-center justify-center"> | |
<i class="fas fa-plug mr-2"></i> Call API | |
</button> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
<!-- Target Data Panel --> | |
<div class="bg-white rounded-lg shadow overflow-hidden"> | |
<div class="bg-gradient-to-r from-green-50 to-green-100 px-6 py-4 border-b border-green-100 flex justify-between items-center"> | |
<h2 class="text-xl font-semibold text-gray-800 flex items-center"> | |
<i class="fas fa-bullseye text-green-600 mr-3"></i> Target Schema | |
</h2> | |
<div class="flex space-x-2"> | |
<div class="relative"> | |
<button id="target-actions-btn" class="px-3 py-1 bg-white hover:bg-gray-50 text-green-600 rounded text-sm transition flex items-center border border-gray-200"> | |
<i class="fas fa-ellipsis-h"></i> | |
</button> | |
<div id="target-actions-menu" class="hidden absolute right-0 mt-1 w-40 bg-white rounded-md shadow-lg z-10 border border-gray-200"> | |
<div class="py-1"> | |
<button id="sample-target-btn" class="w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 flex items-center"> | |
<i class="fas fa-magic mr-2 text-purple-500"></i> Sample Schema | |
</button> | |
<button id="mysql-target-btn" class="w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 flex items-center"> | |
<i class="fas fa-database mr-2 text-blue-500"></i> MySQL | |
</button> | |
<button id="api-target-btn" class="w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 flex items-center"> | |
<i class="fas fa-plug mr-2 text-green-500"></i> API | |
</button> | |
<button id="clear-target-btn" class="w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 flex items-center"> | |
<i class="fas fa-trash-alt mr-2 text-red-500"></i> Clear | |
</button> | |
</div> | |
</div> | |
</div> | |
<button id="update-target-btn" class="px-3 py-1 bg-green-600 hover:bg-green-700 text-white rounded text-sm transition flex items-center"> | |
<i class="fas fa-sync-alt mr-1"></i> Update | |
</button> | |
</div> | |
</div> | |
<div class="p-4"> | |
<div class="flex mb-3 border-b border-gray-200"> | |
<button class="tab-button active mr-2" data-tab="json-target">JSON Editor</button> | |
<button class="tab-button mr-2" data-tab="upload-target">File Upload</button> | |
<button class="tab-button mr-2" data-tab="mysql-target">MySQL</button> | |
<button class="tab-button" data-tab="api-target">API</button> | |
</div> | |
<div id="target-drop-zone" class="drop-zone rounded-lg overflow-hidden"> | |
<!-- JSON Editor Tab --> | |
<div id="json-target" class="tab-content active h-full"> | |
<textarea id="target-schema-json" class="json-editor w-full h-60 border border-gray-300 rounded-md p-3" spellcheck="false" placeholder='{ | |
"example": { | |
"id": null, | |
"fullName": null, | |
"email": null, | |
"isActive": null, | |
"metadata": { | |
"createdDate": null, | |
"modifiedDate": null | |
}, | |
"products": [ | |
{"productId": null, "productName": null} | |
] | |
} | |
}'></textarea> | |
</div> | |
<!-- Upload Tab --> | |
<div id="upload-target" class="tab-content hidden h-full"> | |
<div class="text-center py-12 px-4"> | |
<div class="mx-auto w-16 h-16 flex items-center justify-center bg-green-50 rounded-full mb-4"> | |
<i class="fas fa-cloud-upload-alt text-2xl text-green-500"></i> | |
</div> | |
<p class="text-gray-600 mb-1">Drag & drop a schema file here</p> | |
<p class="text-xs text-gray-400 mb-4">Supports JSON Schema, OpenAPI formats</p> | |
<input type="file" id="target-file-input" class="hidden" accept=".json,.yaml,.yml"> | |
<button id="target-file-btn" class="px-4 py-2 bg-green-600 hover:bg-green-700 text-white rounded-md transition flex items-center mx-auto"> | |
<i class="fas fa-file-upload mr-2"></i> Select File | |
</button> | |
</div> | |
</div> | |
<!-- MySQL Tab --> | |
<div id="mysql-target" class="tab-content hidden"> | |
<div class="space-y-3"> | |
<div> | |
<label class="block text-sm font-medium text-gray-700 mb-1">MySQL Connection</label> | |
<input type="text" id="mysql-target-host" class="w-full border-gray-300 rounded-md shadow-sm focus:border-indigo-500 focus:ring-indigo-500" placeholder="Host" value="localhost"> | |
</div> | |
<div class="grid grid-cols-2 gap-3"> | |
<div> | |
<label class="block text-sm font-medium text-gray-700 mb-1">Username</label> | |
<input type="text" id="mysql-target-username" class="w-full border-gray-300 rounded-md shadow-sm focus:border-indigo-500 focus:ring-indigo-500" placeholder="Username" value="root"> | |
</div> | |
<div> | |
<label class="block text-sm font-medium text-gray-700 mb-1">Password</label> | |
<input type="password" id="mysql-target-password" class="w-full border-gray-300 rounded-md shadow-sm focus:border-indigo-500 focus:ring-indigo-500" placeholder="Password"> | |
</div> | |
</div> | |
<div> | |
<label class="block text-sm font-medium text-gray-700 mb-1">Database</label> | |
<input type="text" id="mysql-target-database" class="w-full border-gray-300 rounded-md shadow-sm focus:border-indigo-500 focus:ring-indigo-500" placeholder="Database"> | |
</div> | |
<div> | |
<label class="block text-sm font-medium text-gray-700 mb-1">Table</label> | |
<select id="mysql-target-table" class="w-full border-gray-300 rounded-md shadow-sm focus:border-indigo-500 focus:ring-indigo-500"> | |
<option value="">Select a table</option> | |
<!-- Tables will be populated here --> | |
</select> | |
</div> | |
<button id="load-table-schema" class="w-full bg-green-600 hover:bg-green-700 text-white py-2 px-4 rounded-md transition flex items-center justify-center"> | |
<i class="fas fa-table mr-2"></i> Load Table Schema | |
</button> | |
</div> | |
</div> | |
<!-- API Tab --> | |
<div id="api-target" class="tab-content hidden"> | |
<div class="space-y-3"> | |
<div> | |
<label class="block text-sm font-medium text-gray-700 mb-1">API Endpoint</label> | |
<input type="text" id="api-target-endpoint" class="w-full border-gray-300 rounded-md shadow-sm focus:border-indigo-500 focus:ring-indigo-500" placeholder="https://api.example.com/data"> | |
</div> | |
<div> | |
<label class="block text-sm font-medium text-gray-700 mb-1">Authentication</label> | |
<select id="api-target-auth" class="w-full border-gray-300 rounded-md shadow-sm focus:border-indigo-500 focus:ring-indigo-500"> | |
<option value="none">None</option> | |
<option value="basic">Basic Auth</option> | |
<option value="bearer">Bearer Token</option> | |
<option value="api-key">API Key</option> | |
</select> | |
</div> | |
<div id="api-target-auth-fields" class="hidden space-y-2"> | |
<input type="text" id="api-target-username" class="w-full border-gray-300 rounded-md shadow-sm focus:border-indigo-500 focus:ring-indigo-500" placeholder="Username"> | |
<input type="password" id="api-target-password" class="w-full border-gray-300 rounded-md shadow-sm focus:border-indigo-500 focus:ring-indigo-500" placeholder="Password"> | |
<input type="text" id="api-target-token" class="w-full border-gray-300 rounded-md shadow-sm focus:border-indigo-500 focus:ring-indigo-500" placeholder="Token"> | |
<input type="text" id="api-target-key" class="w-full border-gray-300 rounded-md shadow-sm focus:border-indigo-500 focus:ring-indigo-500" placeholder="API Key"> | |
</div> | |
<button id="load-api-schema" class="w-full bg-green-600 hover:bg-green-700 text-white py-2 px-4 rounded-md transition flex items-center justify-center"> | |
<i class="fas fa-plug mr-2"></i> Load API Schema | |
</button> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
<!-- Mapping Section --> | |
<div class="bg-white rounded-lg shadow overflow-hidden mb-8"> | |
<div class="bg-gradient-to-r from-purple-50 to-purple-100 px-6 py-4 border-b border-purple-100 flex justify-between items-center"> | |
<h2 class="text-xl font-semibold text-gray-800 flex items-center"> | |
<i class="fas fa-project-diagram text-purple-600 mr-3"></i> Field Mappings | |
</h2> | |
<div class="flex space-x-2"> | |
<button id="auto-map-btn" class="px-3 py-1 bg-purple-600 hover:bg-purple-700 text-white rounded text-sm transition flex items-center"> | |
<i class="fas fa-robot mr-1"></i> Auto-Match | |
</button> | |
<button id="save-mapping-btn" class="px-3 py-1 bg-indigo-600 hover:bg-indigo-700 text-white rounded text-sm transition flex items-center"> | |
<i class="fas fa-save mr-1"></i> Save | |
</button> | |
<button id="clear-mappings-btn" class="px-3 py-1 bg-gray-100 hover:bg-gray-200 text-gray-700 rounded text-sm transition flex items-center"> | |
<i class="fas fa-trash-alt mr-1"></i> Clear All | |
</button> | |
</div> | |
</div> | |
<div class="p-6"> | |
<div id="mapping-container" class="space-y-3"> | |
<div id="no-mappings-message" class="text-center py-8 text-gray-500"> | |
<div class="mx-auto w-16 h-16 flex items-center justify-center bg-gray-100 rounded-full mb-4"> | |
<i class="fas fa-exchange-alt text-gray-400 text-xl"></i> | |
</div> | |
<p class="text-gray-500">No mappings created yet.</p> | |
<p class="text-sm text-gray-400">Add mappings by selecting fields below.</p> | |
</div> | |
<!-- Mappings will be added here dynamically --> | |
</div> | |
<div class="mt-6 grid grid-cols-1 md:grid-cols-2 gap-4"> | |
<div> | |
<label class="block text-sm font-medium text-gray-700 mb-1 flex items-center"> | |
<i class="fas fa-database text-indigo-500 mr-2 text-sm"></i> Source Field | |
</label> | |
<select id="source-field-select" class="w-full border-gray-300 rounded-md shadow-sm focus:border-indigo-500 focus:ring-indigo-500"> | |
<option value="">Select a source field</option> | |
<!-- Source fields will be populated here --> | |
</select> | |
</div> | |
<div> | |
<label class="block text-sm font-medium text-gray-700 mb-1 flex items-center"> | |
<i class="fas fa-bullseye text-green-500 mr-2 text-sm"></i> Target Field | |
</label> | |
<select id="target-field-select" class="w-full border-gray-300 rounded-md shadow-sm focus:border-indigo-500 focus:ring-indigo-500"> | |
<option value="">Select a target field</option> | |
<!-- Target fields will be populated here --> | |
</select> | |
</div> | |
</div> | |
<div class="mt-4"> | |
<label class="block text-sm font-medium text-gray-700 mb-2 flex items-center"> | |
<i class="fas fa-cogs text-purple-500 mr-2 text-sm"></i> Transformation Rules | |
</label> | |
<div class="grid grid-cols-1 md:grid-cols-4 gap-4"> | |
<div> | |
<select id="transform-select" class="w-full border-gray-300 rounded-md shadow-sm focus:border-indigo-500 focus:ring-indigo-500"> | |
<option value="none">No Transformation</option> | |
<optgroup label="Text Transformations"> | |
<option value="uppercase">Uppercase</option> | |
<option value="lowercase">Lowercase</option> | |
<option value="capitalize">Capitalize</option> | |
<option value="trim">Trim Whitespace</option> | |
<option value="replace">Replace Text</option> | |
<option value="concat">Concatenate</option> | |
<option value="substring">Substring</option> | |
</optgroup> | |
<optgroup label="Number Transformations"> | |
<option value="parseInt">Convert to Integer</option> | |
<option value="parseFloat">Convert to Float</option> | |
<option value="round">Round Number</option> | |
<option value="formatNumber">Format Number</option> | |
</optgroup> | |
<optgroup label="Date Transformations"> | |
<option value="parseDate">Parse Date</option> | |
<option value="formatDate">Format Date</option> | |
<option value="dateAdd">Add to Date</option> | |
</optgroup> | |
<optgroup label="Logical Transformations"> | |
<option value="ifElse">If-Else</option> | |
<option value="defaultValue">Default Value</option> | |
<option value="lookup">Lookup Value</option> | |
</optgroup> | |
<optgroup label="Custom Transformations"> | |
<option value="custom">Custom Function</option> | |
<option value="multiple">Multiple Fields</option> | |
</optgroup> | |
</select> | |
</div> | |
<div id="transform-param-container" class="hidden"> | |
<input type="text" id="transform-param" class="w-full border-gray-300 rounded-md shadow-sm focus:border-indigo-500 focus:ring-indigo-500" placeholder="Parameter"> | |
</div> | |
<div id="transform-param2-container" class="hidden"> | |
<input type="text" id="transform-param2" class="w-full border-gray-300 rounded-md shadow-sm focus:border-indigo-500 focus:ring-indigo-500" placeholder="Second parameter"> | |
</div> | |
<div class="flex items-end"> | |
<button id="add-mapping-btn" class="w-full bg-indigo-600 hover:bg-indigo-700 text-white py-2 px-4 rounded-md transition flex items-center justify-center"> | |
<i class="fas fa-plus mr-2"></i> Add Mapping | |
</button> | |
</div> | |
</div> | |
<!-- Transformation Examples --> | |
<div id="transform-examples" class="mt-6 grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-3 hidden"> | |
<h3 class="col-span-full text-sm font-medium text-gray-700">Common Transformations:</h3> | |
<div class="transform-card p-3 border border-gray-200 rounded-md cursor-pointer"> | |
<div class="flex justify-between items-start mb-1"> | |
<span class="text-xs font-medium text-gray-600">Email Formatting</span> | |
<button class="text-xs text-indigo-600 hover:text-indigo-800">Use</button> | |
</div> | |
<p class="text-xs text-gray-500">Trim and lowercase email addresses</p> | |
</div> | |
<div class="transform-card p-3 border border-gray-200 rounded-md cursor-pointer"> | |
<div class="flex justify-between items-start mb-1"> | |
<span class="text-xs font-medium text-gray-600">Date Conversion</span> | |
<button class="text-xs text-indigo-600 hover:text-indigo-800">Use</button> | |
</div> | |
<p class="text-xs text-gray-500">Convert "MM/DD/YYYY" to "YYYY-MM-DD"</p> | |
</div> | |
<div class="transform-card p-3 border border-gray-200 rounded-md cursor-pointer"> | |
<div class="flex justify-between items-start mb-1"> | |
<span class="text-xs font-medium text-gray-600">Full Name</span> | |
<button class="text-xs text-indigo-600 hover:text-indigo-800">Use</button> | |
</div> | |
<p class="text-xs text-gray-500">Combine first + ' ' + last name</p> | |
</div> | |
<div class="transform-card p-3 border border-gray-200 rounded-md cursor-pointer"> | |
<div class="flex justify-between items-start mb-1"> | |
<span class="text-xs font-medium text-gray-600">Default Value</span> | |
<button class="text-xs text-indigo-600 hover:text-indigo-800">Use</button> | |
</div> | |
<p class="text-xs text-gray-500">If empty, use "Unknown"</p> | |
</div> | |
<div class="transform-card p-3 border border-gray-200 rounded-md cursor-pointer"> | |
<div class="flex justify-between items-start mb-1"> | |
<span class="text-xs font-medium text-gray-600">Status Mapping</span> | |
<button class="text-xs text-indigo-600 hover:text-indigo-800">Use</button> | |
</div> | |
<p class="text-xs text-gray-500">Map "A" to "Active", "I" to "Inactive"</p> | |
</div> | |
<div class="transform-card p-3 border border-gray-200 rounded-md cursor-pointer"> | |
<div class="flex justify-between items-start mb-1"> | |
<span class="text-xs font-medium text-gray-600">Currency Format</span> | |
<button class="text-xs text-indigo-600 hover:text-indigo-800">Use</button> | |
</div> | |
<p class="text-xs text-gray-500">Format to 2 decimal places</p> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
<!-- Results Section --> | |
<div class="bg-white rounded-lg shadow overflow-hidden"> | |
<div class="bg-gradient-to-r from-orange-50 to-orange-100 px-6 py-4 border-b border-orange-100 flex justify-between items-center"> | |
<h2 class="text-xl font-semibold text-gray-800 flex items-center"> | |
<i class="fas fa-code text-orange-600 mr-3"></i> Mapped Output | |
</h2> | |
<div class="flex space-x-2"> | |
<button id="preview-sql-btn" class="px-3 py-1 bg-orange-100 hover:bg-orange-200 text-orange-700 rounded text-sm transition flex items-center"> | |
<i class="fas fa-eye mr-1"></i> SQL Preview | |
</button> | |
<button id="copy-output-btn" class="px-3 py-1 bg-orange-100 hover:bg-orange-200 text-orange-700 rounded text-sm transition flex items-center"> | |
<i class="fas fa-copy mr-1"></i> Copy | |
</button> | |
<button id="export-json-btn" class="px-3 py-1 bg-orange-100 hover:bg-orange-200 text-orange-700 rounded text-sm transition flex items-center"> | |
<i class="fas fa-file-export mr-1"></i> Export | |
</button> | |
<button id="save-to-db-btn" class="px-3 py-1 bg-green-600 hover:bg-green-700 text-white rounded text-sm transition flex items-center"> | |
<i class="fas fa-save mr-1"></i> Save to DB | |
</button> | |
</div> | |
</div> | |
<div class="p-6"> | |
<div id="output-empty-state" class="text-center py-8 text-gray-500"> | |
<div class="mx-auto w-16 h-16 flex items-center justify-center bg-orange-50 rounded-full mb-4"> | |
<i class="fas fa-code-branch text-orange-400 text-xl"></i> | |
</div> | |
<p class="text-gray-500">Mapped output will appear here.</p> | |
<p class="text-sm text-gray-400 mt-1">Execute your mappings to see the results.</p> | |
</div> | |
<div id="output-section" class="hidden"> | |
<div class="flex border-b border-gray-200 mb-4"> | |
<button class="tab-button active mr-4" data-tab="json-output">JSON</button> | |
<button class="tab-button mr-4" data-tab="table-output">Table</button> | |
<button class="tab-button" data-tab="sql-output">SQL</button> | |
</div> | |
<div id="json-output" class="tab-content"> | |
<div class="relative"> | |
<select id="output-format-select" class="absolute right-0 top-0 z-10 text-xs border border-gray-300 rounded p-1 bg-white"> | |
<option value="formatted">Formatted</option> | |
<option value="compact">Compact</option> | |
</select> | |
<pre id="output-result" class="hljs overflow-x-auto rounded-md p-4 max-h-96"><code>Your output will appear here</code></pre> | |
</div> | |
</div> | |
<div id="table-output" class="tab-content hidden"> | |
<div class="overflow-x-auto"> | |
<table class="min-w-full divide-y divide-gray-200"> | |
<thead class="bg-gray-50"> | |
<tr> | |
<th scope="col" class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Field</th> | |
<th scope="col" class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Value</th> | |
</tr> | |
</thead> | |
<tbody id="output-table-body" class="bg-white divide-y divide-gray-200"> | |
<!-- Table rows will be added here --> | |
</tbody> | |
</table> | |
</div> | |
</div> | |
<div id="sql-output" class="tab-content hidden"> | |
<div class="space-y-4"> | |
<div class="flex justify-between"> | |
<select id="sql-output-type" class="text-sm border border-gray-300 rounded p-1 bg-white"> | |
<option value="insert">INSERT</option> | |
<option value="update">UPDATE</option> | |
<option value="merge">MERGE</option> | |
</select> | |
<div class="flex items-center space-x-2"> | |
<span class="text-sm text-gray-500">Table:</span> | |
<input type="text" id="sql-table-name" class="text-sm border border-gray-300 rounded p-1" placeholder="table_name" value="target_table"> | |
</div> | |
</div> | |
<pre id="sql-preview-container" class="hljs overflow-x-auto rounded-md p-4 max-h-64"><code>SQL statements will appear here</code></pre> | |
<div class="flex justify-end"> | |
<button id="copy-sql-btn" class="px-3 py-1 bg-gray-100 hover:bg-gray-200 text-gray-700 rounded text-sm transition flex items-center"> | |
<i class="fas fa-copy mr-1"></i> Copy SQL | |
</button> | |
</div> | |
</div> | |
</div> | |
<div class="mt-4"> | |
<button id="execute-mapping-btn" class="w-full bg-indigo-600 hover:bg-indigo-700 text-white py-2 px-4 rounded-md transition flex items-center justify-center"> | |
<i class="fas fa-play mr-2"></i> Execute Mapping | |
</button> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
<!-- Right Sidebar --> | |
<div id="right-sidebar" class="w-64 bg-white rounded-lg shadow p-4 shrink-0 relative"> | |
<button id="toggle-right-sidebar" class="sidebar-toggle absolute top-1/2 -translate-y-1/2 bg-white border border-gray-200 rounded-full w-6 h-6 flex items-center justify-center shadow-sm hover:bg-gray-50"> | |
<i class="fas fa-chevron-right text-gray-500 text-xs"></i> | |
</button> | |
<h3 class="font-medium text-gray-700 mb-3 flex items-center"> | |
<i class="fas fa-cog text-gray-500 mr-2"></i> Settings | |
</h3> | |
<div class="space-y-4"> | |
<div> | |
<label class="block text-xs font-medium text-gray-500 mb-1">JSON Formatting</label> | |
<div class="flex items-center"> | |
<span class="text-xs mr-2">Spaces:</span> | |
<input type="number" id="json-indent" min="0" max="8" value="2" class="w-12 text-xs border border-gray-300 rounded px-1 py-0.5"> | |
</div> | |
</div> | |
<div> | |
<label class="block text-xs font-medium text-gray-500 mb-1">Date Format</label> | |
<select id="date-format" class="w-full text-xs border border-gray-300 rounded px-2 py-1"> | |
<option value="YYYY-MM-DD">YYYY-MM-DD (ISO)</option> | |
<option value="MM/DD/YYYY">MM/DD/YYYY (US)</option> | |
<option value="DD/MM/YYYY">DD/MM/YYYY (EU)</option> | |
<option value="custom">Custom...</option> | |
</select> | |
<div id="custom-date-format-container" class="hidden mt-1"> | |
<input type="text" id="custom-date-format" class="w-full text-xs border border-gray-300 rounded px-2 py-1" placeholder="YYYY-MM-DD HH:mm"> | |
</div> | |
</div> | |
<div> | |
<label class="block text-xs font-medium text-gray-500 mb-1">Auto-save</label> | |
<div class="flex items-center"> | |
<input type="checkbox" id="auto-save" class="rounded text-indigo-600 focus:ring-indigo-500"> | |
<label for="auto-save" class="ml-2 text-xs text-gray-700">Enable</label> | |
</div> | |
</div> | |
<div> | |
<label class="block text-xs font-medium text-gray-500 mb-1">Advanced</label> | |
<button id="open-db-config" class="w-full text-left text-xs px-3 py-1 bg-gray-100 hover:bg-gray-200 rounded-md flex items-center"> | |
<i class="fas fa-database mr-2 text-gray-500"></i> Database Config | |
</button> | |
</div> | |
</div> | |
<div class="mt-8"> | |
<h3 class="font-medium text-gray-700 mb-3 flex items-center"> | |
<i class="fas fa-chart-line text-blue-500 mr-2"></i> Stats | |
</h3> | |
<div class="space-y-2 text-xs"> | |
<div class="flex justify-between"> | |
<span class="text-gray-500">Total Mappings:</span> | |
<span id="total-mappings" class="font-medium">0</span> | |
</div> | |
<div class="flex justify-between"> | |
<span class="text-gray-500">Source Fields:</span> | |
<span id="source-fields-count" class="font-medium">0</span> | |
</div> | |
<div class="flex justify-between"> | |
<span class="text-gray-500">Target Fields:</span> | |
<span id="target-fields-count" class="font-medium">0</span> | |
</div> | |
<div class="flex justify-between"> | |
<span class="text-gray-500">Transformations:</span> | |
<span id="transformations-count" class="font-medium">0</span> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
<!-- Modals --> | |
<div id="saved-mappings-modal" class="fixed inset-0 bg-gray-600 bg-opacity-50 flex items-center justify-center z-50 hidden"> | |
<div class="bg-white rounded-lg shadow-xl w-full max-w-2xl max-h-[80vh] overflow-hidden"> | |
<div class="border-b border-gray-200 px-6 py-4 flex justify-between items-center"> | |
<h3 class="text-lg font-semibold text-gray-800 flex items-center"> | |
<i class="fas fa-folder-open mr-2 text-indigo-500"></i> Saved Mappings | |
</h3> | |
<div class="flex items-center space-x-2"> | |
<input type="text" id="search-mappings" class="text-sm border border-gray-300 rounded px-3 py-1 w-64" placeholder="Search mappings..."> | |
<button id="close-saved-modal" class="text-gray-400 hover:text-gray-500"> | |
<i class="fas fa-times"></i> | |
</button> | |
</div> | |
</div> | |
<div class="p-6 overflow-y-auto"> | |
<div id="saved-mappings-list" class="grid grid-cols-1 md:grid-cols-2 gap-3"> | |
<!-- Saved mappings will appear here --> | |
</div> | |
<div id="no-saved-mappings" class="text-center py-8 text-gray-500"> | |
<div class="mx-auto w-16 h-16 flex items-center justify-center bg-gray-100 rounded-full mb-4"> | |
<i class="fas fa-inbox text-gray-400"></i> | |
</div> | |
<p>No saved mappings found.</p> | |
<p class="text-sm mt-1">Create mappings to save them for later use.</p> | |
</div> | |
</div> | |
<div class="border-t border-gray-200 px-6 py-4 flex justify-between items-center"> | |
<button id="import-mapping-btn" class="px-3 py-1.5 bg-gray-100 hover:bg-gray-200 text-gray-700 rounded-lg transition flex items-center"> | |
<i class="fas fa-file-import mr-2"></i> Import | |
</button> | |
<div class="flex space-x-2"> | |
<button id="cancel-saved-modal" class="px-4 py-1.5 bg-gray-100 text-gray-700 rounded-lg hover:bg-gray-200 transition"> | |
Close | |
</button> | |
</div> | |
</div> | |
</div> | |
</div> | |
<!-- Save Mapping Modal --> | |
<div id="save-mapping-modal" class="fixed inset-0 bg-gray-600 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="border-b border-gray-200 px-6 py-4"> | |
<h3 class="text-lg font-semibold text-gray-800 flex items-center"> | |
<i class="fas fa-save mr-2 text-indigo-500"></i> Save Mapping | |
</h3> | |
</div> | |
<div class="p-6 space-y-4"> | |
<div> | |
<label class="block text-sm font-medium text-gray-700 mb-1">Mapping Name*</label> | |
<input type="text" id="mapping-name" class="w-full border-gray-300 rounded-md shadow-sm focus:border-indigo-500 focus:ring-indigo-500" placeholder="e.g. Customer Data Import"> | |
</div> | |
<div> | |
<label class="block text-sm font-medium text-gray-700 mb-1">Description</label> | |
<textarea id="mapping-description" class="w-full border-gray-300 rounded-md shadow-sm focus:border-indigo-500 focus:ring-indigo-500" rows="2" placeholder="Describe this mapping configuration"></textarea> | |
</div> | |
<div> | |
<label class="block text-sm font-medium text-gray-700 mb-1">Tags</label> | |
<div class="flex flex-wrap gap-2 items-center"> | |
<input type="text" id="mapping-tags" class="flex-1 min-w-0 border-gray-300 rounded-md shadow-sm focus:border-indigo-500 focus:ring-indigo-500" placeholder="Add tags (comma separated)"> | |
<div id="tags-display" class="flex flex-wrap gap-1"></div> | |
</div> | |
</div> | |
<div> | |
<label class="block text-sm font-medium text-gray-700 mb-1">Visibility</label> | |
<div class="flex space-x-4"> | |
<div class="flex items-center"> | |
<input id="visibility-public" name="visibility" type="radio" class="h-4 w-4 text-indigo-600 focus:ring-indigo-500"> | |
<label for="visibility-public" class="ml-2 block text-sm text-gray-700">Public</label> | |
</div> | |
<div class="flex items-center"> | |
<input id="visibility-private" name="visibility" type="radio" checked class="h-4 w-4 text-indigo-600 focus:ring-indigo-500"> | |
<label for="visibility-private" class="ml-2 block text-sm text-gray-700">Private</label> | |
</div> | |
</div> | |
</div> | |
</div> | |
<div class="border-t border-gray-200 px-6 py-4 flex justify-end space-x-3"> | |
<button id="cancel-save-mapping" class="px-4 py-1.5 bg-gray-100 text-gray-700 rounded-lg hover:bg-gray-200 transition"> | |
Cancel | |
</button> | |
<button id="confirm-save-mapping" class="px-4 py-1.5 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700 transition flex items-center"> | |
<i class="fas fa-save mr-2"></i> Save | |
</button> | |
</div> | |
</div> | |
</div> | |
<!-- Database Config Modal --> | |
<div id="db-config-modal" class="fixed inset-0 bg-gray-600 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="border-b border-gray-200 px-6 py-4"> | |
<h3 class="text-lg font-semibold text-gray-800 flex items-center"> | |
<i class="fas fa-database mr-2 text-gray-500"></i> Database Configuration | |
</h3> | |
</div> | |
<div class="p-6 space-y-4"> | |
<div> | |
<label class="block text-sm font-medium text-gray-700 mb-1">Host*</label> | |
<input type="text" id="config-host" class="w-full border-gray-300 rounded-md shadow-sm focus:border-indigo-500 focus:ring-indigo-500" value="localhost"> | |
</div> | |
<div> | |
<label class="block text-sm font-medium text-gray-700 mb-1">Port</label> | |
<input type="number" id="config-port" class="w-full border-gray-300 rounded-md shadow-sm focus:border-indigo-500 focus:ring-indigo-500" value="3306"> | |
</div> | |
<div> | |
<label class="block text-sm font-medium text-gray-700 mb-1">Database*</label> | |
<input type="text" id="config-database" class="w-full border-gray-300 rounded-md shadow-sm focus:border-indigo-500 focus:ring-indigo-500" value="data_mapper"> | |
</div> | |
<div> | |
<label class="block text-sm font-medium text-gray-700 mb-1">Username*</label> | |
<input type="text" id="config-username" class="w-full border-gray-300 rounded-md shadow-sm focus:border-indigo-500 focus:ring-indigo-500" value="root"> | |
</div> | |
<div> | |
<label class="block text-sm font-medium text-gray-700 mb-1">Password</label> | |
<input type="password" id="config-password" class="w-full border-gray-300 rounded-md shadow-sm focus:border-indigo-500 focus:ring-indigo-500"> | |
</div> | |
<div> | |
<label class="block text-sm font-medium text-gray-700 mb-1">Connection Name (Optional)</label> | |
<input type="text" id="config-name" class="w-full border-gray-300 rounded-md shadow-sm focus:border-indigo-500 focus:ring-indigo-500" placeholder="e.g. Production DB"> | |
</div> | |
</div> | |
<div class="border-t border-gray-200 px-6 py-4 flex justify-between"> | |
<div> | |
<button id="test-db-connection" class="px-4 py-1.5 bg-blue-100 text-blue-600 rounded-lg hover:bg-blue-200 transition flex items-center"> | |
<i class="fas fa-plug mr-2"></i> Test | |
</button> | |
</div> | |
<div class="flex space-x-3"> | |
<button id="cancel-db-config" class="px-4 py-1.5 bg-gray-100 text-gray-700 rounded-lg hover:bg-gray-200 transition"> | |
Cancel | |
</button> | |
<button id="save-db-config" class="px-4 py-1.5 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700 transition"> | |
Save | |
</button> | |
</div> | |
</div> | |
</div> | |
</div> | |
<!-- Help Modal --> | |
<div id="help-modal" class="fixed inset-0 bg-gray-600 bg-opacity-50 flex items-center justify-center z-50 hidden"> | |
<div class="bg-white rounded-lg shadow-xl w-full max-w-3xl max-h-[80vh] overflow-hidden"> | |
<div class="border-b border-gray-200 px-6 py-4 flex justify-between items-center"> | |
<h3 class="text-lg font-semibold text-gray-800 flex items-center"> | |
<i class="fas fa-question-circle mr-2 text-blue-500"></i> Help Center | |
</h3> | |
<button id="close-help-modal" class="text-gray-400 hover:text-gray-500"> | |
<i class="fas fa-times"></i> | |
</button> | |
</div> | |
<div class="p-6 overflow-y-auto"> | |
<div class="prose prose-sm max-w-none"> | |
<h3 class="text-lg font-medium mb-3">Getting Started</h3> | |
<p>The Data Mapper Pro allows you to define mappings between different data formats. Follow these steps:</p> | |
<ol class="list-decimal pl-5 space-y-2 my-3"> | |
<li>Load or input your source data (JSON, database, API, file)</li> | |
<li>Define your target schema (JSON structure or database table)</li> | |
<li>Create field mappings between source and target</li> | |
<li>Apply transformations as needed</li> | |
<li>Execute mappings and view/export results</li> | |
</ol> | |
<h3 class="text-lg font-medium mb-3 mt-6">Common Transformations</h3> | |
<div class="grid grid-cols-1 md:grid-cols-2 gap-3"> | |
<div class="border rounded-md p-3"> | |
<h4 class="font-medium text-indigo-600 mb-1">Text Processing</h4> | |
<ul class="list-disc pl-5 text-sm space-y-1"> | |
<li><code>trim</code> - Remove whitespace</li> | |
<li><code>uppercase</code> - Convert to uppercase</li> | |
<li><code>replace</code> - Find and replace text</li> | |
<li><code>concat</code> - Combine multiple fields</li> | |
</ul> | |
</div> | |
<div class="border rounded-md p-3"> | |
<h4 class="font-medium text-indigo-600 mb-1">Date & Numbers</h4> | |
<ul class="list-disc pl-5 text-sm space-y-1"> | |
<li><code>parseDate</code> - Convert string to date</li> | |
<li><code>formatDate</code> - Format date string</li> | |
<li><code>parseInt/Float</code> - Convert to number</li> | |
<li><code>round</code> - Round decimal places</li> | |
</ul> | |
</div> | |
<div class="border rounded-md p-3"> | |
<h4 class="font-medium text-indigo-600 mb-1">Logical Operations</h4> | |
<ul class="list-disc pl-5 text-sm space-y-1"> | |
<li><code>ifElse</code> - Conditional logic</li> | |
<li><code>defaultValue</code> - Fallback if empty</li> | |
<li><code>lookup</code> - Value mapping</li> | |
<li><code>custom</code> - JavaScript function</li> | |
</ul> | |
</div> | |
<div class="border rounded-md p-3"> | |
<h4 class="font-medium text-indigo-600 mb-1">More Features</h4> | |
<ul class="list-disc pl-5 text-sm space-y-1"> | |
<li>Auto-match similar fields</li> | |
<li>Save and reuse mappings</li> | |
<li>Preview SQL statements</li> | |
<li>Export to multiple formats</li> | |
</ul> | |
</div> | |
</div> | |
<h3 class="text-lg font-medium mb-3 mt-6">Keyboard Shortcuts</h3> | |
<div class="bg-gray-50 rounded-md p-3"> | |
<div class="grid grid-cols-2 gap-2 text-sm"> | |
<div class="flex items-center"> | |
<kbd class="bg-white border rounded px-2 py-1 mr-2 font-mono">Ctrl+Alt+D</kbd> | |
<span>Database config</span> | |
</div> | |
<div class="flex items-center"> | |
<kbd class="bg-white border rounded px-2 py-1 mr-2 font-mono">Ctrl+S</kbd> | |
<span>Save mapping</span> | |
</div> | |
<div class="flex items-center"> | |
<kbd class="bg-white border rounded px-2 py-1 mr-2 font-mono">Ctrl+E</kbd> | |
<span>Execute mapping</span> | |
</div> | |
<div class="flex items-center"> | |
<kbd class="bg-white border rounded px-2 py-1 mr-2 font-mono">F1</kbd> | |
<span>Help</span> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
<div class="border-t border-gray-200 px-6 py-4 flex justify-end"> | |
<button id="close-help-btn" class="px-4 py-1.5 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700 transition"> | |
Close | |
</button> | |
</div> | |
</div> | |
</div> | |
<script> | |
$(document).ready(function() { | |
// Enable syntax highlighting | |
hljs.highlightAll(); | |
// Auto-resize textareas | |
autosize($('textarea')); | |
// Initialize dark mode toggle | |
const toggleDarkMode = () => { | |
$('body').toggleClass('dark bg-gray-900 text-gray-100'); | |
$('body').toggleClass('bg-gray-50 text-gray-800'); | |
$('.bg-white').toggleClass('bg-gray-800'); | |
$('.text-gray-700, .text-gray-800').toggleClass('text-gray-200'); | |
$('.text-gray-500, .text-gray-600').toggleClass('text-gray-400'); | |
$('.border-gray-200, .border-gray-300').toggleClass('border-gray-600'); | |
$('.bg-gray-50, .bg-gray-100').toggleClass('bg-gray-700'); | |
}; | |
$('#toggle-dark-mode').click(toggleDarkMode); | |
// Sidebar toggle functionality | |
$('#toggle-left-sidebar').click(function() { | |
$('#left-sidebar').toggleClass('hidden'); | |
$(this).find('i').toggleClass('fa-chevron-left fa-chevron-right'); | |
}); | |
$('#toggle-right-sidebar').click(function() { | |
$('#right-sidebar').toggleClass('hidden'); | |
$(this).find('i').toggleClass('fa-chevron-right fa-chevron-left'); | |
}); | |
// Menu toggles for source/target actions | |
$('#source-actions-btn').click(function() { | |
$('#source-actions-menu').toggleClass('hidden'); | |
}); | |
$('#target-actions-btn').click(function() { | |
$('#target-actions-menu').toggleClass('hidden'); | |
}); | |
// Close menus when clicking outside | |
$(document).click(function(e) { | |
if (!$(e.target).closest('#source-actions-btn, #source-actions-menu').length) { | |
$('#source-actions-menu').addClass('hidden'); | |
} | |
if (!$(e.target).closest('#target-actions-btn, #target-actions-menu').length) { | |
$('#target-actions-menu').addClass('hidden'); | |
} | |
}); | |
// Tab switching for all tabs | |
$('[data-tab]').click(function() { | |
const tabId = $(this).data('tab'); | |
const container = $(this).closest('.flex'); | |
// Handle tab buttons | |
container.find('[data-tab]').removeClass('active'); | |
$(this).addClass('active'); | |
// Handle tab contents | |
$(this).closest('.p-4').find('.tab-content').addClass('hidden'); | |
$(`#${tabId}`).removeClass('hidden'); | |
}); | |
// Output format tabs | |
$('[data-tab="json-output"], [data-tab="table-output"], [data-tab="sql-output"]').click(function() { | |
$('#output-section .tab-content').addClass('hidden'); | |
$(`#${$(this).data('tab')}`).removeClass('hidden'); | |
}); | |
// API target auth type change | |
$('#api-target-auth').change(function() { | |
$('#api-target-auth-fields').removeClass('hidden'); | |
// Hide all fields first | |
$('#api-target-username, #api-target-password, #api-target-token, #api-target-key').addClass('hidden'); | |
switch($(this).val()) { | |
case 'basic': | |
$('#api-target-username, #api-target-password').removeClass('hidden'); | |
break; | |
case 'bearer': | |
$('#api-target-token').removeClass('hidden'); | |
break; | |
case 'api-key': | |
$('#api-target-key').removeClass('hidden'); | |
break; | |
default: | |
$('#api-target-auth-fields').addClass('hidden'); | |
} | |
}); | |
// Date format selection | |
$('#date-format').change(function() { | |
if ($(this).val() === 'custom') { | |
$('#custom-date-format-container').removeClass('hidden'); | |
} else { | |
$('#custom-date-format-container').addClass('hidden'); | |
} | |
}); | |
// Transformation select functionality | |
$('#transform-select').change(function() { | |
// Basic parameter fields | |
const needsParam = ['replace', 'substring', 'formatNumber', 'formatDate', 'dateAdd', 'defaultValue', 'lookup', 'custom']; | |
const needsTwoParams = ['replace', 'substring', 'dateAdd']; | |
if (needsParam.includes($(this).val())) { | |
$('#transform-param-container').removeClass('hidden'); | |
$('#transform-param').attr('placeholder', | |
$(this).val() === 'replace' ? 'Text to replace (use $1 for captured groups)' : | |
$(this).val() === 'substring' ? 'Start index, e.g. 0,5' : | |
$(this).val() === 'formatNumber' ? 'Decimal places, e.g. 2' : | |
$(this).val() === 'formatDate' ? 'Format pattern, e.g. YYYY-MM-DD' : | |
$(this).val() === 'dateAdd' ? 'Amount to add, e.g. 1' : | |
$(this).val() === 'defaultValue' ? 'Default value if empty' : | |
$(this).val() === 'lookup' ? 'Key:value pairs, e.g. A:Active,I:Inactive' : | |
$(this).val() === 'custom' ? 'JS function, e.g. (val) => val.toUpperCase()' : 'Parameter'); | |
if (needsTwoParams.includes($(this).val())) { | |
$('#transform-param2-container').removeClass('hidden'); | |
$('#transform-param2').attr('placeholder', | |
$(this).val() === 'replace' ? 'Replacement text' : | |
$(this).val() === 'substring' ? 'End index' : | |
$(this).val() === 'dateAdd' ? 'Unit (days, months, years)' : 'Second parameter'); | |
} else { | |
$('#transform-param2-container').addClass('hidden'); | |
} | |
} else { | |
$('#transform-param-container').addClass('hidden'); | |
$('#transform-param2-container').addClass('hidden'); | |
} | |
// Show examples for complex transformations | |
if (['dateFormat', 'concat', 'custom', 'ifElse', 'lookup'].includes($(this).val())) { | |
$('#transform-examples').removeClass('hidden'); | |
} else { | |
$('#transform-examples').addClass('hidden'); | |
} | |
}); | |
// Help modal | |
$('#help-btn, #close-help-btn, #close-help-modal').click(function() { | |
$('#help-modal').toggleClass('hidden'); | |
}); | |
// Database config modal | |
$('#open-db-config, #cancel-db-config').click(function() { | |
$('#db-config-modal').toggleClass('hidden'); | |
}); | |
// Saved mappings modal | |
$('#open-saved-modal, #close-saved-modal, #cancel-saved-modal').click(function() { | |
$('#saved-mappings-modal').toggleClass('hidden'); | |
}); | |
// Save mapping modal | |
$('#save-mapping-btn, #cancel-save-mapping').click(function() { | |
$('#save-mapping-modal').toggleClass('hidden'); | |
}); | |
// Sample data buttons | |
$('#sample-source-btn').click(function() { | |
const sampleData = { | |
"customer": { | |
"id": 45678, | |
"name": "Jane Smith", | |
"email": " [email protected] ", | |
"status": "A", | |
"registration_date": "12/15/2022", | |
"address": { | |
"street": "456 Oak Ave", | |
"city": "Somewhere", | |
"zip": "54321", | |
"country": "USA" | |
}, | |
"preferences": { | |
"newsletter": true, | |
"notifications": false | |
}, | |
"orders": [ | |
{"id": "ORD-123", "amount": "125.99", "date": "01/05/2023"}, | |
{"id": "ORD-456", "amount": "89.50", "date": "03/12/2023"} | |
] | |
} | |
}; | |
loadSourceData(sampleData); | |
$('#source-data-json').val(JSON.stringify(sampleData, null, 2)); | |
showNotification('success', 'Sample source data loaded!'); | |
}); | |
$('#sample-target-btn').click(function() { | |
const sampleSchema = { | |
"user": { | |
"userId": null, | |
"fullName": null, | |
"emailAddress": null, | |
"status": null, | |
"joinDate": null, | |
"contactInfo": { | |
"street": null, | |
"city": null, | |
"zipCode": null, | |
"country": null | |
}, | |
"settings": { | |
"receivesNewsletter": null | |
}, | |
"purchases": [ | |
{"orderId": null, "totalAmount": null, "purchaseDate": null} | |
] | |
} | |
}; | |
loadTargetSchema(sampleSchema); | |
$('#target-schema-json').val(JSON.stringify(sampleSchema, null, 2)); | |
showNotification('success', 'Sample target schema loaded!'); | |
}); | |
// Clear buttons | |
$('#clear-source-btn').click(function() { | |
$('#source-data-json').val(''); | |
$('#source-field-select').empty().append('<option value="">Select a source field</option>'); | |
showNotification('info', 'Source data cleared'); | |
}); | |
$('#clear-target-btn').click(function() { | |
$('#target-schema-json').val(''); | |
$('#target-field-select').empty().append('<option value="">Select a target field</option>'); | |
showNotification('info', 'Target schema cleared'); | |
}); | |
$('#clear-mappings-btn').click(function() { | |
mappings = []; | |
renderMappings(); | |
$('#execute-mapping-btn').addClass('hidden'); | |
$('#output-result').addClass('hidden'); | |
$('#output-empty-state').removeClass('hidden'); | |
showNotification('info', 'All mappings cleared'); | |
}); | |
// File upload buttons | |
$('#source-file-btn, #target-file-btn').click(function() { | |
const isSource = $(this).attr('id') === 'source-file-btn'; | |
const fileInput = isSource ? $('#source-file-input') : $('#target-file-input'); | |
fileInput.click(); | |
}); | |
$('#source-file-input, #target-file-input').change(function() { | |
const isSource = $(this).attr('id') === 'source-file-input'; | |
const file = this.files[0]; | |
if (!file) return; | |
const reader = new FileReader(); | |
reader.onload = function(e) { | |
try { | |
const content = JSON.parse(e.target.result); | |
if (isSource) { | |
loadSourceData(content); | |
$('#source-data-json').val(JSON.stringify(content, null, 2)); | |
} else { | |
loadTargetSchema(content); | |
$('#target-schema-json').val(JSON.stringify(content, null, 2)); | |
} | |
showNotification('success', 'File loaded successfully!'); | |
} catch (error) { | |
showNotification('error', `Error parsing file: ${error.message}`); | |
} | |
}; | |
reader.readAsText(file); | |
}); | |
// Update source/target from JSON | |
$('#update-source-btn').click(function() { | |
try { | |
const json = JSON.parse($('#source-data-json').val() || 'null'); | |
if (json) { | |
loadSourceData(json); | |
showNotification('success', 'Source data updated!'); | |
} else { | |
showNotification('error', 'Please enter valid JSON data'); | |
} | |
} catch (error) { | |
showNotification('error', `Invalid JSON: ${error.message}`); | |
} | |
}); | |
$('#update-target-btn').click(function() { | |
try { | |
const json = JSON.parse($('#target-schema-json').val() || 'null'); | |
if (json) { | |
loadTargetSchema(json); | |
showNotification('success', 'Target schema updated!'); | |
} else { | |
showNotification('error', 'Please enter valid JSON schema'); | |
} | |
} catch (error) { | |
showNotification('error', `Invalid JSON: ${error.message}`); | |
} | |
}); | |
// Auto mapping | |
$('#auto-map-btn').click(function() { | |
if (!sourceData || !targetSchema) { | |
showNotification('error', 'Please provide both source data and target schema'); | |
return; | |
} | |
const sourceFields = extractFields(sourceData); | |
const targetFields = extractFields(targetSchema); | |
mappings = []; | |
targetFields.forEach(targetField => { | |
const targetParts = targetField.split('.'); | |
const targetBase = targetParts[targetParts.length - 1].toLowerCase(); | |
const sourceField = sourceFields.find(sourceField => { | |
const sourceParts = sourceField.split('.'); | |
const sourceBase = sourceParts[sourceParts.length - 1].toLowerCase(); | |
// Simple matching by field name (could be enhanced) | |
return sourceBase === targetBase || | |
sourceBase.replace(/_/g, '') === targetBase.replace(/_/g, '') || | |
(sourceBase.includes('id') && targetBase.includes('id')) || | |
(sourceBase.includes('name') && targetBase.includes('name')) || | |
(sourceBase.includes('date') && targetBase.includes('date')); | |
}); | |
if (sourceField) { | |
let transform = 'none'; | |
// Auto-detect some transformations | |
if (sourceField.toLowerCase().includes('date') && targetField.toLowerCase().includes('date')) { | |
transform = 'formatDate'; | |
} else if (sourceField.toLowerCase().includes('email') && targetField.toLowerCase().includes('email')) { | |
transform = 'lowercase'; | |
} else if (targetField.toLowerCase().includes('name') && targetField.toLowerCase().includes('full')) { | |
transform = 'concat'; | |
} | |
mappings.push({ | |
sourceField: sourceField, | |
targetField: targetField, | |
transform: transform, | |
transformParam: transform === 'formatDate' ? 'YYYY-MM-DD' : | |
transform === 'concat' ? 'firstName + " " + lastName' : '' | |
}); | |
} | |
}); | |
renderMappings(); | |
if (mappings.length > 0) { | |
$('#execute-mapping-btn').removeClass('hidden'); | |
showNotification('success', `Auto-matched ${mappings.length} fields`); | |
} else { | |
showNotification('warning', 'No fields could be automatically matched'); | |
} | |
}); | |
// Add mapping | |
$('#add-mapping-btn').click(function() { | |
const sourceField = $('#source-field-select').val(); | |
const targetField = $('#target-field-select').val(); | |
const transform = $('#transform-select').val(); | |
const param = $('#transform-param').val(); | |
const param2 = $('#transform-param2').val(); | |
if (!sourceField || !targetField) { | |
showNotification('error', 'Please select both source and target fields'); | |
return; | |
} | |
// Check for duplicate target mappings | |
if (mappings.some(m => m.targetField === targetField)) { | |
showNotification('error', `Target field "${targetField}" is already mapped`); | |
return; | |
} | |
const mapping = { | |
sourceField, | |
targetField, | |
transform, | |
transformParam: param, | |
transformParam2: param2 | |
}; | |
mappings.push(mapping); | |
renderMappings(); | |
// Reset form | |
$('#source-field-select').val(''); | |
$('#target-field-select').val(''); | |
$('#transform-select').val('none'); | |
$('#transform-param-container').addClass('hidden'); | |
$('#transform-param2-container').addClass('hidden'); | |
// Show execute button if we have mappings | |
if (mappings.length > 0) { | |
$('#execute-mapping-btn, #output-section').removeClass('hidden'); | |
$('#output-empty-state').addClass('hidden'); | |
} | |
showNotification('success', 'Mapping added successfully'); | |
}); | |
// Execute mapping | |
$('#execute-mapping-btn').click(function() { | |
if (!sourceData || !targetSchema || mappings.length === 0) { | |
showNotification('error', 'Please provide source data, target schema, and at least one mapping'); | |
return; | |
} | |
try { | |
// Create a deep copy of the target schema to modify | |
const output = JSON.parse(JSON.stringify(targetSchema)); | |
// Get the selected date format | |
let dateFormat = $('#date-format').val(); | |
if (dateFormat === 'custom') { | |
dateFormat = $('#custom-date-format').val() || 'YYYY-MM-DD'; | |
} | |
// Apply each mapping | |
mappings.forEach(mapping => { | |
const sourceValue = getNestedValue(sourceData, mapping.sourceField); | |
let transformedValue = sourceValue; | |
// Apply transformation based on type | |
switch(mapping.transform) { | |
// Text transformations | |
case 'uppercase': | |
transformedValue = String(transformedValue).toUpperCase(); | |
break; | |
case 'lowercase': | |
transformedValue = String(transformedValue).toLowerCase(); | |
break; | |
case 'capitalize': | |
transformedValue = String(transformedValue).replace(/\b\w/g, l => l.toUpperCase()); | |
break; | |
case 'trim': | |
transformedValue = String(transformedValue).trim(); | |
break; | |
case 'replace': | |
const regex = new RegExp(mapping.transformParam, 'g'); | |
transformedValue = String(transformedValue).replace(regex, mapping.transformParam2 || ''); | |
break; | |
case 'concat': | |
if (mapping.transformParam) { | |
let concatPattern = mapping.transformParam; | |
const fieldRefs = concatPattern.match(/\b[a-zA-Z0-9_.]+\b/g) || []; | |
fieldRefs.forEach(field => { | |
if (field.includes('.')) { | |
const val = getNestedValue(sourceData, field); | |
concatPattern = concatPattern.replace(new RegExp(field, 'g'), val !== undefined ? val : ''); | |
} | |
}); | |
transformedValue = concatPattern; | |
} | |
break; | |
case 'substring': | |
const start = parseInt(mapping.transformParam) || 0; | |
const end = parseInt(mapping.transformParam2) || String(transformedValue).length; | |
transformedValue = String(transformedValue).substring(start, end); | |
break; | |
// Number transformations | |
case 'parseInt': | |
transformedValue = parseInt(transformedValue) || 0; | |
break; | |
case 'parseFloat': | |
transformedValue = parseFloat(transformedValue) || 0; | |
break; | |
case 'round': | |
const decimals = parseInt(mapping.transformParam) || 0; | |
const factor = Math.pow(10, decimals); | |
transformedValue = Math.round(parseFloat(transformedValue) * factor) / factor; | |
break; | |
case 'formatNumber': | |
transformedValue = parseFloat(transformedValue).toFixed(parseInt(mapping.transformParam) || 2); | |
break; | |
// Date transformations | |
case 'parseDate': | |
transformedValue = new Date(transformedValue); | |
break; | |
case 'formatDate': | |
if (transformedValue instanceof Date || !isNaN(new Date(transformedValue))) { | |
const date = new Date(transformedValue); | |
const formatPattern = mapping.transformParam || dateFormat; | |
transformedValue = formatDate(date, formatPattern); | |
} | |
break; | |
case 'dateAdd': | |
const amount = parseInt(mapping.transformParam) || 0; | |
const unit = mapping.transformParam2 || 'days'; | |
const date = new Date(transformedValue); | |
switch(unit.toLowerCase()) { | |
case 'days': | |
date.setDate(date.getDate() + amount); | |
break; | |
case 'months': | |
date.setMonth(date.getMonth() + amount); | |
break; | |
case 'years': | |
date.setFullYear(date.getFullYear() + amount); | |
break; | |
} | |
transformedValue = date; | |
break; | |
// Logical transformations | |
case 'ifElse': | |
const condition = mapping.transformParam; | |
const trueValue = mapping.transformParam2; | |
const falseValue = transformedValue; | |
try { | |
// Simple condition evaluation (for demo only - be careful with eval in production!) | |
const conditionMet = eval(`(${transformedValue}) ${condition}`); | |
transformedValue = conditionMet ? trueValue : falseValue; | |
} catch (e) { | |
console.error('Condition evaluation error:', e); | |
} | |
break; | |
case 'defaultValue': | |
if (transformedValue === undefined || transformedValue === null || transformedValue === '') { | |
transformedValue = mapping.transformParam || ''; | |
} | |
break; | |
case 'lookup': | |
const lookupPairs = mapping.transformParam.split(',').map(pair => { | |
const [key, val] = pair.split(':').map(s => s.trim()); | |
return { key, val }; | |
}); | |
const foundPair = lookupPairs.find(pair => pair.key === transformedValue); | |
if (foundPair) { | |
transformedValue = foundPair.val; | |
} else if (lookupPairs.length > 0) { | |
transformedValue = lookupPairs[0].val; // Default to first value | |
} | |
break; | |
// Custom transformations | |
case 'custom': | |
try { | |
const customFn = eval(`(${mapping.transformParam})`); | |
transformedValue = customFn(transformedValue); | |
} catch (e) { | |
console.error('Custom function error:', e); | |
} | |
break; | |
case 'multiple': | |
// Combine multiple source fields based on transformation | |
transformedValue = ''; | |
break; | |
} | |
// Set the transformed value in the output | |
setNestedValue(output, mapping.targetField, transformedValue); | |
}); | |
mappedOutput = output; | |
updateOutputDisplay(output); | |
showNotification('success', 'Mapping executed successfully!'); | |
} catch (error) { | |
showNotification('error', `Error executing mappings: ${error.message}`); | |
console.error(error); | |
} | |
}); | |
// Output format select | |
$('#output-format-select').change(function() { | |
if (mappedOutput) { | |
updateOutputDisplay(mappedOutput); | |
} | |
}); | |
// Copy output buttons | |
$('#copy-output-btn').click(function() { | |
if (!mappedOutput) { | |
showNotification('error', 'No output to copy'); | |
return; | |
} | |
const format = $('#output-format-select').val(); | |
const outputText = format === 'compact' ? | |
JSON.stringify(mappedOutput) : | |
JSON.stringify(mappedOutput, null, parseInt($('#json-indent').val() || 2)); | |
navigator.clipboard.writeText(outputText) | |
.then(() => showNotification('success', 'Output copied to clipboard!')) | |
.catch(err => showNotification('error', 'Failed to copy: ' + err)); | |
}); | |
$('#copy-sql-btn').click(function() { | |
const sqlText = $('#sql-preview-container').text(); | |
if (!sqlText || sqlText === 'SQL statements will appear here') { | |
showNotification('error', 'No SQL to copy'); | |
return; | |
} | |
navigator.clipboard.writeText(sqlText) | |
.then(() => showNotification('success', 'SQL copied to clipboard!')) | |
.catch(err => showNotification('error', 'Failed to copy: ' + err)); | |
}); | |
// Export output | |
$('#export-json-btn').click(function() { | |
if (!mappedOutput) { | |
showNotification('error', 'No output to export'); | |
return; | |
} | |
const format = $('#output-format-select').val(); | |
const outputText = format === 'compact' ? | |
JSON.stringify(mappedOutput) : | |
JSON.stringify(mappedOutput, null, parseInt($('#json-indent').val() || 2)); | |
const blob = new Blob([outputText], { type: 'application/json' }); | |
const url = URL.createObjectURL(blob); | |
const a = document.createElement('a'); | |
a.href = url; | |
a.download = 'mapped-output.json'; | |
document.body.appendChild(a); | |
a.click(); | |
document.body.removeChild(a); | |
URL.revokeObjectURL(url); | |
showNotification('success', 'Output exported successfully!'); | |
}); | |
// SQL preview | |
$('#preview-sql-btn').click(function() { | |
if (!mappedOutput || !targetSchema) { | |
showNotification('error', 'Please execute mappings first'); | |
return; | |
} | |
const tableName = $('#sql-table-name').val() || 'target_table'; | |
const fields = extractFields(targetSchema); | |
const sqlType = $('#sql-output-type').val(); | |
let sql = ''; | |
switch(sqlType) { | |
case 'insert': | |
sql = `INSERT INTO ${tableName} (`; | |
sql += fields.join(', ') + ') VALUES ('; | |
sql += fields.map(f => '?').join(', ') + ');'; | |
break; | |
case 'update': | |
const idField = fields.find(f => f.toLowerCase().includes('id')) || fields[0]; | |
sql = `UPDATE ${tableName} SET\n`; | |
sql += fields.filter(f => f !== idField) | |
.map(f => ` ${f} = ?`).join(',\n'); | |
sql += `\nWHERE ${idField} = ?;`; | |
break; | |
case 'merge': | |
const keyField = fields.find(f => f.toLowerCase().includes('id')) || fields[0]; | |
sql = `MERGE INTO ${tableName} AS target\n`; | |
sql += `USING (SELECT ? AS ${keyField}) AS source\n`; | |
sql += `ON (target.${keyField} = source.${keyField})\n`; | |
sql += 'WHEN MATCHED THEN\n'; | |
sql += ' UPDATE SET ' + fields.filter(f => f !== keyField) | |
.map(f => `${f} = ?`).join(', ') + '\n'; | |
sql += 'WHEN NOT MATCHED THEN\n'; | |
sql += ' INSERT (' + fields.join(', ') + ')\n'; | |
sql += ' VALUES (' + fields.map(() => '?').join(', ') + ');'; | |
break; | |
} | |
$('#sql-preview-container').html(`<code>${hljs.highlight('sql', sql).value}</code>`); | |
$('#sql-output').removeClass('hidden'); | |
$('#json-output').addClass('hidden'); | |
showNotification('info', 'SQL preview generated'); | |
}); | |
// Database operations | |
$('#save-to-db-btn').click(function() { | |
if (!mappedOutput) { | |
showNotification('error', 'No output to save to database'); | |
return; | |
} | |
const host = $('#mysql-target-host').val() || $('#config-host').val(); | |
const port = $('#config-port').val() || 3306; | |
const username = $('#mysql-target-username').val() || $('#config-username').val(); | |
const password = $('#mysql-target-password').val() || $('#config-password').val(); | |
const database = $('#mysql-target-database').val() || $('#config-database').val(); | |
const table = $('#mysql-target-table').val() || $('#sql-table-name').val(); | |
if (!host || !username || !database || !table) { | |
showNotification('error', 'Please configure target database information first'); | |
setTimeout(() => $('#mysql-target-btn').click(), 1000); | |
return; | |
} | |
// In a real app, you would make an AJAX call to your backend API | |
// For this demo, we'll just show a success message | |
showNotification('success', `Data saved successfully to ${database}.${table}!`); | |
}); | |
$('#test-db-connection').click(function() { | |
const host = $('#config-host').val(); | |
const port = $('#config-port').val(); | |
const username = $('#config-username').val(); | |
const password = $('#config-password').val(); | |
const database = $('#config-database').val(); | |
if (!host || !username || !database) { | |
showNotification('error', 'Please fill all required fields'); | |
return; | |
} | |
// In a real app, this would test the database connection | |
// For this demo, we'll just show a success message | |
showNotification('success', 'Connection to database successful!'); | |
}); | |
$('#save-db-config').click(function() { | |
const dbConfig = { | |
host: $('#config-host').val(), | |
port: $('#config-port').val(), | |
database: $('#config-database').val(), | |
username: $('#config-username').val(), | |
password: $('#config-password').val(), | |
name: $('#config-name').val() || 'Default Connection' | |
}; | |
// In a real app, you would save this to localStorage or server | |
// For this demo, we'll just show a success message | |
showNotification('success', 'Database configuration saved!'); | |
$('#db-config-modal').addClass('hidden'); | |
}); | |
// Helper functions | |
function loadSourceData(data) { | |
sourceData = data; | |
populateSourceFields(); | |
updateStats(); | |
} | |
function loadTargetSchema(schema) { | |
targetSchema = schema; | |
populateTargetFields(); | |
updateStats(); | |
} | |
function populateSourceFields() { | |
$('#source-field-select').empty().append('<option value="">Select a source field</option>'); | |
if (sourceData && typeof sourceData === 'object') { | |
const fields = extractFields(sourceData); | |
fields.forEach(field => { | |
$('#source-field-select').append(`<option value="${field}">${field}</option>`); | |
}); | |
} | |
} | |
function populateTargetFields() { | |
$('#target-field-select').empty().append('<option value="">Select a target field</option>'); | |
if (targetSchema && typeof targetSchema === 'object') { | |
const fields = extractFields(targetSchema); | |
fields.forEach(field => { | |
$('#target-field-select').append(`<option value="${field}">${field}</option>`); | |
}); | |
} | |
} | |
function extractFields(obj, prefix = '') { | |
let fields = []; | |
for (const key in obj) { | |
if (obj.hasOwnProperty(key)) { | |
const fullPath = prefix ? `${prefix}.${key}` : key; | |
if (typeof obj[key] === 'object' && obj[key] !== null && !Array.isArray(obj[key])) { | |
fields = fields.concat(extractFields(obj[key], fullPath)); | |
} else { | |
fields.push(fullPath); | |
} | |
} | |
} | |
return fields; | |
} | |
function renderMappings() { | |
if (mappings.length === 0) { | |
$('#no-mappings-message').removeClass('hidden'); | |
$('#mapping-container').empty(); | |
return; | |
} | |
$('#no-mappings-message').addClass('hidden'); | |
$('#mapping-container').empty(); | |
mappings.forEach((mapping, index) => { | |
const mappingEl = $(` | |
<div class="mapping-item bg-gray-50 p-3 rounded-lg border border-gray-200 flex justify-between items-start"> | |
<div class="flex-1"> | |
<div class="flex items-center text-sm"> | |
<span class="font-medium text-indigo-600 truncate">${mapping.sourceField}</span> | |
<i class="fas fa-arrow-right mx-2 text-gray-400"></i> | |
<span class="font-medium text-green-600 truncate">${mapping.targetField}</span> | |
</div> | |
<div class="text-xs text-gray-500 mt-1 flex items-center"> | |
<span class="mr-2">${mapping.transform !== 'none' ? | |
`Transformation: ${mapping.transform}${mapping.transformParam ? ` (${mapping.transformParam.length > 30 ? mapping.transformParam.substring(0, 30) + '...' : mapping.transformParam})` : ''}` : | |
'No transformation'}</span> | |
</div> | |
</div> | |
<div class="flex items-center ml-2"> | |
<button class="text-gray-500 hover:text-indigo-600 mr-2 edit-mapping" title="Edit"> | |
<i class="fas fa-pencil-alt text-xs"></i> | |
</button> | |
<button class="text-gray-500 hover:text-red-600 delete-mapping" title="Delete"> | |
<i class="fas fa-trash-alt text-xs"></i> | |
</button> | |
</div> | |
</div> | |
`); | |
mappingEl.find('.delete-mapping').on('click', () => { | |
mappings.splice(index, 1); | |
renderMappings(); | |
if (mappings.length === 0) { | |
$('#execute-mapping-btn, #output-section').addClass('hidden'); | |
$('#output-empty-state').removeClass('hidden'); | |
} | |
showNotification('info', 'Mapping removed'); | |
}); | |
mappingEl.find('.edit-mapping').on('click', () => { | |
$('#source-field-select').val(mapping.sourceField); | |
$('#target-field-select').val(mapping.targetField); | |
$('#transform-select').val(mapping.transform); | |
if (mapping.transformParam) { | |
$('#transform-param-container').removeClass('hidden'); | |
$('#transform-param').val(mapping.transformParam); | |
if (mapping.transformParam2) { | |
$('#transform-param2-container').removeClass('hidden'); | |
$('#transform-param2').val(mapping.transformParam2); | |
} else { | |
$('#transform-param2-container').addClass('hidden'); | |
} | |
} else { | |
$('#transform-param-container, #transform-param2-container').addClass('hidden'); | |
} | |
mappings.splice(index, 1); | |
renderMappings(); | |
showNotification('info', 'Mapping ready for editing'); | |
$('html, body').animate({ scrollTop: $('#source-field-select').offset().top - 100 }, 300); | |
}); | |
$('#mapping-container').append(mappingEl); | |
}); | |
updateStats(); | |
} | |
function updateOutputDisplay(output) { | |
const format = $('#output-format-select').val(); | |
const indent = $('#json-indent').val() || 2; | |
// Update JSON output | |
if (format === 'compact') { | |
$('#output-result').html(`<code>${hljs.highlight('json', JSON.stringify(output)).value}</code>`); | |
} else { | |
$('#output-result').html(`<code>${hljs.highlight('json', JSON.stringify(output, null, indent)).value}</code>`); | |
} | |
// Update table output | |
const tableBody = $('#output-table-body').empty(); | |
const fields = extractFields(output); | |
fields.forEach(field => { | |
const value = getNestedValue(output, field); | |
tableBody.append(` | |
<tr> | |
<td class="px-4 py-2 whitespace-nowrap text-sm font-medium text-gray-900">${field}</td> | |
<td class="px-4 py-2 whitespace-nowrap text-sm text-gray-500">${value !== null && value !== undefined ? value : '<span class="text-gray-400">null</span>'}</td> | |
</tr> | |
`); | |
}); | |
$('#output-empty-state').addClass('hidden'); | |
$('#output-section').removeClass('hidden'); | |
$('#json-output').removeClass('hidden'); | |
} | |
function updateStats() { | |
$('#total-mappings').text(mappings.length); | |
$('#source-fields-count').text(sourceData ? extractFields(sourceData).length : 0); | |
$('#target-fields-count').text(targetSchema ? extractFields(targetSchema).length : 0); | |
$('#transformations-count').text(mappings.filter(m => m.transform !== 'none').length); | |
} | |
function getNestedValue(obj, path) { | |
const keys = path.split('.'); | |
let current = obj; | |
for (const key of keys) { | |
if (current[key] === undefined) { | |
return undefined; | |
} | |
current = current[key]; | |
} | |
return current; | |
} | |
function setNestedValue(obj, path, value) { | |
const keys = path.split('.'); | |
let current = obj; | |
for (let i = 0; i < keys.length - 1; i++) { | |
const key = keys[i]; | |
if (current[key] === undefined) { | |
current[key] = {}; | |
} | |
current = current[key]; | |
} | |
current[keys[keys.length - 1]] = value; | |
} | |
function formatDate(date, format) { | |
const pad = num => num.toString().padStart(2, '0'); | |
const replacements = { | |
'YYYY': date.getFullYear(), | |
'YY': date.getFullYear().toString().slice(-2), | |
'MM': pad(date.getMonth() + 1), | |
'DD': pad(date.getDate()), | |
'HH': pad(date.getHours()), | |
'hh': pad(date.getHours() % 12 || 12), | |
'mm': pad(date.getMinutes()), | |
'ss': pad(date.getSeconds()), | |
'a': date.getHours() < 12 ? 'AM' : 'PM' | |
}; | |
let result = format; | |
for (const [token, value] of Object.entries(replacements)) { | |
result = result.replace(token, value); | |
} | |
return result; | |
} | |
function showNotification(type, message) { | |
const icons = { | |
success: 'check-circle', | |
error: 'exclamation-circle', | |
warning: 'exclamation-triangle', | |
info: 'info-circle' | |
}; | |
const colors = { | |
success: 'bg-green-100 border-green-500 text-green-700', | |
error: 'bg-red-100 border-red-500 text-red-700', | |
warning: 'bg-yellow-100 border-yellow-500 text-yellow-700', | |
info: 'bg-blue-100 border-blue-500 text-blue-700' | |
}; | |
const notification = $(` | |
<div class="fixed top-4 right-4 border-l-4 p-4 rounded shadow-lg max-w-xs z-50 flex items-start ${colors[type]} animate-fade-in"> | |
<i class="fas fa-${icons[type]} mr-2 mt-0.5"></i> | |
<span>${message}</span> | |
</div> | |
`); | |
$('body').append(notification); | |
setTimeout(() => { | |
notification.addClass('opacity-0 transition-opacity duration-300'); | |
setTimeout(() => notification.remove(), 300); | |
}, 3000); | |
} | |
// Initialize with sample data | |
$('#sample-source-btn').click(); | |
$('#sample-target-btn').click(); | |
}); | |
</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment