converter v1
This commit is contained in:
parent
c8e921d6a0
commit
a0550df293
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,3 +1,4 @@
|
||||
node_modules/
|
||||
*/node_modules/
|
||||
*/build/
|
||||
/.env
|
||||
|
||||
177
api.php
Normal file
177
api.php
Normal file
@ -0,0 +1,177 @@
|
||||
<?php
|
||||
// api.php
|
||||
|
||||
header('Content-Type: application/json');
|
||||
|
||||
function load_env($path) {
|
||||
if (!file_exists($path)) {
|
||||
error_log(".env file not found at " . $path);
|
||||
return;
|
||||
}
|
||||
$lines = file($path, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
|
||||
foreach ($lines as $line) {
|
||||
if (strpos(trim($line), '#') === 0) {
|
||||
continue;
|
||||
}
|
||||
list($name, $value) = explode('=', $line, 2);
|
||||
$name = trim($name);
|
||||
$value = trim($value);
|
||||
if (!array_key_exists($name, $_SERVER) && !array_key_exists($name, $_ENV)) {
|
||||
putenv(sprintf('%s=%s', $name, $value));
|
||||
$_ENV[$name] = $value;
|
||||
$_SERVER[$name] = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function get_csv_sample($filePath) {
|
||||
$sample = [];
|
||||
$handle = fopen($filePath, 'r');
|
||||
if ($handle === false) {
|
||||
return null;
|
||||
}
|
||||
$headers = fgetcsv($handle);
|
||||
$sample[] = implode(',', $headers);
|
||||
|
||||
$i = 0;
|
||||
while (($row = fgetcsv($handle)) !== false && $i < 5) {
|
||||
$sample[] = implode(',', $row);
|
||||
$i++;
|
||||
}
|
||||
fclose($handle);
|
||||
return implode('\n', $sample);
|
||||
}
|
||||
|
||||
function call_openai_api($inputSample, $outputSample) {
|
||||
$apiKey = getenv('OPENAI_API_KEY');
|
||||
if (!$apiKey) {
|
||||
return ['error' => 'OpenAI API key not configured.'];
|
||||
}
|
||||
|
||||
$prompt = "I have two CSV files. I want to convert the input file to the output file format.\n\n";
|
||||
$prompt .= "Here is a sample of the input CSV file:\n---\n" . $inputSample . "\n---\n\n";
|
||||
$prompt .= "Here is a sample of the output CSV file:\n---\n" . $outputSample . "\n---\n\n";
|
||||
$prompt .= "Please generate a PHP script that contains a single function `convert_csv($input_file_path, $output_file_path)`.\n";
|
||||
$prompt .= "This function should read the data from the `$input_file_path`, transform it into the format of the output sample, and write the result to `$output_file_path`.\n";
|
||||
$prompt .= "The script should handle CSV headers correctly. Do not include any explanations, just the raw PHP code starting with `<?php`.";
|
||||
|
||||
$data = [
|
||||
'model' => 'gpt-5',
|
||||
'messages' => [
|
||||
[
|
||||
'role' => 'user',
|
||||
'content' => $prompt
|
||||
]
|
||||
]
|
||||
];
|
||||
|
||||
$ch = curl_init('https://api.openai.com/v1/chat/completions');
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_POST, true);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
||||
'Content-Type: application/json',
|
||||
'Authorization: Bearer ' . $apiKey
|
||||
]);
|
||||
|
||||
$response = curl_exec($ch);
|
||||
$httpcode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
|
||||
if ($httpcode != 200) {
|
||||
return ['error' => 'Failed to connect to OpenAI API. Response: ' . $response];
|
||||
}
|
||||
|
||||
$result = json_decode($response, true);
|
||||
|
||||
if (isset($result['choices'][0]['message']['content'])) {
|
||||
$php_code = $result['choices'][0]['message']['content'];
|
||||
// Clean up the response to get only the code
|
||||
if (strpos($php_code, '```php') !== false) {
|
||||
$php_code = substr($php_code, strpos($php_code, '```php') + 5);
|
||||
$php_code = substr($php_code, 0, strrpos($php_code, '```'));
|
||||
}
|
||||
if (strpos($php_code, '<?php') === false) {
|
||||
$php_code = "<?php\n" . $php_code;
|
||||
}
|
||||
return ['success' => true, 'code' => $php_code];
|
||||
}
|
||||
|
||||
return ['error' => 'Could not extract PHP code from OpenAI response.', 'details' => $result];
|
||||
}
|
||||
|
||||
load_env(__DIR__ . '/.env');
|
||||
$action = $_POST['action'] ?? $_GET['action'] ?? '';
|
||||
|
||||
if ($action === 'build_mapping') {
|
||||
if (!isset($_FILES['inputSample']) || !isset($_FILES['outputSample'])) {
|
||||
echo json_encode(['success' => false, 'error' => 'Input and output sample files are required.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$inputTmpPath = $_FILES['inputSample']['tmp_name'];
|
||||
$outputTmpPath = $_FILES['outputSample']['tmp_name'];
|
||||
|
||||
$inputSampleContent = get_csv_sample($inputTmpPath);
|
||||
$outputSampleContent = get_csv_sample($outputTmpPath);
|
||||
|
||||
if ($inputSampleContent === null || $outputSampleContent === null) {
|
||||
echo json_encode(['success' => false, 'error' => 'Failed to read sample files.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$result = call_openai_api($inputSampleContent, $outputSampleContent);
|
||||
|
||||
if (isset($result['error'])) {
|
||||
echo json_encode(['success' => false, 'error' => $result['error'], 'details' => $result['details'] ?? null]);
|
||||
exit;
|
||||
}
|
||||
|
||||
$mappingDir = __DIR__ . '/mappings';
|
||||
if (!is_dir($mappingDir)) {
|
||||
mkdir($mappingDir, 0775, true);
|
||||
}
|
||||
$mappingFile = 'mappings/mapping_' . time() . '.php';
|
||||
file_put_contents($mappingFile, $result['code']);
|
||||
|
||||
echo json_encode(['success' => true, 'mapping_file' => $mappingFile]);
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($action === 'convert') {
|
||||
if (!isset($_FILES['fullInputFile']) || !isset($_POST['mappingFile'])) {
|
||||
echo json_encode(['success' => false, 'error' => 'Input file and mapping file are required.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$mappingFile = $_POST['mappingFile'];
|
||||
if (!file_exists($mappingFile)) {
|
||||
echo json_encode(['success' => false, 'error' => 'Mapping file not found.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
require_once $mappingFile;
|
||||
|
||||
if (!function_exists('convert_csv')) {
|
||||
echo json_encode(['success' => false, 'error' => 'Invalid mapping file: convert_csv function not found.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$inputTmpPath = $_FILES['fullInputFile']['tmp_name'];
|
||||
$outputDir = __DIR__ . '/outputs';
|
||||
if (!is_dir($outputDir)) {
|
||||
mkdir($outputDir, 0775, true);
|
||||
}
|
||||
$outputFile = $outputDir . '/converted_' . time() . '.csv';
|
||||
|
||||
try {
|
||||
convert_csv($inputTmpPath, $outputFile);
|
||||
echo json_encode(['success' => true, 'download_url' => basename($outputFile)]);
|
||||
} catch (Exception $e) {
|
||||
echo json_encode(['success' => false, 'error' => 'Error during conversion: ' . $e->getMessage()]);
|
||||
}
|
||||
exit;
|
||||
}
|
||||
|
||||
|
||||
echo json_encode(['success' => false, 'error' => 'No action specified.']);
|
||||
53
assets/css/custom.css
Normal file
53
assets/css/custom.css
Normal file
@ -0,0 +1,53 @@
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
||||
background-color: #f8f9fa;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 960px;
|
||||
}
|
||||
|
||||
.card {
|
||||
border: none;
|
||||
border-radius: 0.75rem;
|
||||
box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.1);
|
||||
transition: transform 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
.card:hover {
|
||||
transform: translateY(-5px);
|
||||
}
|
||||
|
||||
.card-title {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.btn {
|
||||
border-radius: 0.5rem;
|
||||
padding: 0.75rem 1.25rem;
|
||||
font-weight: 600;
|
||||
transition: all 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background-color: #0d6efd;
|
||||
border-color: #0d6efd;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background-color: #0b5ed7;
|
||||
border-color: #0a58ca;
|
||||
}
|
||||
|
||||
.form-control {
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
|
||||
header {
|
||||
border-bottom: 1px solid #e9ecef;
|
||||
}
|
||||
|
||||
footer {
|
||||
border-top: 1px solid #e9ecef;
|
||||
}
|
||||
94
assets/js/main.js
Normal file
94
assets/js/main.js
Normal file
@ -0,0 +1,94 @@
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
const mappingForm = document.getElementById('mappingForm');
|
||||
const convertForm = document.getElementById('convertForm');
|
||||
|
||||
const inputSample = document.getElementById('inputSample');
|
||||
const outputSample = document.getElementById('outputSample');
|
||||
const buildMappingBtn = document.getElementById('buildMappingBtn');
|
||||
|
||||
const fullInputFile = document.getElementById('fullInputFile');
|
||||
const convertBtn = document.getElementById('convertBtn');
|
||||
|
||||
const convertSection = document.getElementById('convertSection');
|
||||
const convertResult = document.getElementById('convertResult');
|
||||
const mappingFileInput = document.getElementById('mappingFile');
|
||||
|
||||
function checkMappingFiles() {
|
||||
buildMappingBtn.disabled = !(inputSample.files.length > 0 && outputSample.files.length > 0);
|
||||
}
|
||||
|
||||
function checkConvertFile() {
|
||||
convertBtn.disabled = !(fullInputFile.files.length > 0);
|
||||
}
|
||||
|
||||
inputSample.addEventListener('change', checkMappingFiles);
|
||||
outputSample.addEventListener('change', checkMappingFiles);
|
||||
fullInputFile.addEventListener('change', checkConvertFile);
|
||||
|
||||
mappingForm.addEventListener('submit', function (e) {
|
||||
e.preventDefault();
|
||||
buildMappingBtn.disabled = true;
|
||||
buildMappingBtn.innerHTML = '<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span> Building...';
|
||||
|
||||
const formData = new FormData(this);
|
||||
formData.append('action', 'build_mapping');
|
||||
|
||||
fetch('api.php', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
alert('Mapping built successfully!');
|
||||
mappingFileInput.value = data.mapping_file;
|
||||
convertSection.classList.remove('d-none');
|
||||
} else {
|
||||
let errorMsg = 'Error building mapping: ' + data.error;
|
||||
if(data.details) {
|
||||
errorMsg += '\nDetails: ' + JSON.stringify(data.details);
|
||||
}
|
||||
alert(errorMsg);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error:', error);
|
||||
alert('An error occurred while building the mapping.');
|
||||
})
|
||||
.finally(() => {
|
||||
buildMappingBtn.disabled = false;
|
||||
buildMappingBtn.innerHTML = '<i class="fas fa-cogs me-2"></i>Build Mapping';
|
||||
});
|
||||
});
|
||||
|
||||
convertForm.addEventListener('submit', function (e) {
|
||||
e.preventDefault();
|
||||
convertBtn.disabled = true;
|
||||
convertBtn.innerHTML = '<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span> Converting...';
|
||||
convertResult.innerHTML = '';
|
||||
|
||||
const formData = new FormData(this);
|
||||
formData.append('action', 'convert');
|
||||
|
||||
fetch('api.php', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
convertResult.innerHTML = `<div class="alert alert-success">Conversion successful! <a href="outputs/${data.download_url}" download>Download Converted File</a></div>`;
|
||||
} else {
|
||||
convertResult.innerHTML = `<div class="alert alert-danger">Error: ${data.error}</div>`;
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error:', error);
|
||||
convertResult.innerHTML = `<div class="alert alert-danger">An unexpected error occurred.</div>`;
|
||||
})
|
||||
.finally(() => {
|
||||
convertBtn.disabled = false;
|
||||
convertBtn.innerHTML = '<i class="fas fa-sync-alt me-2"></i>Convert';
|
||||
});
|
||||
});
|
||||
});
|
||||
1
converter.py
Normal file
1
converter.py
Normal file
@ -0,0 +1 @@
|
||||
# This is a placeholder for the Python converter script.
|
||||
233
index.php
233
index.php
@ -1,150 +1,95 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
@ini_set('display_errors', '1');
|
||||
@error_reporting(E_ALL);
|
||||
@date_default_timezone_set('UTC');
|
||||
|
||||
$phpVersion = PHP_VERSION;
|
||||
$now = date('Y-m-d H:i:s');
|
||||
?>
|
||||
<!doctype html>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>New Style</title>
|
||||
<?php
|
||||
// Read project preview data from environment
|
||||
$projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? '';
|
||||
$projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
|
||||
?>
|
||||
<?php if ($projectDescription): ?>
|
||||
<!-- Meta description -->
|
||||
<meta name="description" content='<?= htmlspecialchars($projectDescription) ?>' />
|
||||
<!-- Open Graph meta tags -->
|
||||
<meta property="og:description" content="<?= htmlspecialchars($projectDescription) ?>" />
|
||||
<!-- Twitter meta tags -->
|
||||
<meta property="twitter:description" content="<?= htmlspecialchars($projectDescription) ?>" />
|
||||
<?php endif; ?>
|
||||
<?php if ($projectImageUrl): ?>
|
||||
<!-- Open Graph image -->
|
||||
<meta property="og:image" content="<?= htmlspecialchars($projectImageUrl) ?>" />
|
||||
<!-- Twitter image -->
|
||||
<meta property="twitter:image" content="<?= htmlspecialchars($projectImageUrl) ?>" />
|
||||
<?php endif; ?>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
:root {
|
||||
--bg-color-start: #6a11cb;
|
||||
--bg-color-end: #2575fc;
|
||||
--text-color: #ffffff;
|
||||
--card-bg-color: rgba(255, 255, 255, 0.01);
|
||||
--card-border-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: 'Inter', sans-serif;
|
||||
background: linear-gradient(45deg, var(--bg-color-start), var(--bg-color-end));
|
||||
color: var(--text-color);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 100vh;
|
||||
text-align: center;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
body::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100"><path d="M-10 10L110 10M10 -10L10 110" stroke-width="1" stroke="rgba(255,255,255,0.05)"/></svg>');
|
||||
animation: bg-pan 20s linear infinite;
|
||||
z-index: -1;
|
||||
}
|
||||
@keyframes bg-pan {
|
||||
0% { background-position: 0% 0%; }
|
||||
100% { background-position: 100% 100%; }
|
||||
}
|
||||
main {
|
||||
padding: 2rem;
|
||||
}
|
||||
.card {
|
||||
background: var(--card-bg-color);
|
||||
border: 1px solid var(--card-border-color);
|
||||
border-radius: 16px;
|
||||
padding: 2rem;
|
||||
backdrop-filter: blur(20px);
|
||||
-webkit-backdrop-filter: blur(20px);
|
||||
box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
.loader {
|
||||
margin: 1.25rem auto 1.25rem;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border: 3px solid rgba(255, 255, 255, 0.25);
|
||||
border-top-color: #fff;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
@keyframes spin {
|
||||
from { transform: rotate(0deg); }
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
.hint {
|
||||
opacity: 0.9;
|
||||
}
|
||||
.sr-only {
|
||||
position: absolute;
|
||||
width: 1px; height: 1px;
|
||||
padding: 0; margin: -1px;
|
||||
overflow: hidden;
|
||||
clip: rect(0, 0, 0, 0);
|
||||
white-space: nowrap; border: 0;
|
||||
}
|
||||
h1 {
|
||||
font-size: 3rem;
|
||||
font-weight: 700;
|
||||
margin: 0 0 1rem;
|
||||
letter-spacing: -1px;
|
||||
}
|
||||
p {
|
||||
margin: 0.5rem 0;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
code {
|
||||
background: rgba(0,0,0,0.2);
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
||||
}
|
||||
footer {
|
||||
position: absolute;
|
||||
bottom: 1rem;
|
||||
font-size: 0.8rem;
|
||||
opacity: 0.7;
|
||||
}
|
||||
</style>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Data Mapping Tool</title>
|
||||
<meta name="description" content="Built with Flatlogic Generator">
|
||||
<meta name="keywords" content="csv, xlsx, data mapping, data conversion, php, ai, automatic mapping, data transformation, Built with Flatlogic Generator">
|
||||
<meta property="og:title" content="datamapping">
|
||||
<meta property="og:description" content="Built with Flatlogic Generator">
|
||||
<meta property="og:image" content="">
|
||||
<meta name="twitter:card" content="summary_large_image">
|
||||
<meta name="twitter:image" content="">
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||||
<link rel="stylesheet" href="assets/css/custom.css?v=<?php echo time(); ?>">
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
<div class="card">
|
||||
<h1>Analyzing your requirements and generating your website…</h1>
|
||||
<div class="loader" role="status" aria-live="polite" aria-label="Applying initial changes">
|
||||
<span class="sr-only">Loading…</span>
|
||||
</div>
|
||||
<p class="hint"><?= ($_SERVER['HTTP_HOST'] ?? '') === 'appwizzy.com' ? 'AppWizzy' : 'Flatlogic' ?> AI is collecting your requirements and applying the first changes.</p>
|
||||
<p class="hint">This page will update automatically as the plan is implemented.</p>
|
||||
<p>Runtime: PHP <code><?= htmlspecialchars($phpVersion) ?></code> — UTC <code><?= htmlspecialchars($now) ?></code></p>
|
||||
</div>
|
||||
</main>
|
||||
<footer>
|
||||
Page updated: <?= htmlspecialchars($now) ?> (UTC)
|
||||
</footer>
|
||||
<header class="bg-light shadow-sm">
|
||||
<div class="container d-flex justify-content-between align-items-center py-3">
|
||||
<a href="/" class="text-decoration-none text-dark">
|
||||
<h1 class="h4 mb-0 fw-bold">Data Mapper</h1>
|
||||
</a>
|
||||
<nav>
|
||||
<a href="/healthz" class="text-muted">Health Check</a>
|
||||
</nav>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main class="container my-5">
|
||||
<div class="row g-5">
|
||||
<div class="col-lg-6">
|
||||
<div class="card h-100">
|
||||
<div class="card-body d-flex flex-column">
|
||||
<h2 class="card-title h4 mb-4"><span class="badge bg-primary me-2">1</span>Build Mapping</h2>
|
||||
<p class="text-muted">Provide input and output sample files (CSV or XLSX) to generate a mapping configuration using AI.</p>
|
||||
<form id="mappingForm" class="mt-auto">
|
||||
<div class="mb-3">
|
||||
<label for="inputSample" class="form-label">Input Sample</label>
|
||||
<input class="form-control" type="file" id="inputSample" name="inputSample" accept=".csv, .xlsx" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="outputSample" class="form-label">Output Sample</label>
|
||||
<input class="form-control" type="file" id="outputSample" name="outputSample" accept=".csv, .xlsx" required>
|
||||
</div>
|
||||
<button type="submit" id="buildMappingBtn" class="btn btn-primary w-100" disabled>
|
||||
<i class="fas fa-cogs me-2"></i>Build Mapping
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="convertSection" class="col-lg-6 d-none">
|
||||
<div class="card h-100">
|
||||
<div class="card-body d-flex flex-column">
|
||||
<h2 class="card-title h4 mb-4"><span class="badge bg-secondary me-2">2</span>Convert Data</h2>
|
||||
<p class="text-muted">Use the generated mapping to convert your full input file.</p>
|
||||
<form id="convertForm" class="mt-auto">
|
||||
<input type="hidden" id="mappingFile" name="mappingFile">
|
||||
<div class="mb-3">
|
||||
<label for="fullInputFile" class="form-label">Full Input File</label>
|
||||
<input class="form-control" type="file" id="fullInputFile" name="fullInputFile" accept=".csv, .xlsx" required>
|
||||
</div>
|
||||
<button type="submit" id="convertBtn" class="btn btn-secondary w-100" disabled>
|
||||
<i class="fas fa-sync-alt me-2"></i>Convert
|
||||
</button>
|
||||
</form>
|
||||
<div id="convertResult" class="mt-4"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<footer class="bg-light pt-5 pb-4 text-center text-muted">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-lg-4 mx-auto">
|
||||
<h5 class="text-uppercase fw-bold mb-4">Environment</h5>
|
||||
<ul class="list-unstyled">
|
||||
<li><i class="fas fa-server me-2"></i>PHP Version: <?php echo phpversion(); ?></li>
|
||||
<li><i class="far fa-clock me-2"></i>Server Time: <?php echo date('Y-m-d H:i:s'); ?></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<hr class="my-4">
|
||||
<p class="mb-0">© <?php echo date('Y'); ?> Data Mapper. All rights reserved.</p>
|
||||
<p>Built with <a href="https://flatlogic.com" target="_blank" class="text-muted">Flatlogic</a></p>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<script src="assets/js/main.js?v=<?php echo time(); ?>"></script>
|
||||
</body>
|
||||
</html>
|
||||
156
mappings/mapping_1760650510.php
Normal file
156
mappings/mapping_1760650510.php
Normal file
@ -0,0 +1,156 @@
|
||||
<?php
|
||||
function convert_csv($inputPath, $outputPath) {
|
||||
if (!is_readable($inputPath)) {
|
||||
throw new RuntimeException("Input file not readable: $inputPath");
|
||||
}
|
||||
|
||||
$in = fopen($inputPath, 'rb');
|
||||
if ($in === false) {
|
||||
throw new RuntimeException("Failed to open input file: $inputPath");
|
||||
}
|
||||
|
||||
$out = fopen($outputPath, 'wb');
|
||||
if ($out === false) {
|
||||
fclose($in);
|
||||
throw new RuntimeException("Failed to open output file: $outputPath");
|
||||
}
|
||||
|
||||
// Read header and build index map
|
||||
$headers = fgetcsv($in);
|
||||
if ($headers === false) {
|
||||
fclose($in);
|
||||
fclose($out);
|
||||
throw new RuntimeException("Input CSV appears to be empty.");
|
||||
}
|
||||
|
||||
// Handle potential BOM
|
||||
if (isset($headers[0])) {
|
||||
$headers[0] = preg_replace('/^\xEF\xBB\xBF/', '', $headers[0]);
|
||||
}
|
||||
|
||||
// Build a map of header name (trimmed) to index
|
||||
$idx = [];
|
||||
foreach ($headers as $i => $name) {
|
||||
$norm = trim($name);
|
||||
$idx[$norm] = $i;
|
||||
}
|
||||
|
||||
// Helper to fetch a cell by header name, with fallbacks
|
||||
$get = function(array $row, array $candidates) use ($idx) {
|
||||
foreach ($candidates as $cand) {
|
||||
if (isset($idx[$cand])) {
|
||||
$val = isset($row[$idx[$cand]]) ? $row[$idx[$cand]] : '';
|
||||
$val = is_string($val) ? trim($val) : $val;
|
||||
if ($val !== '' && $val !== null) {
|
||||
return $val;
|
||||
}
|
||||
}
|
||||
}
|
||||
return '';
|
||||
};
|
||||
|
||||
// Helper to normalize date to YYYY-MM-DD
|
||||
$normDate = function($s) {
|
||||
$s = trim($s);
|
||||
if ($s === '') {
|
||||
return '';
|
||||
}
|
||||
// Try to extract date part
|
||||
// Accept formats: YYYY-MM-DD, YYYY-MM-DDTHH:MM:SSZ, etc.
|
||||
if (preg_match('/^\d{4}-\d{2}-\d{2}/', $s, $m)) {
|
||||
return $m[0];
|
||||
}
|
||||
// Try strtotime
|
||||
$ts = strtotime($s);
|
||||
if ($ts !== false) {
|
||||
return date('Y-m-d', $ts);
|
||||
}
|
||||
return $s;
|
||||
};
|
||||
|
||||
// Helper to normalize amount (use dot as decimal separator, keep sign)
|
||||
$normAmount = function($s) {
|
||||
$s = trim((string)$s);
|
||||
if ($s === '') return '0';
|
||||
// Replace comma decimal to dot, remove spaces
|
||||
$s = str_replace([' ', ','], ['', '.'], $s);
|
||||
// If it's a plain numeric string, keep as-is
|
||||
if (is_numeric($s)) {
|
||||
// Keep as minimally formatted string
|
||||
// Avoid scientific notation
|
||||
$f = (float)$s;
|
||||
// Preserve sign and decimals without trailing zeros if possible
|
||||
// Format with high precision then trim
|
||||
$str = rtrim(rtrim(number_format($f, 8, '.', ''), '0'), '.');
|
||||
return $str === '' ? '0' : $str;
|
||||
}
|
||||
return $s;
|
||||
};
|
||||
|
||||
// Write output header (semicolon-separated)
|
||||
$outputHeader = [
|
||||
'Data księgowania',
|
||||
'Nadawca / Odbiorca',
|
||||
'Adres nadawcy / odbiorcy',
|
||||
'Rachunek źródłowy',
|
||||
'Rachunek docelowy',
|
||||
'Tytuł',
|
||||
'Kwota',
|
||||
];
|
||||
// Use fputcsv with semicolon delimiter
|
||||
fputcsv($out, $outputHeader, ';');
|
||||
|
||||
while (($row = fgetcsv($in)) !== false) {
|
||||
// Skip entirely empty rows
|
||||
if (count($row) === 1 && trim((string)$row[0]) === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Extract fields using common header names
|
||||
$date = $get($row, ['Date completed (UTC)', 'Date started (UTC)', 'Date', 'Completed Date']);
|
||||
$date = $normDate($date);
|
||||
|
||||
$description = $get($row, ['Description', 'Narrative', 'Details']);
|
||||
$payer = $get($row, ['Payer', 'Counterparty', 'Merchant']);
|
||||
$account = $get($row, ['Account', 'From', 'Source account']);
|
||||
|
||||
$benefIban = $get($row, ['Beneficiary IBAN', 'Beneficiary account IBAN', 'IBAN']);
|
||||
$benefAccNo = $get($row, ['Beneficiary account number', 'Beneficiary account', 'Account number']);
|
||||
$benefRouting = $get($row, ['Beneficiary sort code or routing number', 'Routing number', 'Sort code']);
|
||||
|
||||
$amount = $get($row, ['Amount', 'Total amount', 'Payment amount', 'Paid amount', 'Value']);
|
||||
if ($amount === '') {
|
||||
// Fallback to original amount if needed
|
||||
$amount = $get($row, ['Orig amount', 'Original amount']);
|
||||
}
|
||||
$amount = $normAmount($amount);
|
||||
|
||||
$counterparty = $description !== '' ? $description : ($payer !== '' ? $payer : '');
|
||||
$title = $description !== '' ? $description : ($counterparty !== '' ? $counterparty : '');
|
||||
|
||||
$destAccount = '';
|
||||
if ($benefIban !== '') {
|
||||
$destAccount = $benefIban;
|
||||
} elseif ($benefAccNo !== '') {
|
||||
$destAccount = $benefAccNo;
|
||||
} elseif ($benefRouting !== '') {
|
||||
$destAccount = $benefRouting;
|
||||
}
|
||||
|
||||
$outRow = [
|
||||
$date,
|
||||
$counterparty,
|
||||
'', // Adres nadawcy / odbiorcy (not provided in input)
|
||||
$account,
|
||||
$destAccount,
|
||||
$title,
|
||||
$amount,
|
||||
];
|
||||
|
||||
fputcsv($out, $outRow, ';');
|
||||
}
|
||||
|
||||
fclose($in);
|
||||
fclose($out);
|
||||
}
|
||||
?>
|
||||
47
outputs/converted_1760650527.csv
Normal file
47
outputs/converted_1760650527.csv
Normal file
@ -0,0 +1,47 @@
|
||||
"Data księgowania";"Nadawca / Odbiorca";"Adres nadawcy / odbiorcy";"Rachunek źródłowy";"Rachunek docelowy";Tytuł;Kwota
|
||||
2025-08-23;"Web Innovative Softwar";;"EUR Card EUR";;"Web Innovative Softwar";-18.48
|
||||
2025-08-23;"Web Innovative Softwar";;"EUR Card EUR";;"Web Innovative Softwar";-0.24
|
||||
2025-08-23;"Web Innovative Softwar";;"EUR Card EUR";;"Web Innovative Softwar";-34.35
|
||||
2025-08-23;"Web Innovative Softwar";;"EUR Card EUR";;"Web Innovative Softwar";-0.24
|
||||
2025-08-23;"Discord* 12xserverboos";;"EUR Card EUR";;"Discord* 12xserverboos";-44.54
|
||||
2025-08-22;"To Flatlogic Poland";;"EUR Main";PL51109025900000000161061759;"To Flatlogic Poland";-50
|
||||
2025-08-22;Cloudflare;;"EUR Card EUR";;Cloudflare;-8.03
|
||||
2025-08-20;Cloudflare;;"EUR Card EUR";;Cloudflare;-25.71
|
||||
2025-08-20;"Openai *chatgpt Subscr";;"EUR Card EUR";;"Openai *chatgpt Subscr";-186.18
|
||||
2025-08-20;Calendly;;"EUR Card EUR";;Calendly;-42.24
|
||||
2025-08-20;"Facebk *whhzuwgcy2";;"EUR Card EUR";;"Facebk *whhzuwgcy2";-25.74
|
||||
2025-08-19;"Main · EUR → Main · USD";;"EUR Main";;"Main · EUR → Main · USD";-4115.97
|
||||
2025-08-19;"Card EUR · EUR → Card USD · USD";;"EUR Card EUR";;"Card EUR · EUR → Card USD · USD";-250.15
|
||||
2025-08-19;"To Card EUR";;"EUR Main";;"To Card EUR";-1300
|
||||
2025-08-19;"From Main";;"EUR Card EUR";;"From Main";1300
|
||||
2025-08-14;"Main · EUR → Main · PLN";;"EUR Main";;"Main · EUR → Main · PLN";-41.07
|
||||
2025-08-14;"Money added from MOGANADEN MOORGHEN";;"EUR Main";;"Money added from MOGANADEN MOORGHEN";6468.75
|
||||
2025-08-12;Ing*meeting15.com;;"EUR Card EUR";;Ing*meeting15.com;-59.48
|
||||
2025-08-07;"Google*cloud Qzxlb5";;"EUR Card EUR";;"Google*cloud Qzxlb5";-417.23
|
||||
2025-08-07;"Google Cloud Sv93dn";;"EUR Card EUR";;"Google Cloud Sv93dn";-500
|
||||
2025-08-06;"Card USD · USD → Card EUR · EUR";;"EUR Card EUR";;"Card USD · USD → Card EUR · EUR";1000
|
||||
2025-08-04;"Main · EUR → Card USD · USD";;"EUR Main";;"Main · EUR → Card USD · USD";-6007.69
|
||||
2025-08-04;"Main · EUR → Card USD · USD";;"EUR Main";;"Main · EUR → Card USD · USD";-3460.84
|
||||
2025-08-04;"Money added from FLATLOGIC POLAND SPOLKA Z OGRANICZONA ODPOWIEDZIALNOSCIA";;"EUR Main";;"Money added from FLATLOGIC POLAND SPOLKA Z OGRANICZONA ODPOWIEDZIALNOSCIA";8800
|
||||
2025-08-04;"Openai *chatgpt Subscr";;"EUR Card EUR";;"Openai *chatgpt Subscr";-17.41
|
||||
2025-07-29;"Github, Inc.";;"EUR Card EUR";;"Github, Inc.";-81.96
|
||||
2025-07-29;Dropbox*sgvfd1d5tz31;;"EUR Card EUR";;Dropbox*sgvfd1d5tz31;-11.99
|
||||
2025-07-28;"To Flatlogic EU";;"EUR Main";;"To Flatlogic EU";-400
|
||||
2025-07-28;Figma;;"EUR Card EUR";;Figma;-102.39
|
||||
2025-07-27;"Trello.com* Atlassian";;"EUR Card EUR";;"Trello.com* Atlassian";-5.12
|
||||
2025-07-26;Paypro-charge.com;;"EUR Card EUR";;Paypro-charge.com;-11.62
|
||||
2025-07-23;"Discord* 12xserverboos";;"EUR Card EUR";;"Discord* 12xserverboos";-44.27
|
||||
2025-07-21;"To Flatlogic Poland";;"EUR Main";PL51109025900000000161061759;"To Flatlogic Poland";-450
|
||||
2025-07-20;Cloudflare;;"EUR Card EUR";;Cloudflare;-25.83
|
||||
2025-07-20;Calendly;;"EUR Card EUR";;Calendly;-42.36
|
||||
2025-07-18;"Main · EUR → Main · USD";;"EUR Main";;"Main · EUR → Main · USD";-972.24
|
||||
2025-07-18;"To Flatlogic Poland";;"EUR Main";PL51109025900000000161061759;"To Flatlogic Poland";-250
|
||||
2025-07-17;"Openai *chatgpt Subscr";;"EUR Card EUR";;"Openai *chatgpt Subscr";-186.18
|
||||
2025-07-16;"To Card EUR";;"EUR Main";;"To Card EUR";-500
|
||||
2025-07-16;"From Main";;"EUR Card EUR";;"From Main";500
|
||||
2025-07-16;"Veed.io Basic";;"EUR Card EUR";;"Veed.io Basic";-44
|
||||
2025-07-14;"Main · EUR → Main · PLN";;"EUR Main";;"Main · EUR → Main · PLN";-8.49
|
||||
2025-07-14;"Main · EUR → Main · USD";;"EUR Main";;"Main · EUR → Main · USD";-17.12
|
||||
2025-07-14;"Main · EUR → Main · USD";;"EUR Main";;"Main · EUR → Main · USD";-2121
|
||||
2025-07-14;"Main · EUR → Main · USD";;"EUR Main";;"Main · EUR → Main · USD";-1711.65
|
||||
2025-07-14;"Money added from MOGANADEN MOORGHEN";;"EUR Main";;"Money added from MOGANADEN MOORGHEN";1631.25
|
||||
|
Loading…
x
Reference in New Issue
Block a user