Skip to content

Instantly share code, notes, and snippets.

@dexit
Created April 1, 2025 11:38
Show Gist options
  • Save dexit/2f9143a4e073b5f05d92026e71410484 to your computer and use it in GitHub Desktop.
Save dexit/2f9143a4e073b5f05d92026e71410484 to your computer and use it in GitHub Desktop.
Datamapper .html
<!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