Created
April 1, 2025 10:09
-
-
Save dexit/480da7dec2c47f64525324f15707e51a to your computer and use it in GitHub Desktop.
field data mapping etl
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</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> | |
<style> | |
.drop-zone { | |
border: 2px dashed #ccc; | |
transition: all 0.3s; | |
} | |
.drop-zone.active { | |
border-color: #3b82f6; | |
background-color: #f0f7ff; | |
} | |
.mapping-item { | |
transition: all 0.2s; | |
} | |
.mapping-item:hover { | |
transform: translateY(-2px); | |
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1); | |
} | |
.transform-select { | |
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e"); | |
background-position: right 0.5rem center; | |
background-repeat: no-repeat; | |
background-size: 1.5em 1.5em; | |
} | |
.saved-mapping { | |
cursor: pointer; | |
transition: all 0.2s; | |
} | |
.saved-mapping:hover { | |
background-color: #f8fafc; | |
} | |
.json-editor { | |
min-height: 200px; | |
font-family: "Courier New", Courier, monospace; | |
} | |
.tab-button { | |
padding: 0.5rem 1rem; | |
border-radius: 0.25rem; | |
transition: all 0.2s; | |
} | |
.tab-button.active { | |
background-color: #3b82f6; | |
color: white; | |
} | |
</style> | |
</head> | |
<body class="bg-gray-50 min-h-screen"> | |
<div class="container mx-auto px-4 py-8"> | |
<header class="mb-8 flex justify-between items-start"> | |
<div> | |
<h1 class="text-3xl font-bold text-gray-800">Advanced Data Mapper</h1> | |
<p class="text-gray-600 mt-2">Connect and transform data with powerful mappings</p> | |
</div> | |
<button id="open-saved-modal" class="px-4 py-2 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700 transition flex items-center"> | |
<i class="fas fa-folder-open mr-2"></i> Saved Mappings | |
</button> | |
</header> | |
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-8"> | |
<!-- Source Data Panel --> | |
<div class="bg-white rounded-lg shadow p-6"> | |
<div class="flex justify-between items-center mb-4"> | |
<h2 class="text-xl font-semibold text-gray-700"> | |
<i class="fas fa-database mr-2 text-blue-500"></i> Source Data | |
</h2> | |
<div class="flex space-x-2"> | |
<button id="sample-source-btn" class="px-3 py-1 bg-blue-100 text-blue-600 rounded text-sm hover:bg-blue-200 transition"> | |
<i class="fas fa-magic mr-1"></i> Sample Data | |
</button> | |
<button id="mysql-source-btn" class="px-3 py-1 bg-blue-100 text-blue-600 rounded text-sm hover:bg-blue-200 transition"> | |
<i class="fas fa-database mr-1"></i> MySQL | |
</button> | |
<button id="clear-source-btn" class="px-3 py-1 bg-gray-100 text-gray-600 rounded text-sm hover:bg-gray-200 transition"> | |
<i class="fas fa-trash-alt mr-1"></i> Clear | |
</button> | |
<button id="update-source-btn" class="px-3 py-1 bg-blue-600 text-white rounded text-sm hover:bg-blue-700 transition"> | |
<i class="fas fa-sync-alt mr-1"></i> Update | |
</button> | |
</div> | |
</div> | |
<div class="flex mb-3 border-b border-gray-200"> | |
<button class="tab-button active" data-tab="json-source">JSON Editor</button> | |
<button class="tab-button" data-tab="upload-source">File Upload</button> | |
<button class="tab-button" data-tab="mysql-source">MySQL Query</button> | |
</div> | |
<div id="source-drop-zone" class="drop-zone rounded-lg p-4 h-64 overflow-y-auto"> | |
<!-- JSON Editor Tab --> | |
<div id="json-source" class="tab-content active"> | |
<textarea id="source-data-json" class="json-editor w-full h-60 border border-gray-300 rounded p-2 text-sm font-mono" placeholder='{ | |
"example": { | |
"id": 1, | |
"name": "Sample", | |
"details": { | |
"active": true, | |
"createdAt": "2023-01-01" | |
} | |
} | |
}'></textarea> | |
</div> | |
<!-- Upload Tab --> | |
<div id="upload-source" class="tab-content hidden text-center py-8"> | |
<i class="fas fa-cloud-upload-alt text-4xl text-gray-300 mb-3"></i> | |
<p class="text-gray-500">Drag & drop a JSON file here</p> | |
<p class="text-sm text-gray-400 mt-1">Or click to select a file</p> | |
<input type="file" id="source-file-input" class="hidden" accept=".json,application/json"> | |
<button id="source-file-btn" class="mt-3 px-4 py-2 bg-blue-100 text-blue-600 rounded-md hover:bg-blue-200 transition"> | |
<i class="fas fa-file-upload mr-2"></i> Select File | |
</button> | |
</div> | |
<!-- MySQL Tab --> | |
<div id="mysql-source" class="tab-content hidden"> | |
<label class="block text-sm font-medium text-gray-700 mb-1">MySQL Connection</label> | |
<div class="grid grid-cols-2 gap-3 mb-3"> | |
<input type="text" id="mysql-host" class="border-gray-300 rounded-md shadow-sm" placeholder="Host" value="localhost"> | |
<input type="text" id="mysql-username" class="border-gray-300 rounded-md shadow-sm" placeholder="Username" value="root"> | |
<input type="password" id="mysql-password" class="border-gray-300 rounded-md shadow-sm" placeholder="Password"> | |
<input type="text" id="mysql-database" class="border-gray-300 rounded-md shadow-sm" placeholder="Database"> | |
</div> | |
<textarea id="mysql-query" class="w-full border-gray-300 rounded-md shadow-sm h-20" placeholder="SELECT * FROM table"></textarea> | |
<button id="execute-mysql" class="mt-2 w-full bg-blue-600 hover:bg-blue-700 text-white py-2 px-4 rounded-md transition"> | |
<i class="fas fa-play mr-2"></i> Execute Query | |
</button> | |
</div> | |
</div> | |
</div> | |
<!-- Target Data Panel --> | |
<div class="bg-white rounded-lg shadow p-6"> | |
<div class="flex justify-between items-center mb-4"> | |
<h2 class="text-xl font-semibold text-gray-700"> | |
<i class="fas fa-bullseye mr-2 text-green-500"></i> Target Schema | |
</h2> | |
<div class="flex space-x-2"> | |
<button id="sample-target-btn" class="px-3 py-1 bg-green-100 text-green-600 rounded text-sm hover:bg-green-200 transition"> | |
<i class="fas fa-magic mr-1"></i> Sample Schema | |
</button> | |
<button id="mysql-target-btn" class="px-3 py-1 bg-green-100 text-green-600 rounded text-sm hover:bg-green-200 transition"> | |
<i class="fas fa-database mr-1"></i> MySQL | |
</button> | |
<button id="clear-target-btn" class="px-3 py-1 bg-gray-100 text-gray-600 rounded text-sm hover:bg-gray-200 transition"> | |
<i class="fas fa-trash-alt mr-1"></i> Clear | |
</button> | |
<button id="update-target-btn" class="px-3 py-1 bg-green-600 text-white rounded text-sm hover:bg-green-700 transition"> | |
<i class="fas fa-sync-alt mr-1"></i> Update | |
</button> | |
</div> | |
</div> | |
<div class="flex mb-3 border-b border-gray-200"> | |
<button class="tab-button active" data-tab="json-target">JSON Editor</button> | |
<button class="tab-button" data-tab="upload-target">File Upload</button> | |
<button class="tab-button" data-tab="mysql-target">MySQL Table</button> | |
</div> | |
<div id="target-drop-zone" class="drop-zone rounded-lg p-4 h-64 overflow-y-auto"> | |
<!-- JSON Editor Tab --> | |
<div id="json-target" class="tab-content active"> | |
<textarea id="target-schema-json" class="json-editor w-full h-60 border border-gray-300 rounded p-2 text-sm font-mono" placeholder='{ | |
"example": { | |
"id": null, | |
"name": null, | |
"details": { | |
"active": null, | |
"createdAt": null | |
} | |
} | |
}'></textarea> | |
</div> | |
<!-- Upload Tab --> | |
<div id="upload-target" class="tab-content hidden text-center py-8"> | |
<i class="fas fa-cloud-upload-alt text-4xl text-gray-300 mb-3"></i> | |
<p class="text-gray-500">Drag & drop a JSON schema file here</p> | |
<p class="text-sm text-gray-400 mt-1">Or click to select a file</p> | |
<input type="file" id="target-file-input" class="hidden" accept=".json,application/json"> | |
<button id="target-file-btn" class="mt-3 px-4 py-2 bg-green-100 text-green-600 rounded-md hover:bg-green-200 transition"> | |
<i class="fas fa-file-upload mr-2"></i> Select File | |
</button> | |
</div> | |
<!-- MySQL Tab --> | |
<div id="mysql-target" class="tab-content hidden"> | |
<label class="block text-sm font-medium text-gray-700 mb-1">MySQL Connection</label> | |
<div class="grid grid-cols-2 gap-3 mb-3"> | |
<input type="text" id="mysql-target-host" class="border-gray-300 rounded-md shadow-sm" placeholder="Host" value="localhost"> | |
<input type="text" id="mysql-target-username" class="border-gray-300 rounded-md shadow-sm" placeholder="Username" value="root"> | |
<input type="password" id="mysql-target-password" class="border-gray-300 rounded-md shadow-sm" placeholder="Password"> | |
<input type="text" id="mysql-target-database" class="border-gray-300 rounded-md shadow-sm" placeholder="Database"> | |
</div> | |
<div class="relative"> | |
<select id="mysql-target-table" class="w-full border-gray-300 rounded-md shadow-sm transform-select"> | |
<option value="">Select a table</option> | |
<!-- Tables will be populated here --> | |
</select> | |
<button id="load-table-schema" class="mt-2 w-full bg-green-600 hover:bg-green-700 text-white py-2 px-4 rounded-md transition"> | |
<i class="fas fa-table mr-2"></i> Load Table Schema | |
</button> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
<!-- Mapping Section --> | |
<div class="bg-white rounded-lg shadow p-6 mb-8"> | |
<div class="flex justify-between items-center mb-4"> | |
<h2 class="text-xl font-semibold text-gray-700"> | |
<i class="fas fa-project-diagram mr-2 text-purple-500"></i> Field Mappings | |
</h2> | |
<div class="flex space-x-2"> | |
<button id="auto-map-btn" class="px-3 py-1 bg-purple-100 text-purple-600 rounded text-sm hover:bg-purple-200 transition"> | |
<i class="fas fa-robot mr-1"></i> Auto-Match Fields | |
</button> | |
<button id="save-mapping-btn" class="px-3 py-1 bg-indigo-100 text-indigo-600 rounded text-sm hover:bg-indigo-200 transition"> | |
<i class="fas fa-save mr-1"></i> Save Mapping | |
</button> | |
<button id="clear-mappings-btn" class="px-3 py-1 bg-gray-100 text-gray-600 rounded text-sm hover:bg-gray-200 transition"> | |
<i class="fas fa-trash-alt mr-1"></i> Clear All | |
</button> | |
</div> | |
</div> | |
<div id="mapping-container" class="space-y-3"> | |
<div id="no-mappings-message" class="text-center py-8 text-gray-500"> | |
<i class="fas fa-exchange-alt text-3xl mb-3"></i> | |
<p>No mappings created yet. 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">Source Field</label> | |
<select id="source-field-select" class="w-full border-gray-300 rounded-md shadow-sm focus:border-blue-500 focus:ring-blue-500 transform-select"> | |
<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">Target Field</label> | |
<select id="target-field-select" class="w-full border-gray-300 rounded-md shadow-sm focus:border-blue-500 focus:ring-blue-500 transform-select"> | |
<option value="">Select a target field</option> | |
<!-- Target fields will be populated here --> | |
</select> | |
</div> | |
</div> | |
<div class="mt-4 grid grid-cols-1 md:grid-cols-3 gap-4"> | |
<div> | |
<label class="block text-sm font-medium text-gray-700 mb-1">Transformation</label> | |
<select id="transform-select" class="w-full border-gray-300 rounded-md shadow-sm focus:border-blue-500 focus:ring-blue-500 transform-select"> | |
<option value="none">None (Direct mapping)</option> | |
<option value="uppercase">Uppercase</option> | |
<option value="lowercase">Lowercase</option> | |
<option value="trim">Trim whitespace</option> | |
<option value="parseInt">Convert to integer</option> | |
<option value="parseFloat">Convert to float</option> | |
<option value="toString">Convert to string</option> | |
<option value="dateFormat">Format date</option> | |
<option value="concat">Concatenate fields</option> | |
<option value="custom">Custom function</option> | |
</select> | |
</div> | |
<div id="transform-param-container" class="hidden"> | |
<label class="block text-sm font-medium text-gray-700 mb-1">Parameter</label> | |
<input type="text" id="transform-param" class="w-full border-gray-300 rounded-md shadow-sm focus:border-blue-500 focus:ring-blue-500" placeholder="e.g. YYYY-MM-DD"> | |
</div> | |
<div class="flex items-end"> | |
<button id="add-mapping-btn" class="w-full bg-blue-600 hover:bg-blue-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> | |
</div> | |
<!-- Results Section --> | |
<div class="bg-white rounded-lg shadow p-6"> | |
<div class="flex justify-between items-center mb-4"> | |
<h2 class="text-xl font-semibold text-gray-700"> | |
<i class="fas fa-code mr-2 text-orange-500"></i> Mapped Output | |
</h2> | |
<div class="flex space-x-2"> | |
<button id="preview-sql-btn" class="px-3 py-1 bg-orange-100 text-orange-600 rounded text-sm hover:bg-orange-200 transition"> | |
<i class="fas fa-eye mr-1"></i> Preview SQL | |
</button> | |
<button id="copy-output-btn" class="px-3 py-1 bg-orange-100 text-orange-600 rounded text-sm hover:bg-orange-200 transition"> | |
<i class="fas fa-copy mr-1"></i> Copy | |
</button> | |
<button id="download-output-btn" class="px-3 py-1 bg-orange-100 text-orange-600 rounded text-sm hover:bg-orange-200 transition"> | |
<i class="fas fa-download mr-1"></i> Download | |
</button> | |
<button id="save-to-db-btn" class="px-3 py-1 bg-green-100 text-green-600 rounded text-sm hover:bg-green-200 transition"> | |
<i class="fas fa-save mr-1"></i> Save to DB | |
</button> | |
</div> | |
</div> | |
<div class="relative"> | |
<div id="output-empty-state" class="text-center py-8 text-gray-500"> | |
<i class="fas fa-code-branch text-3xl mb-3"></i> | |
<p>Mapped output will appear here after creating mappings.</p> | |
</div> | |
<pre id="output-result" class="hidden bg-gray-50 p-4 rounded text-sm text-gray-800 font-mono overflow-x-auto"></pre> | |
<div id="sql-preview-container" class="hidden bg-gray-50 p-4 rounded text-sm text-gray-800 font-mono overflow-x-auto"></div> | |
<button id="execute-mapping-btn" class="hidden mt-4 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-play mr-2"></i> Execute Mapping | |
</button> | |
</div> | |
</div> | |
</div> | |
<!-- Saved Mappings Modal --> | |
<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"> | |
<i class="fas fa-folder-open mr-2"></i> Saved Mappings | |
</h3> | |
<button id="close-saved-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 id="saved-mappings-list" class="space-y-2"> | |
<!-- Saved mappings will appear here --> | |
</div> | |
<div id="no-saved-mappings" class="text-center py-8 text-gray-500"> | |
<i class="fas fa-inbox text-3xl mb-3"></i> | |
<p>No saved mappings found.</p> | |
</div> | |
</div> | |
<div class="border-t border-gray-200 px-6 py-4 flex justify-end"> | |
<button id="cancel-saved-modal" class="px-4 py-2 bg-gray-100 text-gray-700 rounded-lg hover:bg-gray-200 transition mr-2"> | |
Cancel | |
</button> | |
</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"> | |
<i class="fas fa-save mr-2"></i> Save Mapping | |
</h3> | |
</div> | |
<div class="p-6"> | |
<div class="mb-4"> | |
<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-blue-500 focus:ring-blue-500" placeholder="Enter a name for this mapping"> | |
</div> | |
<div class="mb-4"> | |
<label class="block text-sm font-medium text-gray-700 mb-1">Description (Optional)</label> | |
<textarea id="mapping-description" class="w-full border-gray-300 rounded-md shadow-sm focus:border-blue-500 focus:ring-blue-500" rows="3" placeholder="Describe this mapping"></textarea> | |
</div> | |
</div> | |
<div class="border-t border-gray-200 px-6 py-4 flex justify-end"> | |
<button id="cancel-save-mapping" class="px-4 py-2 bg-gray-100 text-gray-700 rounded-lg hover:bg-gray-200 transition mr-2"> | |
Cancel | |
</button> | |
<button id="confirm-save-mapping" class="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition"> | |
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"> | |
<i class="fas fa-database mr-2"></i> Database Configuration | |
</h3> | |
</div> | |
<div class="p-6"> | |
<div class="mb-4"> | |
<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-blue-500 focus:ring-blue-500" value="localhost"> | |
</div> | |
<div class="mb-4"> | |
<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-blue-500 focus:ring-blue-500" value="data_mapper"> | |
</div> | |
<div class="mb-4"> | |
<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-blue-500 focus:ring-blue-500" value="root"> | |
</div> | |
<div class="mb-4"> | |
<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-blue-500 focus:ring-blue-500"> | |
</div> | |
</div> | |
<div class="border-t border-gray-200 px-6 py-4 flex justify-end"> | |
<button id="cancel-db-config" class="px-4 py-2 bg-gray-100 text-gray-700 rounded-lg hover:bg-gray-200 transition mr-2"> | |
Cancel | |
</button> | |
<button id="test-db-connection" class="px-4 py-2 bg-blue-100 text-blue-600 rounded-lg hover:bg-blue-200 transition mr-2"> | |
Test Connection | |
</button> | |
<button id="save-db-config" class="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition"> | |
Save | |
</button> | |
</div> | |
</div> | |
</div> | |
<script> | |
$(document).ready(function() { | |
// DOM elements | |
const $sourceDropZone = $('#source-drop-zone'); | |
const $targetDropZone = $('#target-drop-zone'); | |
const $sourceDataJson = $('#source-data-json'); | |
const $targetSchemaJson = $('#target-schema-json'); | |
const $sourceFileInput = $('#source-file-input'); | |
const $targetFileInput = $('#target-file-input'); | |
const $sourceFileBtn = $('#source-file-btn'); | |
const $targetFileBtn = $('#target-file-btn'); | |
const $sourceFieldSelect = $('#source-field-select'); | |
const $targetFieldSelect = $('#target-field-select'); | |
const $transformSelect = $('#transform-select'); | |
const $transformParamContainer = $('#transform-param-container'); | |
const $transformParam = $('#transform-param'); | |
const $addMappingBtn = $('#add-mapping-btn'); | |
const $mappingContainer = $('#mapping-container'); | |
const $noMappingsMessage = $('#no-mappings-message'); | |
const $outputResult = $('#output-result'); | |
const $outputEmptyState = $('#output-empty-state'); | |
const $executeMappingBtn = $('#execute-mapping-btn'); | |
const $copyOutputBtn = $('#copy-output-btn'); | |
const $downloadOutputBtn = $('#download-output-btn'); | |
const $autoMapBtn = $('#auto-map-btn'); | |
const $saveMappingBtn = $('#save-mapping-btn'); | |
const $clearMappingsBtn = $('#clear-mappings-btn'); | |
const $sampleSourceBtn = $('#sample-source-btn'); | |
const $sampleTargetBtn = $('#sample-target-btn'); | |
const $clearSourceBtn = $('#clear-source-btn'); | |
const $clearTargetBtn = $('#clear-target-btn'); | |
const $updateSourceBtn = $('#update-source-btn'); | |
const $updateTargetBtn = $('#update-target-btn'); | |
const $mysqlSourceBtn = $('#mysql-source-btn'); | |
const $mysqlTargetBtn = $('#mysql-target-btn'); | |
const $previewSqlBtn = $('#preview-sql-btn'); | |
const $sqlPreviewContainer = $('#sql-preview-container'); | |
const $saveToDbBtn = $('#save-to-db-btn'); | |
const $savedMappingsModal = $('#saved-mappings-modal'); | |
const $openSavedModal = $('#open-saved-modal'); | |
const $closeSavedModal = $('#close-saved-modal'); | |
const $cancelSavedModal = $('#cancel-saved-modal'); | |
const $savedMappingsList = $('#saved-mappings-list'); | |
const $noSavedMappings = $('#no-saved-mappings'); | |
const $saveMappingModal = $('#save-mapping-modal'); | |
const $mappingName = $('#mapping-name'); | |
const $mappingDescription = $('#mapping-description'); | |
const $confirmSaveMapping = $('#confirm-save-mapping'); | |
const $cancelSaveMapping = $('#cancel-save-mapping'); | |
const $dbConfigModal = $('#db-config-modal'); | |
const $configHost = $('#config-host'); | |
const $configDatabase = $('#config-database'); | |
const $configUsername = $('#config-username'); | |
const $configPassword = $('#config-password'); | |
const $testDbConnection = $('#test-db-connection'); | |
const $saveDbConfig = $('#save-db-config'); | |
const $cancelDbConfig = $('#cancel-db-config'); | |
// Tab buttons | |
const $sourceTabButtons = $('[data-tab^="json-source"], [data-tab^="upload-source"], [data-tab^="mysql-source"]'); | |
const $targetTabButtons = $('[data-tab^="json-target"], [data-tab^="upload-target"], [data-tab^="mysql-target"]'); | |
const $sourceTabContents = $('.tab-content', $sourceDropZone); | |
const $targetTabContents = $('.tab-content', $targetDropZone); | |
// State | |
let sourceData = null; | |
let targetSchema = null; | |
let mappings = []; | |
let mappedOutput = null; | |
let dbConfig = { | |
host: 'localhost', | |
database: 'data_mapper', | |
username: 'root', | |
password: '' | |
}; | |
// Load saved DB config from localStorage if available | |
if (localStorage.getItem('dbConfig')) { | |
dbConfig = JSON.parse(localStorage.getItem('dbConfig')); | |
$configHost.val(dbConfig.host); | |
$configDatabase.val(dbConfig.database); | |
$configUsername.val(dbConfig.username); | |
$configPassword.val(dbConfig.password); | |
} | |
// Initialize tab switching | |
$sourceTabButtons.on('click', function() { | |
const tabId = $(this).data('tab'); | |
$sourceTabButtons.removeClass('active'); | |
$(this).addClass('active'); | |
$sourceTabContents.addClass('hidden'); | |
$(`#${tabId}`).removeClass('hidden'); | |
}); | |
$targetTabButtons.on('click', function() { | |
const tabId = $(this).data('tab'); | |
$targetTabButtons.removeClass('active'); | |
$(this).addClass('active'); | |
$targetTabContents.addClass('hidden'); | |
$(`#${tabId}`).removeClass('hidden'); | |
}); | |
// Initialize file inputs | |
$sourceFileBtn.on('click', function() { | |
$sourceFileInput.click(); | |
}); | |
$targetFileBtn.on('click', function() { | |
$targetFileInput.click(); | |
}); | |
$sourceFileInput.on('change', function() { | |
if (this.files.length > 0) { | |
handleFileUpload(this.files, 'source'); | |
} | |
}); | |
$targetFileInput.on('change', function() { | |
if (this.files.length > 0) { | |
handleFileUpload(this.files, 'target'); | |
} | |
}); | |
function handleFileUpload(files, type) { | |
const file = files[0]; | |
const reader = new FileReader(); | |
reader.onload = function(e) { | |
try { | |
const content = JSON.parse(e.target.result); | |
if (type === 'source') { | |
loadSourceData(content); | |
$sourceDataJson.val(JSON.stringify(content, null, 2)); | |
} else { | |
loadTargetSchema(content); | |
$targetSchemaJson.val(JSON.stringify(content, null, 2)); | |
} | |
showSuccess('File loaded successfully!'); | |
} catch (error) { | |
showError(`Invalid JSON: ${error.message}`); | |
} | |
}; | |
reader.onerror = function() { | |
showError('Error reading file'); | |
}; | |
reader.readAsText(file); | |
} | |
// Update source/target data from JSON input | |
$updateSourceBtn.on('click', function() { | |
try { | |
const json = JSON.parse($sourceDataJson.val() || 'null'); | |
if (json) { | |
loadSourceData(json); | |
showSuccess('Source data updated!'); | |
} else { | |
showError('Please enter valid JSON data'); | |
} | |
} catch (error) { | |
showError(`Invalid JSON: ${error.message}`); | |
} | |
}); | |
$updateTargetBtn.on('click', function() { | |
try { | |
const json = JSON.parse($targetSchemaJson.val() || 'null'); | |
if (json) { | |
loadTargetSchema(json); | |
showSuccess('Target schema updated!'); | |
} else { | |
showError('Please enter valid JSON schema'); | |
} | |
} catch (error) { | |
showError(`Invalid JSON: ${error.message}`); | |
} | |
}); | |
// Load data functions | |
function loadSourceData(data) { | |
sourceData = data; | |
populateSourceFields(); | |
$executeMappingBtn.removeClass('hidden'); | |
} | |
function loadTargetSchema(schema) { | |
targetSchema = schema; | |
populateTargetFields(); | |
$executeMappingBtn.removeClass('hidden'); | |
} | |
// MySQL Source Connection | |
$mysqlSourceBtn.on('click', function() { | |
$('[data-tab="mysql-source"]').click(); | |
}); | |
$executeMysql.on('click', function() { | |
const host = $('#mysql-host').val(); | |
const username = $('#mysql-username').val(); | |
const password = $('#mysql-password').val(); | |
const database = $('#mysql-database').val(); | |
const query = $('#mysql-query').val(); | |
if (!host || !username || !database || !query) { | |
showError('Please fill all required fields'); | |
return; | |
} | |
$.ajax({ | |
url: 'api/mysql_query.php', | |
method: 'POST', | |
dataType: 'json', | |
data: { | |
host: host, | |
username: username, | |
password: password, | |
database: database, | |
query: query | |
}, | |
success: function(data) { | |
if (data.error) { | |
showError(data.error); | |
} else { | |
loadSourceData(data); | |
$sourceDataJson.val(JSON.stringify(data, null, 2)); | |
showSuccess('Query executed successfully!'); | |
} | |
}, | |
error: function(xhr, status, error) { | |
showError('Error executing query: ' + error); | |
} | |
}); | |
}); | |
// MySQL Target Connection | |
$mysqlTargetBtn.on('click', function() { | |
$('[data-tab="mysql-target"]').click(); | |
}); | |
$loadTableSchema.on('click', function() { | |
const table = $mysqlTargetTable.val(); | |
if (!table) { | |
showError('Please select a table'); | |
return; | |
} | |
const host = $('#mysql-target-host').val(); | |
const username = $('#mysql-target-username').val(); | |
const password = $('#mysql-target-password').val(); | |
const database = $('#mysql-target-database').val(); | |
$.ajax({ | |
url: 'api/mysql_schema.php', | |
method: 'POST', | |
dataType: 'json', | |
data: { | |
host: host, | |
username: username, | |
password: password, | |
database: database, | |
table: table | |
}, | |
success: function(data) { | |
if (data.error) { | |
showError(data.error); | |
} else { | |
// Create schema object with null values | |
const schema = {}; | |
data.columns.forEach(column => { | |
schema[column.name] = null; | |
}); | |
loadTargetSchema(schema); | |
$targetSchemaJson.val(JSON.stringify(schema, null, 2)); | |
showSuccess('Table schema loaded successfully!'); | |
} | |
}, | |
error: function(xhr, status, error) { | |
showError('Error loading table schema: ' + error); | |
} | |
}); | |
}); | |
// Field population functions | |
function populateSourceFields() { | |
$sourceFieldSelect.empty().append('<option value="">Select a source field</option>'); | |
if (sourceData && typeof sourceData === 'object') { | |
const fields = extractFields(sourceData); | |
fields.forEach(field => { | |
$sourceFieldSelect.append(`<option value="${field}">${field}</option>`); | |
}); | |
} | |
} | |
function populateTargetFields() { | |
$targetFieldSelect.empty().append('<option value="">Select a target field</option>'); | |
if (targetSchema && typeof targetSchema === 'object') { | |
const fields = extractFields(targetSchema); | |
fields.forEach(field => { | |
$targetFieldSelect.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) { | |
fields = fields.concat(extractFields(obj[key], fullPath)); | |
} else { | |
fields.push(fullPath); | |
} | |
} | |
} | |
return fields; | |
} | |
// Transformation select change | |
$transformSelect.on('change', function() { | |
if (this.value === 'dateFormat' || this.value === 'custom' || this.value === 'concat') { | |
$transformParamContainer.removeClass('hidden'); | |
$transformParam.attr('placeholder', | |
this.value === 'dateFormat' ? 'e.g. YYYY-MM-DD' : | |
this.value === 'concat' ? 'e.g. "prefix-"+field1+"-"+field2' : | |
'e.g. value => value.toUpperCase()'); | |
} else { | |
$transformParamContainer.addClass('hidden'); | |
} | |
}); | |
// Add mapping | |
$addMappingBtn.on('click', function() { | |
const sourceField = $sourceFieldSelect.val(); | |
const targetField = $targetFieldSelect.val(); | |
const transform = $transformSelect.val(); | |
const transformParamValue = $transformParam.val(); | |
if (!sourceField || !targetField) { | |
showError('Please select both source and target fields'); | |
return; | |
} | |
// Check if this mapping already exists | |
if (mappings.some(m => m.targetField === targetField)) { | |
showError(`Target field "${targetField}" is already mapped`); | |
return; | |
} | |
const mapping = { | |
sourceField, | |
targetField, | |
transform, | |
transformParam: transformParamValue | |
}; | |
mappings.push(mapping); | |
renderMappings(); | |
// Reset form | |
$sourceFieldSelect.val(''); | |
$targetFieldSelect.val(''); | |
$transformSelect.val('none'); | |
$transformParamContainer.addClass('hidden'); | |
$transformParam.val(''); | |
// Show execute button if we have mappings | |
if (mappings.length > 0) { | |
$executeMappingBtn.removeClass('hidden'); | |
} | |
}); | |
// Render mappings | |
function renderMappings() { | |
if (mappings.length === 0) { | |
$noMappingsMessage.removeClass('hidden'); | |
$mappingContainer.empty(); | |
return; | |
} | |
$noMappingsMessage.addClass('hidden'); | |
$mappingContainer.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-center"> | |
<div class="flex-1"> | |
<div class="flex items-center text-sm"> | |
<span class="font-medium text-blue-600">${mapping.sourceField}</span> | |
<i class="fas fa-arrow-right mx-2 text-gray-400"></i> | |
<span class="font-medium text-green-600">${mapping.targetField}</span> | |
</div> | |
<div class="text-xs text-gray-500 mt-1"> | |
${mapping.transform !== 'none' ? | |
`Transformation: ${mapping.transform}${mapping.transformParam ? ` (${mapping.transformParam})` : ''}` : | |
'No transformation'} | |
</div> | |
</div> | |
<button class="text-red-500 hover:text-red-700 ml-2"> | |
<i class="fas fa-trash-alt"></i> | |
</button> | |
</div> | |
`); | |
mappingEl.find('button').on('click', () => { | |
mappings.splice(index, 1); | |
renderMappings(); | |
if (mappings.length === 0) { | |
$executeMappingBtn.addClass('hidden'); | |
$outputResult.addClass('hidden'); | |
$outputEmptyState.removeClass('hidden'); | |
} | |
}); | |
$mappingContainer.append(mappingEl); | |
}); | |
} | |
// Execute mapping | |
$executeMappingBtn.on('click', function() { | |
if (!sourceData || !targetSchema || mappings.length === 0) { | |
showError('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)); | |
// Apply each mapping | |
mappings.forEach(mapping => { | |
const sourceValue = getNestedValue(sourceData, mapping.sourceField); | |
let transformedValue = sourceValue; | |
// Apply transformation | |
switch (mapping.transform) { | |
case 'uppercase': | |
if (typeof transformedValue === 'string') { | |
transformedValue = transformedValue.toUpperCase(); | |
} | |
break; | |
case 'lowercase': | |
if (typeof transformedValue === 'string') { | |
transformedValue = transformedValue.toLowerCase(); | |
} | |
break; | |
case 'trim': | |
if (typeof transformedValue === 'string') { | |
transformedValue = transformedValue.trim(); | |
} | |
break; | |
case 'parseInt': | |
transformedValue = parseInt(transformedValue); | |
break; | |
case 'parseFloat': | |
transformedValue = parseFloat(transformedValue); | |
break; | |
case 'toString': | |
transformedValue = String(transformedValue); | |
break; | |
case 'dateFormat': | |
if (transformedValue instanceof Date) { | |
transformedValue = formatDate(transformedValue, mapping.transformParam || 'YYYY-MM-DD'); | |
} | |
break; | |
case 'concat': | |
if (mapping.transformParam) { | |
try { | |
// Replace field references with actual values | |
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; | |
} catch (e) { | |
console.error('Concatenation error:', e); | |
} | |
} | |
break; | |
case 'custom': | |
try { | |
// WARNING: Using eval is dangerous in production! | |
// This is just for demo purposes | |
const customFn = eval(`(${mapping.transformParam})`); | |
transformedValue = customFn(transformedValue); | |
} catch (e) { | |
console.error('Custom function error:', e); | |
} | |
break; | |
// 'none' does nothing | |
} | |
// Set the value in the output | |
setNestedValue(output, mapping.targetField, transformedValue); | |
}); | |
mappedOutput = output; | |
$outputResult.text(JSON.stringify(output, null, 2)).removeClass('hidden'); | |
$outputEmptyState.addClass('hidden'); | |
// Hide SQL preview if showing | |
$sqlPreviewContainer.addClass('hidden'); | |
$previewSqlBtn.removeClass('bg-orange-200').addClass('bg-orange-100'); | |
showSuccess('Mapping executed successfully!'); | |
} catch (error) { | |
showError(`Error executing mappings: ${error.message}`); | |
} | |
}); | |
// Helper functions for nested object access | |
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; | |
} | |
// Simple date formatting helper | |
function formatDate(date, format) { | |
const pad = num => num.toString().padStart(2, '0'); | |
const replacements = { | |
'YYYY': date.getFullYear(), | |
'MM': pad(date.getMonth() + 1), | |
'DD': pad(date.getDate()), | |
'HH': pad(date.getHours()), | |
'mm': pad(date.getMinutes()), | |
'ss': pad(date.getSeconds()) | |
}; | |
let result = format; | |
for (const [token, value] of Object.entries(replacements)) { | |
result = result.replace(token, value); | |
} | |
return result; | |
} | |
// Copy output | |
$copyOutputBtn.on('click', function() { | |
if (!mappedOutput) { | |
showError('No output to copy'); | |
return; | |
} | |
navigator.clipboard.writeText(JSON.stringify(mappedOutput, null, 2)) | |
.then(() => { | |
showSuccess('Output copied to clipboard!'); | |
}) | |
.catch(err => { | |
showError('Failed to copy: ' + err); | |
}); | |
}); | |
// Download output | |
$downloadOutputBtn.on('click', function() { | |
if (!mappedOutput) { | |
showError('No output to download'); | |
return; | |
} | |
const dataStr = 'data:text/json;charset=utf-8,' + encodeURIComponent(JSON.stringify(mappedOutput, null, 2)); | |
const downloadAnchorNode = document.createElement('a'); | |
downloadAnchorNode.setAttribute('href', dataStr); | |
downloadAnchorNode.setAttribute('download', 'mapped-output.json'); | |
document.body.appendChild(downloadAnchorNode); | |
downloadAnchorNode.click(); | |
downloadAnchorNode.remove(); | |
}); | |
// Preview SQL | |
$previewSqlBtn.on('click', function() { | |
if (!mappedOutput || !targetSchema) { | |
showError('Please execute mappings first'); | |
return; | |
} | |
// Check if the target schema matches a single table structure | |
let tableName = 'target_table'; // Default or could prompt user | |
let fields = extractFields(targetSchema); | |
// Simple INSERT statement generation | |
let sql = `INSERT INTO ${tableName} (`; | |
sql += fields.join(', ') + ') VALUES ('; | |
// Add placeholders for values | |
sql += fields.map(f => '?').join(', ') + ');'; | |
// Display the SQL | |
$sqlPreviewContainer.text(sql).toggleClass('hidden'); | |
$outputResult.toggleClass('hidden'); | |
// Toggle button state | |
$(this).toggleClass('bg-orange-100 bg-orange-200'); | |
if (!$sqlPreviewContainer.hasClass('hidden')) { | |
showSuccess('SQL preview generated!'); | |
} | |
}); | |
// Save to database | |
$saveToDbBtn.on('click', function() { | |
if (!mappedOutput) { | |
showError('No output to save to database'); | |
return; | |
} | |
// Get target table info (would normally prompt user) | |
const host = $('#mysql-target-host').val() || dbConfig.host; | |
const username = $('#mysql-target-username').val() || dbConfig.username; | |
const password = $('#mysql-target-password').val() || dbConfig.password; | |
const database = $('#mysql-target-database').val() || dbConfig.database; | |
const table = $mysqlTargetTable.val(); | |
if (!host || !username || !database || !table) { | |
showError('Please configure target database information first'); | |
setTimeout(() => { | |
$mysqlTargetBtn.click(); | |
}, 1000); | |
return; | |
} | |
$.ajax({ | |
url: 'api/mysql_save.php', | |
method: 'POST', | |
dataType: 'json', | |
data: { | |
host: host, | |
username: username, | |
password: password, | |
database: database, | |
table: table, | |
data: JSON.stringify(mappedOutput) | |
}, | |
success: function(data) { | |
if (data.error) { | |
showError(data.error); | |
} else { | |
showSuccess(`Data saved successfully to ${table}!`); | |
} | |
}, | |
error: function(xhr, status, error) { | |
showError('Error saving to database: ' + error); | |
} | |
}); | |
}); | |
// Auto-map fields | |
$autoMapBtn.on('click', function() { | |
if (!sourceData || !targetSchema) { | |
showError('Please provide both source data and target schema'); | |
return; | |
} | |
const sourceFields = extractFields(sourceData); | |
const targetFields = extractFields(targetSchema); | |
// Clear existing mappings | |
mappings = []; | |
// Simple matching by field name (could be enhanced) | |
targetFields.forEach(targetField => { | |
const targetFieldName = targetField.split('.').pop().toLowerCase(); | |
// Find a source field with a similar name | |
const matchingSourceField = sourceFields.find(sourceField => { | |
const sourceFieldName = sourceField.split('.'').pop().toLowerCase(); | |
return sourceFieldName === targetFieldName; | |
}); | |
if (matchingSourceField) { | |
mappings.push({ | |
sourceField: matchingSourceField, | |
targetField: targetField, | |
transform: 'none', | |
transformParam: '' | |
}); | |
} | |
}); | |
renderMappings(); | |
if (mappings.length > 0) { | |
$executeMappingBtn.removeClass('hidden'); | |
showSuccess(`Auto-matched ${mappings.length} fields`); | |
} else { | |
showError('No fields could be automatically matched'); | |
} | |
}); | |
// Save mapping | |
$saveMappingBtn.on('click', function() { | |
if (mappings.length === 0) { | |
showError('No mappings to save'); | |
return; | |
} | |
// Prompt for mapping name | |
$mappingName.val(''); | |
$mappingDescription.val(''); | |
$saveMappingModal.removeClass('hidden'); | |
}); | |
$confirmSaveMapping.on('click', function() { | |
const name = $mappingName.val(); | |
const description = $mappingDescription.val(); | |
if (!name) { | |
showError('Please enter a name for the mapping'); | |
return; | |
} | |
$.ajax({ | |
url: 'api/save_mapping.php', | |
method: 'POST', | |
dataType: 'json', | |
data: { | |
name: name, | |
description: description, | |
sourceData: JSON.stringify(sourceData), | |
targetSchema: JSON.stringify(targetSchema), | |
mappings: JSON.stringify(mappings) | |
}, | |
success: function(data) { | |
if (data.error) { | |
showError(data.error); | |
} else { | |
$saveMappingModal.addClass('hidden'); | |
showSuccess('Mapping saved successfully!'); | |
} | |
}, | |
error: function(xhr, status, error) { | |
showError('Error saving mapping: ' + error); | |
} | |
}); | |
}); | |
$cancelSaveMapping.on('click', function() { | |
$saveMappingModal.addClass('hidden'); | |
}); | |
// Load saved mappings | |
$openSavedModal.on('click', function() { | |
$.ajax({ | |
url: 'api/get_mappings.php', | |
method: 'GET', | |
dataType: 'json', | |
success: function(data) { | |
if (data.error) { | |
showError(data.error); | |
} else { | |
$savedMappingsList.empty(); | |
if (data.mappings.length === 0) { | |
$noSavedMappings.removeClass('hidden'); | |
} else { | |
$noSavedMappings.addClass('hidden'); | |
data.mappings.forEach(mapping => { | |
const mappingEl = $(` | |
<div class="saved-mapping p-3 border border-gray-200 rounded-lg flex justify-between items-center"> | |
<div> | |
<div class="font-medium">${mapping.name}</div> | |
<div class="text-sm text-gray-500">${mapping.description || 'No description'}</div> | |
<div class="text-xs text-gray-400">${new Date(mapping.created_at).toLocaleString()}</div> | |
</div> | |
<button class="text-blue-500 hover:text-blue-700" data-id="${mapping.id}"> | |
<i class="fas fa-arrow-right"></i> | |
</button> | |
</div> | |
`); | |
mappingEl.find('button').on('click', function() { | |
loadSavedMapping($(this).data('id')); | |
}); | |
$savedMappingsList.append(mappingEl); | |
}); | |
} | |
$savedMappingsModal.removeClass('hidden'); | |
} | |
}, | |
error: function(xhr, status, error) { | |
showError('Error loading saved mappings: ' + error); | |
} | |
}); | |
}); | |
function loadSavedMapping(id) { | |
$.ajax({ | |
url: 'api/get_mapping.php', | |
method: 'GET', | |
dataType: 'json', | |
data: { id: id }, | |
success: function(data) { | |
if (data.error) { | |
showError(data.error); | |
} else { | |
try { | |
sourceData = JSON.parse(data.source_data); | |
targetSchema = JSON.parse(data.target_schema); | |
mappings = JSON.parse(data.mappings); | |
// Update JSON editors | |
$sourceDataJson.val(JSON.stringify(sourceData, null, 2)); | |
$targetSchemaJson.val(JSON.stringify(targetSchema, null, 2)); | |
populateSourceFields(); | |
populateTargetFields(); | |
renderMappings(); | |
if (mappings.length > 0) { | |
$executeMappingBtn.removeClass('hidden'); | |
} | |
$savedMappingsModal.addClass('hidden'); | |
showSuccess(`Mapping "${data.name}" loaded successfully!`); | |
} catch (e) { | |
showError('Error parsing saved mapping: ' + e.message); | |
} | |
} | |
}, | |
error: function(xhr, status, error) { | |
showError('Error loading mapping: ' + error); | |
} | |
}); | |
} | |
$closeSavedModal.add($cancelSavedModal).on('click', function() { | |
$savedMappingsModal.addClass('hidden'); | |
}); | |
// Database configuration | |
$(document).on('keydown', function(e) { | |
// Ctrl+Alt+D to open DB config | |
if (e.ctrlKey && e.altKey && e.key === 'd') { | |
e.preventDefault(); | |
$dbConfigModal.removeClass('hidden'); | |
} | |
}); | |
$testDbConnection.on('click', function() { | |
const host = $configHost.val(); | |
const username = $configUsername.val(); | |
const password = $configPassword.val(); | |
const database = $configDatabase.val(); | |
if (!host || !username || !database) { | |
showError('Please fill all required fields'); | |
return; | |
} | |
$.ajax({ | |
url: 'api/test_db_connection.php', | |
method: 'POST', | |
dataType: 'json', | |
data: { | |
host: host, | |
username: username, | |
password: password, | |
database: database | |
}, | |
success: function(data) { | |
if (data.error) { | |
showError(data.error); | |
} else { | |
showSuccess('Connection successful!'); | |
} | |
}, | |
error: function(xhr, status, error) { | |
showError('Connection failed: ' + error); | |
} | |
}); | |
}); | |
$saveDbConfig.on('click', function() { | |
dbConfig = { | |
host: $configHost.val(), | |
database: $configDatabase.val(), | |
username: $configUsername.val(), | |
password: $configPassword.val() | |
}; | |
localStorage.setItem('dbConfig', JSON.stringify(dbConfig)); | |
$dbConfigModal.addClass('hidden'); | |
showSuccess('Database configuration saved!'); | |
}); | |
$cancelDbConfig.on('click', function() { | |
$dbConfigModal.addClass('hidden'); | |
}); | |
// Clear buttons | |
$clearSourceBtn.on('click', function() { | |
sourceData = null; | |
$sourceDataJson.val(''); | |
$sourceFieldSelect.empty().append('<option value="">Select a source field</option>'); | |
$('[data-tab="json-source"]').click(); | |
}); | |
$clearTargetBtn.on('click', function() { | |
targetSchema = null; | |
$targetSchemaJson.val(''); | |
$targetFieldSelect.empty().append('<option value="">Select a target field</option>'); | |
$('[data-tab="json-target"]').click(); | |
}); | |
$clearMappingsBtn.on('click', function() { | |
mappings = []; | |
renderMappings(); | |
$executeMappingBtn.addClass('hidden'); | |
$outputResult.addClass('hidden'); | |
$outputEmptyState.removeClass('hidden'); | |
$sqlPreviewContainer.addClass('hidden'); | |
$previewSqlBtn.removeClass('bg-orange-200').addClass('bg-orange-100'); | |
}); | |
// Sample data buttons | |
$sampleSourceBtn.on('click', function() { | |
const sampleData = { | |
"user": { | |
"id": 12345, | |
"name": "John Doe", | |
"email": " [email protected] ", | |
"registrationDate": new Date(), | |
"address": { | |
"street": "123 Main St", | |
"city": "Anytown", | |
"zip": "12345" | |
}, | |
"preferences": { | |
"newsletter": true, | |
"notifications": false | |
} | |
}, | |
"order": { | |
"id": "ORD-67890", | |
"total": "99.99", | |
"items": [ | |
{"productId": 1, "quantity": 2}, | |
{"productId": 2, "quantity": 1} | |
] | |
} | |
}; | |
loadSourceData(sampleData); | |
$sourceDataJson.val(JSON.stringify(sampleData, null, 2)); | |
$('[data-tab="json-source"]').click(); | |
showSuccess('Sample source data loaded!'); | |
}); | |
$sampleTargetBtn.on('click', function() { | |
const sampleSchema = { | |
"customer": { | |
"customerId": null, | |
"fullName": null, | |
"emailAddress": null, | |
"signupDate": null, | |
"contactInfo": { | |
"streetAddress": null, | |
"city": null, | |
"postalCode": null | |
}, | |
"marketingPreferences": { | |
"emailSubscribed": null | |
} | |
}, | |
"transaction": { | |
"orderId": null, | |
"amount": null, | |
"lineItems": [] | |
} | |
}; | |
loadTargetSchema(sampleSchema); | |
$targetSchemaJson.val(JSON.stringify(sampleSchema, null, 2)); | |
$('[data-tab="json-target"]').click(); | |
showSuccess('Sample target schema loaded!'); | |
}); | |
// Notification functions | |
function showError(message) { | |
const notification = $(` | |
<div class="fixed top-4 right-4 bg-red-100 border-l-4 border-red-500 text-red-700 p-4 rounded shadow-lg max-w-xs z-50"> | |
<div class="flex items-center"> | |
<i class="fas fa-exclamation-circle mr-2"></i> | |
<span>${message}</span> | |
</div> | |
</div> | |
`); | |
$('body').append(notification); | |
setTimeout(() => { | |
notification.addClass('opacity-0 transition-opacity duration-500'); | |
setTimeout(() => notification.remove(), 500); | |
}, 3000); | |
} | |
function showSuccess(message) { | |
const notification = $(` | |
<div class="fixed top-4 right-4 bg-green-100 border-l-4 border-green-500 text-green-700 p-4 rounded shadow-lg max-w-xs z-50"> | |
<div class="flex items-center"> | |
<i class="fas fa-check-circle mr-2"></i> | |
<span>${message}</span> | |
</div> | |
</div> | |
`); | |
$('body').append(notification); | |
setTimeout(() => { | |
notification.addClass('opacity-0 transition-opacity duration-500'); | |
setTimeout(() => notification.remove(), 500); | |
}, 3000); | |
} | |
// Initialize with JSON editor tabs selected | |
$('[data-tab="json-source"]').click(); | |
$('[data-tab="json-target"]').click(); | |
}); | |
</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment