fix dashboard generation
This commit is contained in:
parent
ee17043571
commit
0c9c7ad511
@ -12,28 +12,72 @@ if (!$profile_id) {
|
|||||||
|
|
||||||
// Basic AI/Rule-based KPI generation
|
// Basic AI/Rule-based KPI generation
|
||||||
function generate_kpi_data($profile) {
|
function generate_kpi_data($profile) {
|
||||||
// In a real scenario, you'd have a more complex logic or an API call to an AI model
|
// Default values
|
||||||
|
$base_mrr = 5000;
|
||||||
|
$base_cac = 400;
|
||||||
|
$base_nrr = 95;
|
||||||
|
|
||||||
|
// Adjust KPIs based on profile data
|
||||||
|
if (isset($profile['organization_size'])) {
|
||||||
|
switch ($profile['organization_size']) {
|
||||||
|
case '1-10':
|
||||||
|
$base_mrr = 5000;
|
||||||
|
break;
|
||||||
|
case '11-50':
|
||||||
|
$base_mrr = 25000;
|
||||||
|
break;
|
||||||
|
case '51-200':
|
||||||
|
$base_mrr = 75000;
|
||||||
|
break;
|
||||||
|
case '201+':
|
||||||
|
$base_mrr = 200000;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($profile['market_approach'])) {
|
||||||
|
if ($profile['market_approach'] === 'Product-led') {
|
||||||
|
$base_nrr = 110;
|
||||||
|
$base_cac = 200;
|
||||||
|
} elseif ($profile['market_approach'] === 'Sales-led') {
|
||||||
|
$base_nrr = 98;
|
||||||
|
$base_cac = 800;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$current_mrr = $base_mrr * (rand(80, 120) / 100);
|
||||||
|
$target_mrr = $base_mrr * 2;
|
||||||
|
|
||||||
|
$current_cac = $base_cac * (rand(90, 130) / 100);
|
||||||
|
$target_cac = $base_cac * 0.8;
|
||||||
|
|
||||||
|
$current_nrr = $base_nrr * (rand(95, 105) / 100);
|
||||||
|
$target_nrr = $base_nrr * 1.1;
|
||||||
|
|
||||||
$kpis = [
|
$kpis = [
|
||||||
['name' => 'Monthly Recurring Revenue (MRR)', 'current' => '$' . number_format(rand(5000, 15000)), 'target' => '$25,000'],
|
['name' => 'Monthly Recurring Revenue (MRR)', 'current' => number_format($current_mrr), 'target' => number_format($target_mrr)],
|
||||||
['name' => 'Customer Acquisition Cost (CAC)', 'current' => '$' . number_format(rand(300, 800)), 'target' => '$400'],
|
['name' => 'Customer Acquisition Cost (CAC)', 'current' => number_format($current_cac), 'target' => number_format($target_cac)],
|
||||||
['name' => 'Net Revenue Retention (NRR)', 'current' => rand(95, 110) . '%', 'target' => '115%']
|
['name' => 'Net Revenue Retention (NRR)', 'current' => round($current_nrr) . '%', 'target' => round($target_nrr) . '%']
|
||||||
];
|
];
|
||||||
|
|
||||||
// Generate sample data for the chart
|
// Generate sample data for the chart
|
||||||
$labels = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun'];
|
$labels = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun'];
|
||||||
|
|
||||||
|
$mrr_growth_rate = ($target_mrr - $current_mrr) / 5;
|
||||||
|
|
||||||
$datasets = [
|
$datasets = [
|
||||||
[
|
[
|
||||||
'label' => 'MRR (Actual)',
|
'label' => 'MRR (Actual)',
|
||||||
'data' => array_map(fn() => rand(5000, 18000), range(1, 6)),
|
'data' => array_map(fn($i) => $current_mrr + ($mrr_growth_rate * $i * (rand(80,120)/100)), range(0, 5)),
|
||||||
'borderColor' => '#0d6efd',
|
'borderColor' => '#0d6efd',
|
||||||
'tension' => 0.1
|
'tension' => 0.2
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'label' => 'MRR (Target)',
|
'label' => 'MRR (Target)',
|
||||||
'data' => array_map(fn($i) => 15000 + ($i * 1500), range(1, 6)),
|
'data' => array_map(fn($i) => $current_mrr + ($mrr_growth_rate * $i), range(0, 5)),
|
||||||
'borderColor' => '#dc3545',
|
'borderColor' => '#dc3545',
|
||||||
'borderDash' => [5, 5],
|
'borderDash' => [5, 5],
|
||||||
'tension' => 0.1
|
'tension' => 0.2
|
||||||
]
|
]
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
93
profile.php
93
profile.php
@ -93,13 +93,15 @@ if ($profile_id) {
|
|||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<h5 class="card-title">AI-Generated KPI Dashboard</h5>
|
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||||
<p class="card-text">Set and refine KPIs, and track them with an auto-generated dashboard.</p>
|
<h5 class="card-title mb-0">AI-Generated KPI Dashboard</h5>
|
||||||
<div id="kpi-container" class="mt-3">
|
<div>
|
||||||
<?php if (empty($profile['kpi_data'])): ?>
|
<button id="edit-kpi-btn" class="btn btn-secondary btn-sm" style="display: none;">Edit KPIs</button>
|
||||||
<button id="generate-kpi-btn" class="btn btn-primary" data-profile-id="<?php echo htmlspecialchars($profile_id); ?>">Generate KPI Dashboard</button>
|
<button id="generate-kpi-btn" class="btn btn-primary <?php if (!empty($profile['kpi_data'])) echo 'd-none'; ?>" data-profile-id="<?php echo htmlspecialchars($profile_id); ?>">Generate KPI Dashboard</button>
|
||||||
<?php endif; ?>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<p class="card-text">Set and refine KPIs, and track them with an auto-generated dashboard.</p>
|
||||||
|
<div id="kpi-container" class="mt-3"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -248,26 +250,36 @@ if ($profile_id) {
|
|||||||
const renderKpiDashboard = (kpiData) => {
|
const renderKpiDashboard = (kpiData) => {
|
||||||
if (!kpiData) return;
|
if (!kpiData) return;
|
||||||
|
|
||||||
let kpiHtml = '<div class="row">';
|
let kpiHtml = '<form id="kpi-form"><div class="row">';
|
||||||
kpiData.kpis.forEach(kpi => {
|
kpiData.kpis.forEach((kpi, index) => {
|
||||||
kpiHtml += `
|
kpiHtml += `
|
||||||
<div class="col-md-4">
|
<div class="col-md-4">
|
||||||
<div class="card mb-3">
|
<div class="card mb-3">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<h6 class="card-title">${kpi.name}</h6>
|
<h6 class="card-title">${kpi.name}</h6>
|
||||||
<p class="card-text">Current: ${kpi.current} | Target: ${kpi.target}</p>
|
<div class="mb-2">
|
||||||
|
<label class="form-label">Current</label>
|
||||||
|
<input type="text" class="form-control" name="kpi_${index}_current" value="${kpi.current}" readonly>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="form-label">Target</label>
|
||||||
|
<input type="text" class="form-control" name="kpi_${index}_target" value="${kpi.target}" readonly>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
});
|
});
|
||||||
kpiHtml += '</div>';
|
kpiHtml += '</div></form>';
|
||||||
|
|
||||||
kpiHtml += '<div class="row"><div class="col-12"><canvas id="kpi-chart"></canvas></div></div>';
|
kpiHtml += '<div class="row"><div class="col-12"><canvas id="kpi-chart"></canvas></div></div>';
|
||||||
kpiContainer.innerHTML = kpiHtml;
|
kpiContainer.innerHTML = kpiHtml;
|
||||||
|
|
||||||
const ctx = document.getElementById('kpi-chart').getContext('2d');
|
const ctx = document.getElementById('kpi-chart').getContext('2d');
|
||||||
new Chart(ctx, {
|
if(window.kpiChart instanceof Chart) {
|
||||||
|
window.kpiChart.destroy();
|
||||||
|
}
|
||||||
|
window.kpiChart = new Chart(ctx, {
|
||||||
type: 'line',
|
type: 'line',
|
||||||
data: kpiData.chartData,
|
data: kpiData.chartData,
|
||||||
options: {
|
options: {
|
||||||
@ -279,6 +291,8 @@ if ($profile_id) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
document.getElementById('edit-kpi-btn').style.display = 'inline-block';
|
||||||
};
|
};
|
||||||
|
|
||||||
const existingKpiData = <?php echo $profile['kpi_data'] ?? 'null'; ?>;
|
const existingKpiData = <?php echo $profile['kpi_data'] ?? 'null'; ?>;
|
||||||
@ -311,6 +325,63 @@ if ($profile_id) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const editKpiBtn = document.getElementById('edit-kpi-btn');
|
||||||
|
if (editKpiBtn) {
|
||||||
|
editKpiBtn.addEventListener('click', function() {
|
||||||
|
const kpiForm = document.getElementById('kpi-form');
|
||||||
|
const inputs = kpiForm.getElementsByTagName('input');
|
||||||
|
const isEditing = this.textContent === 'Save KPIs';
|
||||||
|
|
||||||
|
if (isEditing) {
|
||||||
|
// Save logic
|
||||||
|
const formData = new FormData(kpiForm);
|
||||||
|
const kpis = [];
|
||||||
|
const kpiData = <?php echo $profile['kpi_data'] ?? 'null'; ?>;
|
||||||
|
|
||||||
|
kpiData.kpis.forEach((kpi, index) => {
|
||||||
|
kpis.push({
|
||||||
|
name: kpi.name,
|
||||||
|
current: formData.get(`kpi_${index}_current`),
|
||||||
|
target: formData.get(`kpi_${index}_target`)
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const updatedKpiData = { kpis: kpis };
|
||||||
|
|
||||||
|
fetch('save_kpis.php', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||||
|
body: `id=${profileId}&kpi_data=${JSON.stringify(updatedKpiData)}`
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data.success) {
|
||||||
|
// Re-render with new data
|
||||||
|
renderKpiDashboard(data.updated_kpi_data);
|
||||||
|
// Toggle back to edit mode
|
||||||
|
this.textContent = 'Edit KPIs';
|
||||||
|
for (let input of inputs) {
|
||||||
|
input.setAttribute('readonly', true);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
alert('Error saving KPIs: ' + data.error);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error saving KPIs:', error);
|
||||||
|
alert('An unexpected error occurred while saving KPIs.');
|
||||||
|
});
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// Edit logic
|
||||||
|
this.textContent = 'Save KPIs';
|
||||||
|
for (let input of inputs) {
|
||||||
|
input.removeAttribute('readonly');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Load existing diagram on page load
|
// Load existing diagram on page load
|
||||||
const existingDiagram = <?php echo json_encode($profile['diagram_mermaid_text'] ?? null); ?>;
|
const existingDiagram = <?php echo json_encode($profile['diagram_mermaid_text'] ?? null); ?>;
|
||||||
|
|||||||
42
save_kpis.php
Normal file
42
save_kpis.php
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
<?php
|
||||||
|
require_once 'db/config.php';
|
||||||
|
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
|
$profile_id = $_POST['id'] ?? null;
|
||||||
|
$kpi_data_json = $_POST['kpi_data'] ?? null;
|
||||||
|
|
||||||
|
if (!$profile_id || !$kpi_data_json) {
|
||||||
|
echo json_encode(['error' => 'Profile ID or KPI data is missing.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$kpi_data = json_decode($kpi_data_json, true);
|
||||||
|
|
||||||
|
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||||
|
throw new Exception('Invalid JSON format for KPI data.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$pdo = db();
|
||||||
|
|
||||||
|
// Fetch existing data to merge, preserving the chart data
|
||||||
|
$stmt = $pdo->prepare("SELECT kpi_data FROM gtm_profiles WHERE id = ?");
|
||||||
|
$stmt->execute([$profile_id]);
|
||||||
|
$existing_data_row = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
$existing_data = $existing_data_row ? json_decode($existing_data_row['kpi_data'], true) : [];
|
||||||
|
|
||||||
|
// We only want to update the 'kpis' part, not the chart data
|
||||||
|
$existing_data['kpis'] = $kpi_data['kpis'];
|
||||||
|
|
||||||
|
$updated_kpi_data_json = json_encode($existing_data);
|
||||||
|
|
||||||
|
$update_stmt = $pdo->prepare("UPDATE gtm_profiles SET kpi_data = ? WHERE id = ?");
|
||||||
|
$update_stmt->execute([$updated_kpi_data_json, $profile_id]);
|
||||||
|
|
||||||
|
echo json_encode(['success' => true, 'message' => 'KPIs updated successfully.', 'updated_kpi_data' => $existing_data]);
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
error_log('KPI Save Error: ' . $e->getMessage());
|
||||||
|
echo json_encode(['error' => 'Error saving KPIs: ' . $e->getMessage()]);
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user