mehrdadmoslemi commited on
Commit
903fe5b
·
verified ·
1 Parent(s): 73a6e1e

add custom python function for mapping rules - Initial Deployment

Browse files
Files changed (2) hide show
  1. README.md +7 -5
  2. index.html +1272 -19
README.md CHANGED
@@ -1,10 +1,12 @@
1
  ---
2
- title: Mm Datamapper
3
- emoji: 😻
4
- colorFrom: yellow
5
- colorTo: green
6
  sdk: static
7
  pinned: false
 
 
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
1
  ---
2
+ title: mm-datamapper
3
+ emoji: 🐳
4
+ colorFrom: pink
5
+ colorTo: gray
6
  sdk: static
7
  pinned: false
8
+ tags:
9
+ - deepsite
10
  ---
11
 
12
+ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
index.html CHANGED
@@ -1,19 +1,1272 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
19
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Data Mapper Pro</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <script src="https://unpkg.com/react@18/umd/react.development.js"></script>
9
+ <script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
10
+ <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
11
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
12
+ <style>
13
+ .resize-handle {
14
+ width: 4px;
15
+ background-color: #e5e7eb;
16
+ cursor: col-resize;
17
+ transition: background-color 0.2s;
18
+ }
19
+ .resize-handle:hover {
20
+ background-color: #3b82f6;
21
+ }
22
+ .json-viewer {
23
+ font-family: monospace;
24
+ white-space: pre;
25
+ padding: 10px;
26
+ border-radius: 4px;
27
+ background-color: #f8fafc;
28
+ height: 100%;
29
+ overflow: auto;
30
+ }
31
+ .mapping-line {
32
+ position: absolute;
33
+ height: 2px;
34
+ background-color: #3b82f6;
35
+ transform-origin: left center;
36
+ z-index: 10;
37
+ }
38
+ .draggable-field {
39
+ cursor: grab;
40
+ transition: all 0.2s;
41
+ }
42
+ .draggable-field:hover {
43
+ background-color: #e0e7ff;
44
+ }
45
+ .draggable-field.dragging {
46
+ opacity: 0.7;
47
+ cursor: grabbing;
48
+ }
49
+ .drop-target {
50
+ transition: all 0.2s;
51
+ }
52
+ .drop-target.highlight {
53
+ background-color: #dbeafe;
54
+ }
55
+ .flask-code {
56
+ font-family: monospace;
57
+ white-space: pre;
58
+ background-color: #1e293b;
59
+ color: #f8fafc;
60
+ padding: 10px;
61
+ border-radius: 4px;
62
+ overflow: auto;
63
+ height: 100%;
64
+ }
65
+ .react-code {
66
+ font-family: monospace;
67
+ white-space: pre;
68
+ background-color: #1e293b;
69
+ color: #f8fafc;
70
+ padding: 10px;
71
+ border-radius: 4px;
72
+ overflow: auto;
73
+ height: 100%;
74
+ }
75
+ </style>
76
+ </head>
77
+ <body class="bg-gray-50">
78
+ <div id="root"></div>
79
+
80
+ <script type="text/babel">
81
+ const { useState, useEffect, useRef } = React;
82
+
83
+ const DataMapperApp = () => {
84
+ const [activeTab, setActiveTab] = useState('mapper');
85
+ const [sourceData, setSourceData] = useState({
86
+ type: 'json',
87
+ content: JSON.stringify({
88
+ users: [
89
+ { id: 1, name: "John Doe", email: "john@example.com", age: 30 },
90
+ { id: 2, name: "Jane Smith", email: "jane@example.com", age: 25 }
91
+ ]
92
+ }, null, 2)
93
+ });
94
+ const [targetSchema, setTargetSchema] = useState({
95
+ type: 'json',
96
+ content: JSON.stringify({
97
+ customers: [
98
+ { customer_id: null, full_name: null, contact_email: null }
99
+ ]
100
+ }, null, 2)
101
+ });
102
+ const [mappings, setMappings] = useState([]);
103
+ const transformTypes = [
104
+ { value: 'direct', label: 'Direct Map' },
105
+ { value: 'concat', label: 'Concatenate' },
106
+ { value: 'upper', label: 'Uppercase' },
107
+ { value: 'lower', label: 'Lowercase' },
108
+ { value: 'capitalize', label: 'Capitalize' },
109
+ { value: 'trim', label: 'Trim Whitespace' },
110
+ { value: 'replace', label: 'Find & Replace' },
111
+ { value: 'math', label: 'Math Operation' },
112
+ { value: 'date', label: 'Date Format' },
113
+ { value: 'custom', label: 'Custom Python' },
114
+ { value: 'custom_function', label: 'Python Function' }
115
+ ];
116
+ const [flaskCode, setFlaskCode] = useState('');
117
+ const [reactCode, setReactCode] = useState('');
118
+ const [showPreview, setShowPreview] = useState(false);
119
+ const [previewData, setPreviewData] = useState(null);
120
+ const [isDragging, setIsDragging] = useState(false);
121
+ const [draggedField, setDraggedField] = useState(null);
122
+ const [showHelp, setShowHelp] = useState(false);
123
+ const [connectionLines, setConnectionLines] = useState([]);
124
+
125
+ const sourceContainerRef = useRef(null);
126
+ const targetContainerRef = useRef(null);
127
+ const mapperContainerRef = useRef(null);
128
+
129
+ // Parse source and target data
130
+ const parsedSourceData = parseData(sourceData);
131
+ const parsedTargetSchema = parseData(targetSchema);
132
+
133
+ // Generate code when mappings change
134
+ useEffect(() => {
135
+ generateBackendCode();
136
+ generateFrontendCode();
137
+ updateConnectionLines();
138
+ }, [mappings, sourceData, targetSchema]);
139
+
140
+ // Update connection lines when container sizes change
141
+ useEffect(() => {
142
+ const observer = new ResizeObserver(() => {
143
+ updateConnectionLines();
144
+ });
145
+
146
+ if (sourceContainerRef.current) {
147
+ observer.observe(sourceContainerRef.current);
148
+ }
149
+ if (targetContainerRef.current) {
150
+ observer.observe(targetContainerRef.current);
151
+ }
152
+ if (mapperContainerRef.current) {
153
+ observer.observe(mapperContainerRef.current);
154
+ }
155
+
156
+ return () => {
157
+ observer.disconnect();
158
+ };
159
+ }, []);
160
+
161
+ function parseData(data) {
162
+ try {
163
+ if (data.type === 'json') {
164
+ return JSON.parse(data.content);
165
+ }
166
+ // Add other parsers for CSV, XML, etc.
167
+ return null;
168
+ } catch (e) {
169
+ console.error("Error parsing data:", e);
170
+ return null;
171
+ }
172
+ }
173
+
174
+ function generateBackendCode() {
175
+ let code = `from flask import Flask, request, jsonify
176
+ import psycopg2
177
+ from psycopg2 import sql
178
+ import json
179
+
180
+ app = Flask(__name__)
181
+
182
+ # Database configuration
183
+ DB_CONFIG = {
184
+ 'dbname': 'your_database',
185
+ 'user': 'your_username',
186
+ 'password': 'your_password',
187
+ 'host': 'localhost',
188
+ 'port': '5432'
189
+ }
190
+
191
+ def get_db_connection():
192
+ conn = psycopg2.connect(**DB_CONFIG)
193
+ return conn
194
+
195
+ @app.route('/transform', methods=['POST'])
196
+ def transform_data():
197
+ try:
198
+ # Get source data from request
199
+ source_data = request.json
200
+
201
+ # Transform data based on mappings
202
+ transformed_data = transform_function(source_data)
203
+
204
+ return jsonify(transformed_data), 200
205
+ except Exception as e:
206
+ return jsonify({'error': str(e)}), 500
207
+
208
+ def transform_function(source_data):
209
+ # Initialize transformed data structure
210
+ transformed_data = ${JSON.stringify(parsedTargetSchema, null, 4).replace(/"null"/g, 'None')}
211
+
212
+ # Custom functions
213
+ ${generateCustomFunctions()}
214
+
215
+ # Apply mappings
216
+ ${generateMappingCode()}
217
+
218
+ return transformed_data
219
+
220
+ @app.route('/save', methods=['POST'])
221
+ def save_data():
222
+ try:
223
+ data = request.json
224
+ conn = get_db_connection()
225
+ cur = conn.cursor()
226
+
227
+ # Insert data into PostgreSQL
228
+ ${generateInsertCode()}
229
+
230
+ conn.commit()
231
+ cur.close()
232
+ conn.close()
233
+
234
+ return jsonify({'message': 'Data saved successfully'}), 200
235
+ except Exception as e:
236
+ return jsonify({'error': str(e)}), 500
237
+
238
+ if __name__ == '__main__':
239
+ app.run(debug=True)
240
+ `;
241
+
242
+ setFlaskCode(code);
243
+ }
244
+
245
+ function generateMappingCode() {
246
+ if (mappings.length === 0) return "pass";
247
+
248
+ let code = "";
249
+
250
+ // Group mappings by target array
251
+ const mappingsByTarget = {};
252
+ mappings.forEach(mapping => {
253
+ if (!mappingsByTarget[mapping.targetArray]) {
254
+ mappingsByTarget[mapping.targetArray] = [];
255
+ }
256
+ mappingsByTarget[mapping.targetArray].push(mapping);
257
+ });
258
+
259
+ // Generate code for each target array
260
+ for (const [targetArray, targetMappings] of Object.entries(mappingsByTarget)) {
261
+ code += `# Transform ${targetArray}\n`;
262
+ code += `for i, source_item in enumerate(source_data.get('${mappings[0].sourceArray}', [])):\n`;
263
+ code += ` if len(transformed_data['${targetArray}']) <= i:\n`;
264
+ code += ` transformed_data['${targetArray}'].append({})\n`;
265
+ code += ` target_item = transformed_data['${targetArray}'][i]\n\n`;
266
+
267
+ targetMappings.forEach(mapping => {
268
+ if (mapping.transform === 'direct') {
269
+ code += ` target_item['${mapping.targetField}'] = source_item.get('${mapping.sourceField}')\n`;
270
+ } else if (mapping.transform === 'concat') {
271
+ const fields = mapping.transformParams.fields.map(f => `str(source_item.get('${f}', ''))`).join(" + ' ' + ");
272
+ code += ` target_item['${mapping.targetField}'] = ${fields}\n`;
273
+ } else if (mapping.transform === 'upper') {
274
+ code += ` target_item['${mapping.targetField}'] = str(source_item.get('${mapping.sourceField}', '')).upper()\n`;
275
+ } else if (mapping.transform === 'lower') {
276
+ code += ` target_item['${mapping.targetField}'] = str(source_item.get('${mapping.sourceField}', '')).lower()\n`;
277
+ } else if (mapping.transform === 'capitalize') {
278
+ code += ` target_item['${mapping.targetField}'] = str(source_item.get('${mapping.sourceField}', '')).capitalize()\n`;
279
+ } else if (mapping.transform === 'trim') {
280
+ code += ` target_item['${mapping.targetField}'] = str(source_item.get('${mapping.sourceField}', '')).strip()\n`;
281
+ } else if (mapping.transform === 'replace') {
282
+ code += ` target_item['${mapping.targetField}'] = str(source_item.get('${mapping.sourceField}', '')).replace('${mapping.transformParams.find}', '${mapping.transformParams.replace}')\n`;
283
+ } else if (mapping.transform === 'math') {
284
+ code += ` try:\n`;
285
+ code += ` value = float(source_item.get('${mapping.sourceField}', 0))\n`;
286
+ code += ` target_item['${mapping.targetField}'] = value ${mapping.transformParams.operation} ${mapping.transformParams.value || 0}\n`;
287
+ code += ` except ValueError:\n`;
288
+ code += ` target_item['${mapping.targetField}'] = None\n`;
289
+ } else if (mapping.transform === 'date') {
290
+ code += ` try:\n`;
291
+ code += ` from datetime import datetime\n`;
292
+ code += ` date_str = source_item.get('${mapping.sourceField}', '')\n`;
293
+ code += ` if date_str:\n`;
294
+ code += ` parsed_date = datetime.strptime(date_str, '${mapping.transformParams.format}')\n`;
295
+ code += ` target_item['${mapping.targetField}'] = parsed_date.strftime('${mapping.transformParams.format}')\n`;
296
+ code += ` else:\n`;
297
+ code += ` target_item['${mapping.targetField}'] = None\n`;
298
+ code += ` except ValueError:\n`;
299
+ code += ` target_item['${mapping.targetField}'] = None\n`;
300
+ } else if (mapping.transform === 'custom') {
301
+ code += ` try:\n`;
302
+ code += ` value = source_item.get('${mapping.sourceField}')\n`;
303
+ code += ` target_item['${mapping.targetField}'] = ${mapping.transformParams.code}\n`;
304
+ code += ` except Exception as e:\n`;
305
+ code += ` target_item['${mapping.targetField}'] = None\n`;
306
+ } else if (mapping.transform === 'custom_function') {
307
+ code += ` try:\n`;
308
+ code += ` value = source_item.get('${mapping.sourceField}')\n`;
309
+ code += ` target_item['${mapping.targetField}'] = custom_${mapping.id}(value)\n`;
310
+ code += ` except Exception as e:\n`;
311
+ code += ` target_item['${mapping.targetField}'] = None\n`;
312
+ }
313
+ });
314
+
315
+ code += "\n";
316
+ }
317
+
318
+ return code;
319
+ }
320
+
321
+ function generateCustomFunctions() {
322
+ if (mappings.length === 0) return "";
323
+
324
+ let code = "";
325
+ mappings.forEach(mapping => {
326
+ if (mapping.transform === 'custom_function' && mapping.transformParams.functionCode) {
327
+ code += `def custom_${mapping.id}(value):\n`;
328
+ code += ` ${mapping.transformParams.functionCode.replace(/\n/g, '\n ')}\n\n`;
329
+ }
330
+ });
331
+ return code;
332
+ }
333
+
334
+ function generateInsertCode() {
335
+ if (!parsedTargetSchema || Object.keys(parsedTargetSchema).length === 0) {
336
+ return "# No target schema defined";
337
+ }
338
+
339
+ const targetArray = Object.keys(parsedTargetSchema)[0];
340
+ const fields = Object.keys(parsedTargetSchema[targetArray][0]);
341
+
342
+ let code = `# Prepare insert statement\n`;
343
+ code += `table_name = '${targetArray}'\n`;
344
+ code += `columns = ${JSON.stringify(fields)}\n\n`;
345
+ code += `# Create SQL query\n`;
346
+ code += `query = sql.SQL("INSERT INTO {} ({}) VALUES ({})").format(\n`;
347
+ code += ` sql.Identifier(table_name),\n`;
348
+ code += ` sql.SQL(', ').join(map(sql.Identifier, columns)),\n`;
349
+ code += ` sql.SQL(', ').join(sql.Placeholder() * len(columns))\n`;
350
+ code += `)\n\n`;
351
+ code += `# Execute for each item\n`;
352
+ code += `for item in data.get('${targetArray}', []):\n`;
353
+ code += ` values = [item.get(col) for col in columns]\n`;
354
+ code += ` cur.execute(query, values)\n`;
355
+
356
+ return code;
357
+ }
358
+
359
+ function generatePreview() {
360
+ try {
361
+ const transformed = {};
362
+ const source = parsedSourceData;
363
+
364
+ // Initialize target structure
365
+ Object.keys(parsedTargetSchema).forEach(key => {
366
+ transformed[key] = JSON.parse(JSON.stringify(parsedTargetSchema[key]));
367
+ });
368
+
369
+ // Apply mappings
370
+ mappings.forEach(mapping => {
371
+ const sourcePath = mapping.sourceArray.split('.');
372
+ const targetPath = mapping.targetArray.split('.');
373
+
374
+ let sourceValue = source;
375
+ let targetObj = transformed;
376
+
377
+ // Get source value
378
+ sourcePath.forEach(part => {
379
+ if (Array.isArray(sourceValue)) {
380
+ sourceValue = sourceValue[0][part];
381
+ } else {
382
+ sourceValue = sourceValue[part];
383
+ }
384
+ });
385
+
386
+ // Set target value
387
+ targetPath.slice(0, -1).forEach(part => {
388
+ if (!targetObj[part]) targetObj[part] = {};
389
+ targetObj = targetObj[part];
390
+ });
391
+
392
+ const targetField = targetPath[targetPath.length - 1];
393
+
394
+ // Apply transformation
395
+ if (mapping.transform === 'direct') {
396
+ targetObj[targetField] = sourceValue;
397
+ } else if (mapping.transform === 'upper') {
398
+ targetObj[targetField] = String(sourceValue).toUpperCase();
399
+ } else if (mapping.transform === 'lower') {
400
+ targetObj[targetField] = String(sourceValue).toLowerCase();
401
+ } else if (mapping.transform === 'concat') {
402
+ const fields = mapping.transformParams.fields || [];
403
+ targetObj[targetField] = fields.map(f => {
404
+ const parts = f.split('.');
405
+ let val = source;
406
+ parts.forEach(part => {
407
+ if (Array.isArray(val)) val = val[0][part];
408
+ else val = val[part];
409
+ });
410
+ return String(val || '');
411
+ }).join(' ');
412
+ }
413
+ // Add other transformations as needed
414
+ });
415
+
416
+ setPreviewData(transformed);
417
+ } catch (error) {
418
+ console.error("Preview generation error:", error);
419
+ setPreviewData({ error: error.message });
420
+ }
421
+ }
422
+
423
+ function generateFrontendCode() {
424
+ let code = `import React, { useState, useEffect } from 'react';
425
+ import './App.css';
426
+
427
+ function App() {
428
+ const [sourceData, setSourceData] = useState(${JSON.stringify(parsedSourceData, null, 2)});
429
+ const [transformedData, setTransformedData] = useState(null);
430
+ const [loading, setLoading] = useState(false);
431
+ const [error, setError] = useState(null);
432
+
433
+ const transformData = async () => {
434
+ setLoading(true);
435
+ setError(null);
436
+
437
+ try {
438
+ const response = await fetch('http://localhost:5000/transform', {
439
+ method: 'POST',
440
+ headers: {
441
+ 'Content-Type': 'application/json',
442
+ },
443
+ body: JSON.stringify(sourceData)
444
+ });
445
+
446
+ if (!response.ok) {
447
+ throw new Error('Transformation failed');
448
+ }
449
+
450
+ const data = await response.json();
451
+ setTransformedData(data);
452
+ } catch (err) {
453
+ setError(err.message);
454
+ } finally {
455
+ setLoading(false);
456
+ }
457
+ };
458
+
459
+ const saveData = async () => {
460
+ if (!transformedData) return;
461
+
462
+ setLoading(true);
463
+ setError(null);
464
+
465
+ try {
466
+ const response = await fetch('http://localhost:5000/save', {
467
+ method: 'POST',
468
+ headers: {
469
+ 'Content-Type': 'application/json',
470
+ },
471
+ body: JSON.stringify(transformedData)
472
+ });
473
+
474
+ if (!response.ok) {
475
+ throw new Error('Save failed');
476
+ }
477
+
478
+ alert('Data saved successfully!');
479
+ } catch (err) {
480
+ setError(err.message);
481
+ } finally {
482
+ setLoading(false);
483
+ }
484
+ };
485
+
486
+ return (
487
+ <div className="container mx-auto p-4">
488
+ <h1 className="text-2xl font-bold mb-4">Data Transformation App</h1>
489
+
490
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
491
+ <div className="border rounded p-4">
492
+ <h2 className="text-xl font-semibold mb-2">Source Data</h2>
493
+ <pre className="bg-gray-100 p-2 rounded overflow-auto">
494
+ {JSON.stringify(sourceData, null, 2)}
495
+ </pre>
496
+ </div>
497
+
498
+ <div className="border rounded p-4">
499
+ <h2 className="text-xl font-semibold mb-2">Transformed Data</h2>
500
+ {loading ? (
501
+ <p>Loading...</p>
502
+ ) : error ? (
503
+ <p className="text-red-500">{error}</p>
504
+ ) : transformedData ? (
505
+ <pre className="bg-gray-100 p-2 rounded overflow-auto">
506
+ {JSON.stringify(transformedData, null, 2)}
507
+ </pre>
508
+ ) : (
509
+ <p>No transformed data yet</p>
510
+ )}
511
+ </div>
512
+ </div>
513
+
514
+ <div className="flex space-x-4">
515
+ <button
516
+ onClick={transformData}
517
+ className="bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded"
518
+ disabled={loading}
519
+ >
520
+ Transform Data
521
+ </button>
522
+
523
+ <button
524
+ onClick={saveData}
525
+ className="bg-green-500 hover:bg-green-600 text-white px-4 py-2 rounded"
526
+ disabled={loading || !transformedData}
527
+ >
528
+ Save to Database
529
+ </button>
530
+ </div>
531
+ </div>
532
+ );
533
+ }
534
+
535
+ export default App;
536
+ `;
537
+
538
+ setReactCode(code);
539
+ }
540
+
541
+ function updateConnectionLines() {
542
+ if (!sourceContainerRef.current || !targetContainerRef.current || !mapperContainerRef.current) return;
543
+
544
+ const newLines = mappings.map(mapping => {
545
+ const sourceElement = document.querySelector(`[data-source-path="${mapping.sourceArray}.${mapping.sourceField}"]`);
546
+ const targetElement = document.querySelector(`[data-target-path="${mapping.targetArray}.${mapping.targetField}"]`);
547
+
548
+ if (!sourceElement || !targetElement) return null;
549
+
550
+ const sourceRect = sourceElement.getBoundingClientRect();
551
+ const targetRect = targetElement.getBoundingClientRect();
552
+ const containerRect = mapperContainerRef.current.getBoundingClientRect();
553
+
554
+ const startX = sourceRect.right - containerRect.left;
555
+ const startY = sourceRect.top + sourceRect.height / 2 - containerRect.top;
556
+ const endX = targetRect.left - containerRect.left;
557
+ const endY = targetRect.top + targetRect.height / 2 - containerRect.top;
558
+
559
+ const length = Math.sqrt(Math.pow(endX - startX, 2) + Math.pow(endY - startY, 2));
560
+ const angle = Math.atan2(endY - startY, endX - startX) * 180 / Math.PI;
561
+
562
+ return {
563
+ id: mapping.id,
564
+ left: startX,
565
+ top: startY,
566
+ width: length,
567
+ transform: `rotate(${angle}deg)`,
568
+ transformOrigin: '0 0'
569
+ };
570
+ }).filter(Boolean);
571
+
572
+ setConnectionLines(newLines);
573
+ }
574
+
575
+ function handleDragStart(sourceArray, sourceField) {
576
+ setIsDragging(true);
577
+ setDraggedField({ sourceArray, sourceField });
578
+ }
579
+
580
+ function handleDragEnd() {
581
+ setIsDragging(false);
582
+ setDraggedField(null);
583
+ }
584
+
585
+ function handleDrop(targetArray, targetField) {
586
+ if (!isDragging || !draggedField) return;
587
+
588
+ const newMapping = {
589
+ id: Date.now().toString(),
590
+ sourceArray: draggedField.sourceArray,
591
+ sourceField: draggedField.sourceField,
592
+ targetArray,
593
+ targetField,
594
+ transform: 'direct',
595
+ transformParams: {
596
+ fields: [],
597
+ find: '',
598
+ replace: '',
599
+ operation: '+',
600
+ value: '',
601
+ format: 'YYYY-MM-DD',
602
+ code: '',
603
+ functionCode: 'return value # Default function'
604
+ }
605
+ };
606
+
607
+ setMappings([...mappings, newMapping]);
608
+ setIsDragging(false);
609
+ setDraggedField(null);
610
+ }
611
+
612
+ function handleDragOver(e) {
613
+ if (isDragging) {
614
+ e.preventDefault();
615
+ }
616
+ }
617
+
618
+ function handleDeleteMapping(id) {
619
+ setMappings(mappings.filter(m => m.id !== id));
620
+ }
621
+
622
+ function handleSourceDataChange(e) {
623
+ setSourceData({
624
+ ...sourceData,
625
+ content: e.target.value
626
+ });
627
+ }
628
+
629
+ function handleTargetSchemaChange(e) {
630
+ setTargetSchema({
631
+ ...targetSchema,
632
+ content: e.target.value
633
+ });
634
+ }
635
+
636
+ function renderFields(data, type) {
637
+ if (!data) return <div className="text-red-500">Invalid data format</div>;
638
+
639
+ return Object.entries(data).map(([arrayName, items]) => {
640
+ if (!Array.isArray(items) || items.length === 0) return null;
641
+
642
+ const fields = Object.keys(items[0]);
643
+
644
+ return (
645
+ <div key={arrayName} className="mb-4">
646
+ <h3 className="font-semibold text-lg mb-2">{arrayName}</h3>
647
+ <div className="space-y-1">
648
+ {fields.map(field => {
649
+ const fullPath = `${arrayName}.${field}`;
650
+ return (
651
+ <div
652
+ key={`${type}-${arrayName}-${field}`}
653
+ data-source-path={type === 'source' ? fullPath : undefined}
654
+ data-target-path={type === 'target' ? fullPath : undefined}
655
+ className={`px-3 py-1 rounded ${type === 'source' ? 'draggable-field bg-blue-50' : 'drop-target'} ${isDragging && type === 'target' ? 'hover:bg-blue-100' : ''}`}
656
+ draggable={type === 'source'}
657
+ onDragStart={() => type === 'source' && handleDragStart(arrayName, field)}
658
+ onDragEnd={handleDragEnd}
659
+ onDragOver={handleDragOver}
660
+ onDrop={() => type === 'target' && handleDrop(arrayName, field)}
661
+ >
662
+ <div className="flex justify-between items-center">
663
+ <span>{field}</span>
664
+ {type === 'source' && (
665
+ <span className="text-gray-500 text-sm">
666
+ <i className="fas fa-grip-lines"></i>
667
+ </span>
668
+ )}
669
+ </div>
670
+ </div>
671
+ ))}
672
+ </div>
673
+ </div>
674
+ );
675
+ });
676
+ }
677
+
678
+ function renderMappings() {
679
+ if (mappings.length === 0) {
680
+ return (
681
+ <div className="text-center text-gray-500 py-8">
682
+ <i className="fas fa-arrows-alt-h text-4xl mb-2"></i>
683
+ <p>Drag fields from source to target to create mappings</p>
684
+ </div>
685
+ );
686
+ }
687
+
688
+ return (
689
+ <div className="space-y-4">
690
+ {mappings.map(mapping => (
691
+ <div key={mapping.id} className="bg-white p-3 rounded shadow-sm border">
692
+ <div className="flex justify-between items-center mb-2">
693
+ <div className="font-medium">
694
+ {mapping.sourceArray}.{mapping.sourceField} → {mapping.targetArray}.{mapping.targetField}
695
+ </div>
696
+ <button
697
+ onClick={() => handleDeleteMapping(mapping.id)}
698
+ className="text-red-500 hover:text-red-700"
699
+ >
700
+ <i className="fas fa-times"></i>
701
+ </button>
702
+ </div>
703
+ <div className="text-sm text-gray-600 mt-2">
704
+ <select
705
+ value={mapping.transform}
706
+ onChange={(e) => {
707
+ const updated = mappings.map(m =>
708
+ m.id === mapping.id ? {...m, transform: e.target.value} : m
709
+ );
710
+ setMappings(updated);
711
+ }}
712
+ className="text-xs border rounded px-2 py-1 mb-2 w-full"
713
+ >
714
+ {transformTypes.map(type => (
715
+ <option key={type.value} value={type.value}>{type.label}</option>
716
+ ))}
717
+ </select>
718
+
719
+ {mapping.transform === 'concat' && (
720
+ <div className="space-y-2">
721
+ <div className="flex items-center space-x-2">
722
+ <input
723
+ type="text"
724
+ value={mapping.transformParams.fields.join(',')}
725
+ onChange={(e) => {
726
+ const updated = mappings.map(m =>
727
+ m.id === mapping.id ? {
728
+ ...m,
729
+ transformParams: {
730
+ ...m.transformParams,
731
+ fields: e.target.value.split(',')
732
+ }
733
+ } : m
734
+ );
735
+ setMappings(updated);
736
+ }}
737
+ placeholder="Comma separated source fields"
738
+ className="text-xs border rounded px-2 py-1 flex-1"
739
+ />
740
+ </div>
741
+ </div>
742
+ )}
743
+
744
+ {mapping.transform === 'replace' && (
745
+ <div className="space-y-2">
746
+ <div className="flex items-center space-x-2">
747
+ <input
748
+ type="text"
749
+ value={mapping.transformParams.find}
750
+ onChange={(e) => {
751
+ const updated = mappings.map(m =>
752
+ m.id === mapping.id ? {
753
+ ...m,
754
+ transformParams: {
755
+ ...m.transformParams,
756
+ find: e.target.value
757
+ }
758
+ } : m
759
+ );
760
+ setMappings(updated);
761
+ }}
762
+ placeholder="Text to find"
763
+ className="text-xs border rounded px-2 py-1 flex-1"
764
+ />
765
+ </div>
766
+ <div className="flex items-center space-x-2">
767
+ <input
768
+ type="text"
769
+ value={mapping.transformParams.replace}
770
+ onChange={(e) => {
771
+ const updated = mappings.map(m =>
772
+ m.id === mapping.id ? {
773
+ ...m,
774
+ transformParams: {
775
+ ...m.transformParams,
776
+ replace: e.target.value
777
+ }
778
+ } : m
779
+ );
780
+ setMappings(updated);
781
+ }}
782
+ placeholder="Replacement text"
783
+ className="text-xs border rounded px-2 py-1 flex-1"
784
+ />
785
+ </div>
786
+ </div>
787
+ )}
788
+
789
+ {mapping.transform === 'math' && (
790
+ <div className="flex items-center space-x-2">
791
+ <select
792
+ value={mapping.transformParams.operation}
793
+ onChange={(e) => {
794
+ const updated = mappings.map(m =>
795
+ m.id === mapping.id ? {
796
+ ...m,
797
+ transformParams: {
798
+ ...m.transformParams,
799
+ operation: e.target.value
800
+ }
801
+ } : m
802
+ );
803
+ setMappings(updated);
804
+ }}
805
+ className="text-xs border rounded px-2 py-1"
806
+ >
807
+ <option value="+">Add</option>
808
+ <option value="-">Subtract</option>
809
+ <option value="*">Multiply</option>
810
+ <option value="/">Divide</option>
811
+ </select>
812
+ <input
813
+ type="text"
814
+ value={mapping.transformParams.value}
815
+ onChange={(e) => {
816
+ const updated = mappings.map(m =>
817
+ m.id === mapping.id ? {
818
+ ...m,
819
+ transformParams: {
820
+ ...m.transformParams,
821
+ value: e.target.value
822
+ }
823
+ } : m
824
+ );
825
+ setMappings(updated);
826
+ }}
827
+ placeholder="Value"
828
+ className="text-xs border rounded px-2 py-1 flex-1"
829
+ />
830
+ </div>
831
+ )}
832
+
833
+ {mapping.transform === 'date' && (
834
+ <div className="flex items-center space-x-2">
835
+ <input
836
+ type="text"
837
+ value={mapping.transformParams.format}
838
+ onChange={(e) => {
839
+ const updated = mappings.map(m =>
840
+ m.id === mapping.id ? {
841
+ ...m,
842
+ transformParams: {
843
+ ...m.transformParams,
844
+ format: e.target.value
845
+ }
846
+ } : m
847
+ );
848
+ setMappings(updated);
849
+ }}
850
+ placeholder="Date format (e.g. YYYY-MM-DD)"
851
+ className="text-xs border rounded px-2 py-1 flex-1"
852
+ />
853
+ </div>
854
+ )}
855
+
856
+ {mapping.transform === 'custom' && (
857
+ <textarea
858
+ value={mapping.transformParams.code}
859
+ onChange={(e) => {
860
+ const updated = mappings.map(m =>
861
+ m.id === mapping.id ? {
862
+ ...m,
863
+ transformParams: {
864
+ ...m.transformParams,
865
+ code: e.target.value
866
+ }
867
+ } : m
868
+ );
869
+ setMappings(updated);
870
+ }}
871
+ placeholder="Python transformation code (use 'value' for input)"
872
+ className="text-xs border rounded px-2 py-1 w-full h-20 font-mono"
873
+ />
874
+ )}
875
+ {mapping.transform === 'custom_function' && (
876
+ <div>
877
+ <p className="text-xs text-gray-500 mb-1">Define a Python function that takes 'value' as input and returns the transformed value:</p>
878
+ <textarea
879
+ value={mapping.transformParams.functionCode}
880
+ onChange={(e) => {
881
+ const updated = mappings.map(m =>
882
+ m.id === mapping.id ? {
883
+ ...m,
884
+ transformParams: {
885
+ ...m.transformParams,
886
+ functionCode: e.target.value
887
+ }
888
+ } : m
889
+ );
890
+ setMappings(updated);
891
+ }}
892
+ placeholder="def transform(value):\n # Your code here\n return value"
893
+ className="text-xs border rounded px-2 py-1 w-full h-32 font-mono"
894
+ />
895
+ </div>
896
+ )}
897
+ </div>
898
+ </div>
899
+ ))}
900
+ </div>
901
+ );
902
+ }
903
+
904
+ return (
905
+ <div className="min-h-screen flex flex-col">
906
+ {/* Header */}
907
+ <header className="bg-gray-800 text-white p-4 shadow">
908
+ <div className="container mx-auto flex justify-between items-center">
909
+ <h1 className="text-2xl font-bold">
910
+ <i className="fas fa-exchange-alt mr-2"></i>
911
+ Data Mapper Pro
912
+ </h1>
913
+ <div className="flex space-x-4">
914
+ <button
915
+ onClick={() => setShowHelp(!showHelp)}
916
+ className="bg-gray-700 hover:bg-gray-600 px-3 py-1 rounded"
917
+ >
918
+ <i className="fas fa-question-circle mr-1"></i>
919
+ Help
920
+ </button>
921
+ <button className="bg-blue-600 hover:bg-blue-500 px-3 py-1 rounded">
922
+ <i className="fas fa-save mr-1"></i>
923
+ Save Project
924
+ </button>
925
+ </div>
926
+ </div>
927
+ </header>
928
+
929
+ {/* Help Modal */}
930
+ {showHelp && (
931
+ <div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
932
+ <div className="bg-white rounded-lg shadow-xl p-6 max-w-2xl w-full max-h-[80vh] overflow-auto">
933
+ <div className="flex justify-between items-center mb-4">
934
+ <h2 className="text-xl font-bold">Data Mapper Help</h2>
935
+ <button
936
+ onClick={() => setShowHelp(false)}
937
+ className="text-gray-500 hover:text-gray-700"
938
+ >
939
+ <i className="fas fa-times"></i>
940
+ </button>
941
+ </div>
942
+ <div className="space-y-4">
943
+ <div>
944
+ <h3 className="font-semibold mb-2">How to create mappings:</h3>
945
+ <ol className="list-decimal pl-5 space-y-1">
946
+ <li>Drag fields from the Source panel to the Target panel</li>
947
+ <li>Mappings will appear in the Mappings panel</li>
948
+ <li>View generated code in the Backend and Frontend tabs</li>
949
+ </ol>
950
+ </div>
951
+ <div>
952
+ <h3 className="font-semibold mb-2">Backend Features:</h3>
953
+ <ul className="list-disc pl-5 space-y-1">
954
+ <li>Flask REST API for data transformation</li>
955
+ <li>Direct PostgreSQL integration without ORM</li>
956
+ <li>Automatic code generation for transformations</li>
957
+ </ul>
958
+ </div>
959
+ <div>
960
+ <h3 className="font-semibold mb-2">Frontend Features:</h3>
961
+ <ul className="list-disc pl-5 space-y-1">
962
+ <li>ReactJS interface for data transformation</li>
963
+ <li>Sample UI for sending data to backend</li>
964
+ <li>Error handling and loading states</li>
965
+ </ul>
966
+ </div>
967
+ </div>
968
+ </div>
969
+ </div>
970
+ )}
971
+
972
+ {/* Main Content */}
973
+ <main className="flex-grow container mx-auto p-4">
974
+ {/* Tabs */}
975
+ <div className="mb-6 border-b border-gray-200">
976
+ <nav className="flex space-x-8">
977
+ <button
978
+ onClick={() => setActiveTab('mapper')}
979
+ className={`py-4 px-1 border-b-2 font-medium text-sm ${activeTab === 'mapper' ? 'border-blue-500 text-blue-600' : 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300'}`}
980
+ >
981
+ <i className="fas fa-project-diagram mr-2"></i>
982
+ Data Mapper
983
+ </button>
984
+ <button
985
+ onClick={() => setActiveTab('backend')}
986
+ className={`py-4 px-1 border-b-2 font-medium text-sm ${activeTab === 'backend' ? 'border-blue-500 text-blue-600' : 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300'}`}
987
+ >
988
+ <i className="fas fa-server mr-2"></i>
989
+ Backend Code
990
+ </button>
991
+ <button
992
+ onClick={() => setActiveTab('frontend')}
993
+ className={`py-4 px-1 border-b-2 font-medium text-sm ${activeTab === 'frontend' ? 'border-blue-500 text-blue-600' : 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300'}`}
994
+ >
995
+ <i className="fas fa-laptop-code mr-2"></i>
996
+ Frontend Code
997
+ </button>
998
+ </nav>
999
+ </div>
1000
+
1001
+ {/* Tab Content */}
1002
+ {activeTab === 'mapper' && (
1003
+ <div className="bg-white rounded-lg shadow overflow-hidden">
1004
+ <div className="p-4 border-b">
1005
+ <h2 className="text-lg font-medium">Data Transformation Mapper</h2>
1006
+ <p className="text-sm text-gray-600">Define how fields from source data map to target schema</p>
1007
+ </div>
1008
+
1009
+ <div className="p-4 border-b flex justify-between items-center">
1010
+ <div>
1011
+ <h2 className="text-lg font-medium">Data Transformation Mapper</h2>
1012
+ <p className="text-sm text-gray-600">Define how fields from source data map to target schema</p>
1013
+ </div>
1014
+ <button
1015
+ onClick={() => {
1016
+ setShowPreview(true);
1017
+ generatePreview();
1018
+ }}
1019
+ className="bg-blue-500 hover:bg-blue-600 text-white px-3 py-1 rounded text-sm"
1020
+ >
1021
+ <i className="fas fa-eye mr-1"></i>
1022
+ Preview Transformation
1023
+ </button>
1024
+ </div>
1025
+
1026
+ <div ref={mapperContainerRef} className="relative">
1027
+ {/* Connection lines */}
1028
+ {connectionLines.map(line => (
1029
+ <div
1030
+ key={line.id}
1031
+ className="mapping-line"
1032
+ style={{
1033
+ left: `${line.left}px`,
1034
+ top: `${line.top}px`,
1035
+ width: `${line.width}px`,
1036
+ transform: line.transform,
1037
+ transformOrigin: line.transformOrigin,
1038
+ opacity: isDragging ? 0.7 : 1,
1039
+ transition: 'opacity 0.2s'
1040
+ }}
1041
+ ></div>
1042
+ ))}
1043
+
1044
+ <div className="grid grid-cols-1 md:grid-cols-3 gap-0">
1045
+ {/* Source Data */}
1046
+ <div
1047
+ ref={sourceContainerRef}
1048
+ className="p-4 border-r h-[500px] overflow-auto"
1049
+ >
1050
+ <div className="flex justify-between items-center mb-4">
1051
+ <h3 className="font-medium text-blue-600">
1052
+ <i className="fas fa-database mr-2"></i>
1053
+ Source Data
1054
+ </h3>
1055
+ <div className="relative">
1056
+ <select
1057
+ value={sourceData.type}
1058
+ onChange={(e) => setSourceData({...sourceData, type: e.target.value})}
1059
+ className="text-xs border rounded px-2 py-1"
1060
+ >
1061
+ <option value="json">JSON</option>
1062
+ <option value="csv">CSV</option>
1063
+ <option value="xml">XML</option>
1064
+ </select>
1065
+ </div>
1066
+ </div>
1067
+
1068
+ {sourceData.type === 'json' ? (
1069
+ <textarea
1070
+ value={sourceData.content}
1071
+ onChange={handleSourceDataChange}
1072
+ className="w-full h-40 border rounded p-2 font-mono text-sm mb-4"
1073
+ placeholder="Paste your source JSON here"
1074
+ />
1075
+ ) : (
1076
+ <div className="bg-gray-100 p-4 rounded text-center text-gray-500">
1077
+ {sourceData.type.toUpperCase()} support coming soon
1078
+ </div>
1079
+ )}
1080
+
1081
+ <div className="mt-4">
1082
+ <h4 className="font-medium mb-2">Fields</h4>
1083
+ {renderFields(parsedSourceData, 'source')}
1084
+ </div>
1085
+ </div>
1086
+
1087
+ {/* Mappings */}
1088
+ <div className="p-4 border-r h-[500px] overflow-auto">
1089
+ <h3 className="font-medium text-purple-600 mb-4">
1090
+ <i className="fas fa-arrows-alt-h mr-2"></i>
1091
+ Mappings
1092
+ </h3>
1093
+ {renderMappings()}
1094
+ </div>
1095
+
1096
+ {/* Target Schema */}
1097
+ <div
1098
+ ref={targetContainerRef}
1099
+ className="p-4 h-[500px] overflow-auto"
1100
+ >
1101
+ <div className="flex justify-between items-center mb-4">
1102
+ <h3 className="font-medium text-green-600">
1103
+ <i className="fas fa-bullseye mr-2"></i>
1104
+ Target Schema
1105
+ </h3>
1106
+ <div className="relative">
1107
+ <select
1108
+ value={targetSchema.type}
1109
+ onChange={(e) => setTargetSchema({...targetSchema, type: e.target.value})}
1110
+ className="text-xs border rounded px-2 py-1"
1111
+ >
1112
+ <option value="json">JSON</option>
1113
+ <option value="sql">SQL Table</option>
1114
+ </select>
1115
+ </div>
1116
+ </div>
1117
+
1118
+ {targetSchema.type === 'json' ? (
1119
+ <textarea
1120
+ value={targetSchema.content}
1121
+ onChange={handleTargetSchemaChange}
1122
+ className="w-full h-40 border rounded p-2 font-mono text-sm mb-4"
1123
+ placeholder="Define your target JSON schema here"
1124
+ />
1125
+ ) : (
1126
+ <div className="bg-gray-100 p-4 rounded text-center text-gray-500">
1127
+ SQL schema support coming soon
1128
+ </div>
1129
+ )}
1130
+
1131
+ <div className="mt-4">
1132
+ <h4 className="font-medium mb-2">Fields</h4>
1133
+ {renderFields(parsedTargetSchema, 'target')}
1134
+ </div>
1135
+ </div>
1136
+ </div>
1137
+ </div>
1138
+ </div>
1139
+ )}
1140
+
1141
+ {activeTab === 'backend' && (
1142
+ <div className="bg-white rounded-lg shadow overflow-hidden">
1143
+ <div className="p-4 border-b">
1144
+ <h2 className="text-lg font-medium">Generated Backend Code</h2>
1145
+ <p className="text-sm text-gray-600">Flask API with PostgreSQL integration</p>
1146
+ </div>
1147
+ <div className="p-4">
1148
+ <div className="flex justify-end mb-2">
1149
+ <button
1150
+ onClick={() => navigator.clipboard.writeText(flaskCode)}
1151
+ className="bg-gray-200 hover:bg-gray-300 px-3 py-1 rounded text-sm"
1152
+ >
1153
+ <i className="fas fa-copy mr-1"></i>
1154
+ Copy Code
1155
+ </button>
1156
+ </div>
1157
+ <div className="flask-code">
1158
+ {flaskCode}
1159
+ </div>
1160
+ <div className="mt-4 p-4 bg-yellow-50 border border-yellow-200 rounded">
1161
+ <h3 className="font-medium text-yellow-800 mb-2">
1162
+ <i className="fas fa-exclamation-triangle mr-2"></i>
1163
+ Implementation Notes
1164
+ </h3>
1165
+ <ul className="list-disc pl-5 text-sm text-yellow-800 space-y-1">
1166
+ <li>Replace database configuration with your actual PostgreSQL credentials</li>
1167
+ <li>Install required packages: <code className="bg-yellow-100 px-1 rounded">pip install flask psycopg2</code></li>
1168
+ <li>The generated code uses raw SQL without ORM for maximum control</li>
1169
+ <li>Add error handling and logging as needed for production use</li>
1170
+ </ul>
1171
+ </div>
1172
+ </div>
1173
+ </div>
1174
+ )}
1175
+
1176
+ {activeTab === 'frontend' && (
1177
+ <div className="bg-white rounded-lg shadow overflow-hidden">
1178
+ <div className="p-4 border-b">
1179
+ <h2 className="text-lg font-medium">Generated Frontend Code</h2>
1180
+ <p className="text-sm text-gray-600">ReactJS interface for data transformation</p>
1181
+ </div>
1182
+ <div className="p-4">
1183
+ <div className="flex justify-end mb-2">
1184
+ <button
1185
+ onClick={() => navigator.clipboard.writeText(reactCode)}
1186
+ className="bg-gray-200 hover:bg-gray-300 px-3 py-1 rounded text-sm"
1187
+ >
1188
+ <i className="fas fa-copy mr-1"></i>
1189
+ Copy Code
1190
+ </button>
1191
+ </div>
1192
+ <div className="react-code">
1193
+ {reactCode}
1194
+ </div>
1195
+ <div className="mt-4 p-4 bg-blue-50 border border-blue-200 rounded">
1196
+ <h3 className="font-medium text-blue-800 mb-2">
1197
+ <i className="fas fa-info-circle mr-2"></i>
1198
+ Implementation Notes
1199
+ </h3>
1200
+ <ul className="list-disc pl-5 text-sm text-blue-800 space-y-1">
1201
+ <li>Create a new React app with <code className="bg-blue-100 px-1 rounded">npx create-react-app data-transformer</code></li>
1202
+ <li>Replace the default App.js with this generated code</li>
1203
+ <li>Make sure your Flask backend is running before testing the frontend</li>
1204
+ <li>Add proper CORS headers to your Flask backend to allow frontend connections</li>
1205
+ </ul>
1206
+ </div>
1207
+ </div>
1208
+ </div>
1209
+ )}
1210
+ </main>
1211
+
1212
+ {/* Preview Modal */}
1213
+ {showPreview && (
1214
+ <div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
1215
+ <div className="bg-white rounded-lg shadow-xl p-6 w-full max-w-4xl max-h-[80vh] overflow-auto">
1216
+ <div className="flex justify-between items-center mb-4">
1217
+ <h2 className="text-xl font-bold">Mapping Preview</h2>
1218
+ <button
1219
+ onClick={() => setShowPreview(false)}
1220
+ className="text-gray-500 hover:text-gray-700"
1221
+ >
1222
+ <i className="fas fa-times"></i>
1223
+ </button>
1224
+ </div>
1225
+ <div className="grid grid-cols-2 gap-4">
1226
+ <div>
1227
+ <h3 className="font-semibold mb-2">Source Data</h3>
1228
+ <pre className="bg-gray-100 p-2 rounded overflow-auto">
1229
+ {JSON.stringify(parsedSourceData, null, 2)}
1230
+ </pre>
1231
+ </div>
1232
+ <div>
1233
+ <h3 className="font-semibold mb-2">Transformed Preview</h3>
1234
+ {previewData === null ? (
1235
+ <div className="flex items-center justify-center h-full">
1236
+ <div className="text-center">
1237
+ <div className="inline-block animate-spin rounded-full h-8 w-8 border-t-2 border-b-2 border-blue-500 mb-2"></div>
1238
+ <p>Generating preview...</p>
1239
+ </div>
1240
+ </div>
1241
+ ) : previewData.error ? (
1242
+ <div className="bg-red-50 p-4 rounded text-red-700">
1243
+ <i className="fas fa-exclamation-circle mr-2"></i>
1244
+ {previewData.error}
1245
+ </div>
1246
+ ) : (
1247
+ <pre className="bg-gray-100 p-2 rounded overflow-auto">
1248
+ {JSON.stringify(previewData, null, 2)}
1249
+ </pre>
1250
+ )}
1251
+ </div>
1252
+ </div>
1253
+ </div>
1254
+ </div>
1255
+ )}
1256
+
1257
+ {/* Footer */}
1258
+ <footer className="bg-gray-100 p-4 border-t">
1259
+ <div className="container mx-auto text-center text-sm text-gray-600">
1260
+ <p>Data Mapper Pro - Generate complete data transformation solutions</p>
1261
+ <p className="mt-1">Backend: Python Flask with PostgreSQL | Frontend: ReactJS</p>
1262
+ </div>
1263
+ </footer>
1264
+ </div>
1265
+ );
1266
+ };
1267
+
1268
+ const root = ReactDOM.createRoot(document.getElementById('root'));
1269
+ root.render(<DataMapperApp />);
1270
+ </script>
1271
+ <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=mehrdadmoslemi/mm-datamapper" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
1272
+ </html>