Visual design specifications for the KPI Matrix module — employee cards, manager dashboards, HR overview, review forms, salary auto-trigger settings, and reporting views.
Status: Implemented — controller
modules/KpiMatrix/controllers/Kpi_matrix.php, views undermodules/KpiMatrix/views/kpi_matrix/. June 2026 reconciliation. KPI Matrix is now a standalone top-level module; the routes are registered at top-levelkpi_matrix(see corrected block below) and it appears as its own standalone top-level left-menu item (no longer an HR/STAFFING submenu). Treat the visual layouts here as the design intent; verify exact tab/column wording against the shipped views.
1. Page Architecture Overview
The KPI Matrix module uses shared views across 3 access contexts and 2 integration points:
┌─────────────────────────────────────────────────────────┐
│ KPI Matrix Module UI │
├─────────────┬───────────────┬───────────────────────────┤
│ Standalone │ Profile Tab │ Settings Integration │
│ /kpi_matrix │ Team_members │ Settings > Component │
├─────────────┴───────────────┴───────────────────────────┤
│ │
│ Standalone Page (6 Tabs): │
│ ┌──────────┬──────────┬─────────┬──────────┬────────┐ │
│ │ Timeline │ My KPIs │ Team │ Reviews │Overview│ │
│ │ │ │ Matrix │ │ │ │
│ └──────────┴──────────┴─────────┴──────────┴────────┘ │
│ ┌──────────┐ │
│ │ Settings │ (Admin/HR only) │
│ └──────────┘ │
│ │
│ Profile Tab: │
│ ┌──────────────────────────────────────────────┐ │
│ │ KPI Score Card + History + Current Metrics │ │
│ └──────────────────────────────────────────────┘ │
│ │
│ Settings > Component > Salary Options: │
│ ┌──────────────────────────────────────────────┐ │
│ │ Auto-Trigger Salary Upgrade Toggle + Config │ │
│ └──────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────┘
Controller: modules/KpiMatrix/controllers/Kpi_matrix.php
Views Directory: modules/KpiMatrix/views/kpi_matrix/
Routes (as registered in modules/KpiMatrix/config/routes.php):
$route['kpi_matrix'] = 'Kpi_matrix/index';
$route['kpi_matrix/(:any)'] = 'Kpi_matrix/$1';
$route['kpi_matrix/(:any)/(:any)'] = 'Kpi_matrix/$1/$2';
2. Standalone Page — Tab Structure
2.1 Tab Layout (index.php)
<ul id="kpi-tabs" data-toggle="ajax-tab" class="nav nav-tabs bg-white title" role="tablist">
<li class="title-tab"><h4 class="pl15 pt10 pr15"><?php echo lang("kpi_matrix"); ?></h4></li>
<!-- Tab 1: Timeline (inline, active by default) -->
<li><a role="presentation" class="active" href="#"
data-target="#kpi-timeline-tab"><?php echo lang("timeline"); ?></a></li>
<!-- Tab 2: My KPIs (self-view, always visible) -->
<li><a role="presentation" href="#"
data-target="#kpi-my-tab"><?php echo lang("my_kpis"); ?></a></li>
<!-- Tab 3: Team Matrix (managers/HR) -->
<?php if ($has_kpi_manage): ?>
<li><a role="presentation" href="#"
data-target="#kpi-team-tab"><?php echo lang("team_matrix"); ?></a></li>
<?php endif; ?>
<!-- Tab 4: Review Cycles -->
<?php if ($has_kpi_manage): ?>
<li><a role="presentation" href="#"
data-target="#kpi-reviews-tab"><?php echo lang("review_cycles"); ?></a></li>
<?php endif; ?>
<!-- Tab 5: Overview / Analytics -->
<?php if ($has_kpi_manage): ?>
<li><a role="presentation" href="#"
data-target="#kpi-overview-tab"><?php echo lang("overview"); ?></a></li>
<?php endif; ?>
<!-- Tab 6: KPI Settings (admin only) -->
<?php if ($is_admin): ?>
<li><a role="presentation" href="#"
data-target="#kpi-settings-tab"><?php echo lang("settings"); ?></a></li>
<?php endif; ?>
<!-- Tab-sensitive buttons -->
<div class="tab-title clearfix no-border">
<div class="title-button-group">
<?php if ($has_kpi_manage): ?>
<?php echo modal_anchor(get_uri("kpi_matrix/cycle_modal_form"),
"<i class='fa fa-plus-circle'></i> " . lang('new_review_cycle'),
array("class" => "btn btn-default", "title" => lang('new_review_cycle'),
"id" => "btn_new_cycle")); ?>
<?php endif; ?>
<?php if ($has_kpi_manage): ?>
<?php echo modal_anchor(get_uri("kpi_matrix/template_modal_form"),
"<i class='fa fa-plus-circle'></i> " . lang('new_template'),
array("class" => "btn btn-default", "title" => lang('new_template'),
"id" => "btn_new_template")); ?>
<?php endif; ?>
</div>
</div>
</ul>
2.2 Tab-Specific Button Visibility
setInterval(function(){
var activeTab = $("#kpi-tabs").find("li.active").text().trim();
$('#btn_new_cycle').toggle(activeTab == "<?php echo lang('review_cycles'); ?>");
$('#btn_new_template').toggle(activeTab == "<?php echo lang('settings'); ?>");
}, 200);
3. Tab 1 — Timeline (Activity Feed)
View: modules/KpiMatrix/views/kpi_matrix/timeline.php
Pattern: Follows ERPat Timeline Activity Feed Pattern (see Loans/E&D timeline implementations)
3.1 Filter Bar
<div class="filter-section">
<!-- Employee filter (multi-select, managers see their team) -->
<select id="kpi_employee_filter" class="select2 multiple" multiple>
<?php foreach ($team_members_dropdown as $id => $name): ?>
<option value="<?php echo $id; ?>"><?php echo $name; ?></option>
<?php endforeach; ?>
</select>
<!-- Department filter -->
<select id="kpi_department_filter" class="select2">
<option value=""><?php echo lang("all_departments"); ?></option>
<?php /* department options */ ?>
</select>
<!-- Module filter (which KPI category activities) -->
<select id="kpi_module_filter" class="form-control">
<option value=""><?php echo lang("all"); ?></option>
<option value="kpi_score"><?php echo lang("score_changes"); ?></option>
<option value="kpi_review"><?php echo lang("review_cycles"); ?></option>
<option value="kpi_salary"><?php echo lang("salary_recommendations"); ?></option>
</select>
<!-- Date range -->
<input type="text" id="kpi_date_from" class="form-control datepicker" placeholder="<?php echo lang('from'); ?>" />
<input type="text" id="kpi_date_to" class="form-control datepicker" placeholder="<?php echo lang('to'); ?>" />
</div>
3.2 Stats Cards
| Card | Value | Source |
|---|---|---|
| Active Cycles | Count of open review cycles | kpi_review_cycles WHERE status='active' |
| Employees Rated | Distinct users with composite scores | kpi_composite_results |
| Avg Composite Score | Average across all employees | AVG(composite_score) |
| Salary Recommendations | Pending recommendations | kpi_salary_recommendations WHERE status='pending' |
3.3 Timeline Card Structure
Same pattern as Loans timeline: date-grouped cards with action badges, module tags, detail chips. System logs integration with kpi_score, kpi_review, kpi_salary module names.
4. Tab 2 — My KPIs (Employee Self-View)
View: modules/KpiMatrix/views/kpi_matrix/my_kpis.php
This tab is always visible to any staff member — it shows their own KPI data.
4.1 Score Summary Card (Gradient Banner)
<div class="kpi-summary-banner" style="background: linear-gradient(135deg, #1d97a6 0%, #10b1af 100%);
color: white; border-radius: 8px; padding: 30px; margin-bottom: 25px;">
<div class="row">
<!-- Left: Composite Score Gauge -->
<div class="col-md-3 text-center">
<div class="score-gauge">
<!-- Circular gauge rendered via CSS/SVG -->
<div class="gauge-value"><?php echo number_format($composite_score, 2); ?></div>
<div class="gauge-label"><?php echo lang('composite_score'); ?></div>
<div class="gauge-rating">
<span class="badge" style="background-color: <?php echo $rating_color; ?>;">
<?php echo $rating_label; ?>
</span>
</div>
</div>
</div>
<!-- Center: Category Scores -->
<div class="col-md-6">
<h4 style="color: white; margin-bottom: 15px;"><?php echo lang('category_breakdown'); ?></h4>
<div class="row">
<?php foreach ($category_scores as $cat): ?>
<div class="col-sm-6" style="margin-bottom: 10px;">
<div style="display: flex; justify-content: space-between; align-items: center;">
<span><?php echo $cat['category_name']; ?></span>
<span class="badge" style="background-color: <?php echo $cat['color']; ?>;">
<?php echo number_format($cat['weighted_score'], 2); ?>
</span>
</div>
<div class="progress" style="height: 6px; margin-top: 4px; background: rgba(255,255,255,0.3);">
<div class="progress-bar" style="width: <?php echo ($cat['weighted_score'] / 5) * 100; ?>%;
background-color: <?php echo $cat['color']; ?>;"></div>
</div>
</div>
<?php endforeach; ?>
</div>
</div>
<!-- Right: Key Stats -->
<div class="col-md-3">
<div class="kpi-stat-item">
<div class="stat-value"><?php echo $review_cycle_name; ?></div>
<div class="stat-label"><?php echo lang('current_cycle'); ?></div>
</div>
<div class="kpi-stat-item">
<div class="stat-value"><?php echo $salary_grade_display; ?></div>
<div class="stat-label"><?php echo lang('salary_grade'); ?></div>
</div>
<div class="kpi-stat-item">
<div class="stat-value"><?php echo format_to_date($last_evaluated); ?></div>
<div class="stat-label"><?php echo lang('last_evaluated'); ?></div>
</div>
</div>
</div>
</div>
4.2 Radar Chart (Category Visualization)
<div class="panel panel-default">
<div class="panel-heading">
<h4><?php echo lang('performance_radar'); ?></h4>
</div>
<div class="panel-body">
<canvas id="kpi-radar-chart" width="400" height="400"></canvas>
</div>
</div>
<script>
// Chart.js radar chart
var ctx = document.getElementById('kpi-radar-chart').getContext('2d');
new Chart(ctx, {
type: 'radar',
data: {
labels: <?php echo json_encode($category_labels); ?>,
datasets: [{
label: '<?php echo lang("current_scores"); ?>',
data: <?php echo json_encode($current_scores); ?>,
backgroundColor: 'rgba(29, 151, 166, 0.2)',
borderColor: '#1d97a6',
pointBackgroundColor: '#1d97a6'
}, {
label: '<?php echo lang("previous_cycle"); ?>',
data: <?php echo json_encode($previous_scores); ?>,
backgroundColor: 'rgba(88, 88, 90, 0.1)',
borderColor: '#58585a',
borderDash: [5, 5],
pointBackgroundColor: '#58585a'
}]
},
options: {
scale: {
ticks: { min: 0, max: 5, stepSize: 1 },
pointLabels: { fontSize: 13 }
},
legend: { position: 'bottom' }
}
});
</script>
4.3 Individual Metric Scores Table
// DataTable for individual metric breakdown
$("#my-kpi-metrics-table").appTable({
source: '<?php echo_uri("kpi_matrix/my_metrics_list/" . $login_user_id); ?>',
columns: [
{title: '<?php echo lang("category"); ?>'},
{title: '<?php echo lang("metric"); ?>'},
{title: '<?php echo lang("raw_value"); ?>', "class": "text-right"},
{title: '<?php echo lang("unit"); ?>', "class": "text-center w80"},
{title: '<?php echo lang("score"); ?>', "class": "text-center w80"},
{title: '<?php echo lang("weight"); ?>', "class": "text-center w80"},
{title: '<?php echo lang("weighted_score"); ?>', "class": "text-right w100"},
{title: '<?php echo lang("source"); ?>', "class": "text-center w100"}
]
});
Controller _make_metric_row():
private function _make_metric_row($data) {
// Score badge with color
$score_badge = '<span class="label" style="background-color: ' . $this->_get_score_color($data->score) . ';">'
. number_format($data->score, 2) . '</span>';
// Source badge
$source_badge = ($data->source === 'auto')
? '<span class="label label-success"><i class="fa fa-bolt"></i> ' . lang('auto') . '</span>'
: '<span class="label label-warning"><i class="fa fa-pencil"></i> ' . lang('manual') . '</span>';
return array(
$data->category_name,
$data->metric_name,
number_format($data->raw_value, 2),
$data->unit,
$score_badge,
number_format($data->weight_percentage, 1) . '%',
number_format($data->weighted_score, 2),
$source_badge
);
}
4.4 Score Trend Line Chart
<div class="panel panel-default">
<div class="panel-heading">
<h4><?php echo lang('score_history'); ?></h4>
</div>
<div class="panel-body">
<canvas id="kpi-trend-chart" width="700" height="250"></canvas>
</div>
</div>
<script>
// Line chart showing composite score over time
new Chart(document.getElementById('kpi-trend-chart').getContext('2d'), {
type: 'line',
data: {
labels: <?php echo json_encode($history_labels); ?>, // ["Q1 2025", "Q2 2025", ...]
datasets: [{
label: '<?php echo lang("composite_score"); ?>',
data: <?php echo json_encode($history_scores); ?>,
borderColor: '#1d97a6',
backgroundColor: 'rgba(29, 151, 166, 0.1)',
fill: true,
tension: 0.3
}]
},
options: {
scales: {
y: { min: 1, max: 5, ticks: { stepSize: 0.5 } }
},
plugins: {
annotation: {
annotations: {
meetLine: {
type: 'line',
yMin: 2.5, yMax: 2.5,
borderColor: '#f39c12',
borderDash: [5, 5],
label: { content: 'Meets Expectations', enabled: true }
}
}
}
}
}
});
</script>
5. Tab 3 — Team Matrix (Manager/HR View)
View: modules/KpiMatrix/views/kpi_matrix/team_matrix.php
5.1 Matrix Heat Map Table
A pivot-style table where rows = employees and columns = KPI categories, with cells color-coded by score:
<div class="table-responsive">
<table class="table table-bordered table-condensed" id="kpi-matrix-table">
<thead>
<tr style="background-color: #1d97a6; color: white;">
<th><?php echo lang('employee'); ?></th>
<th><?php echo lang('department'); ?></th>
<?php foreach ($categories as $cat): ?>
<th class="text-center" style="min-width: 100px;">
<?php echo $cat['short_name']; ?>
</th>
<?php endforeach; ?>
<th class="text-center" style="min-width: 110px; background-color: #0d7a87;">
<?php echo lang('composite'); ?>
</th>
<th class="text-center"><?php echo lang('rating'); ?></th>
<th class="text-center"><?php echo lang('trend'); ?></th>
</tr>
</thead>
<tbody>
<?php foreach ($employees as $emp): ?>
<tr>
<td>
<div style="display: flex; align-items: center; gap: 8px;">
<img src="" class="avatar" style="width: 30px; height: 30px; border-radius: 50%;">
<div>
<strong><?php echo $emp->full_name; ?></strong><br>
<small class="text-muted"><?php echo $emp->job_title; ?></small>
</div>
</div>
</td>
<td><?php echo $emp->department; ?></td>
<?php foreach ($categories as $cat): ?>
<td class="text-center" style="background-color: <?php echo $this->_score_to_bg_color($emp->scores[$cat['code']]); ?>;">
<strong><?php echo number_format($emp->scores[$cat['code']], 2); ?></strong>
</td>
<?php endforeach; ?>
<td class="text-center" style="background-color: <?php echo $this->_score_to_bg_color($emp->composite); ?>; font-weight: bold; font-size: 16px;">
<?php echo number_format($emp->composite, 2); ?>
</td>
<td class="text-center">
<span class="label" style="background-color: <?php echo $this->_rating_color($emp->rating); ?>;">
<?php echo $emp->rating; ?>
</span>
</td>
<td class="text-center">
<?php echo $this->_trend_icon($emp->trend); ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
5.2 Score-to-Color Mapping
// Controller helper
private function _score_to_bg_color($score) {
if ($score === null) return '#f5f5f5'; // No data
if ($score >= 4.50) return '#d4edda'; // Outstanding (green)
if ($score >= 3.50) return '#cce5ff'; // Exceeds (blue)
if ($score >= 2.50) return '#fff3cd'; // Meets (yellow)
if ($score >= 2.00) return '#ffe0cc'; // Needs Improvement (orange)
return '#f8d7da'; // Unsatisfactory (red)
}
private function _rating_color($rating) {
$colors = array(
'Outstanding' => '#28a745',
'Exceeds Expectations' => '#17a2b8',
'Meets Expectations' => '#ffc107',
'Needs Improvement' => '#fd7e14',
'Unsatisfactory' => '#dc3545'
);
return get_array_value($colors, $rating, '#6c757d');
}
private function _trend_icon($trend) {
// trend is delta between current and previous composite
if ($trend === null) return '<i class="fa fa-minus text-muted"></i>';
if ($trend > 0.25) return '<i class="fa fa-arrow-up text-success"></i> +' . number_format($trend, 2);
if ($trend < -0.25) return '<i class="fa fa-arrow-down text-danger"></i> ' . number_format($trend, 2);
return '<i class="fa fa-arrows-h text-muted"></i> ' . number_format($trend, 2);
}
5.3 Matrix Filter Controls
<div class="panel-body" style="display: flex; gap: 15px; flex-wrap: wrap;">
<!-- Review Cycle Selector -->
<?php echo form_dropdown("cycle_id", $cycles_dropdown, $current_cycle_id,
"class='select2' id='matrix_cycle_filter' style='width: 250px;'"); ?>
<!-- Department Filter -->
<?php echo form_dropdown("department_id", $departments_dropdown, "",
"class='select2' id='matrix_dept_filter' style='width: 200px;'"); ?>
<!-- Establishment Filter -->
<?php echo form_dropdown("establishment_id", $establishments_dropdown, "",
"class='select2' id='matrix_est_filter' style='width: 200px;'"); ?>
<!-- Rating Filter -->
<?php echo form_dropdown("rating_filter", array(
"" => lang("all_ratings"),
"outstanding" => lang("outstanding"),
"exceeds" => lang("exceeds_expectations"),
"meets" => lang("meets_expectations"),
"needs_improvement" => lang("needs_improvement"),
"unsatisfactory" => lang("unsatisfactory")
), "", "class='form-control' id='matrix_rating_filter' style='width: 180px;'"); ?>
<!-- Export Button -->
<a href="#" class="btn btn-default">
<i class="fa fa-download"></i> <?php echo lang('export_excel'); ?>
</a>
</div>
6. Tab 4 — Review Cycles
View: modules/KpiMatrix/views/kpi_matrix/review_cycles_tab.php
6.1 Review Cycles DataTable
window.reviewCycleTable = $("#kpi-review-cycles-table").appTable({
source: '<?php echo_uri("kpi_matrix/review_cycles_list_data"); ?>',
columns: [
{title: '<?php echo lang("cycle_name"); ?>'},
{title: '<?php echo lang("template"); ?>'},
{title: '<?php echo lang("period"); ?>'},
{title: '<?php echo lang("assigned"); ?>', "class": "text-center w80"},
{title: '<?php echo lang("scored"); ?>', "class": "text-center w80"},
{title: '<?php echo lang("avg_score"); ?>', "class": "text-center w100"},
{title: '<?php echo lang("status"); ?>', "class": "text-center w100"},
{title: '<?php echo lang("last_updated"); ?>'},
{title: '<i class="fa fa-bars"></i>', "class": "text-center dropdown-option w100"}
],
checkBoxes: [
{text: '<?php echo lang("draft"); ?>', name: "status", value: "draft", isChecked: true},
{text: '<?php echo lang("active"); ?>', name: "status", value: "active", isChecked: true},
{text: '<?php echo lang("completed"); ?>', name: "status", value: "completed", isChecked: false},
{text: '<?php echo lang("cancelled"); ?>', name: "status", value: "cancelled", isChecked: false}
]
});
6.2 Cycle Modal Form
View: modules/KpiMatrix/views/kpi_matrix/cycle_modal_form.php
<div class="modal-body clearfix">
<form id="cycle-form" class="general-form form-horizontal" role="form"
action="<?php echo get_uri("kpi_matrix/save_cycle"); ?>"
data-callBack="onCycleSaved">
<input type="hidden" name="id" value="<?php echo $model_info->id; ?>" />
<!-- Cycle Name -->
<div class="form-group">
<label class="col-md-3"><?php echo lang("cycle_name"); ?></label>
<div class="col-md-9">
<?php echo form_input(array(
"id" => "cycle_name",
"name" => "cycle_name",
"value" => $model_info->cycle_name,
"class" => "form-control",
"placeholder" => "e.g., Q1 2026 Performance Review",
"data-rule-required" => true,
"data-msg-required" => lang("field_required")
)); ?>
</div>
</div>
<!-- Template -->
<div class="form-group">
<label class="col-md-3"><?php echo lang("template"); ?></label>
<div class="col-md-9">
<?php echo form_dropdown("template_id", $templates_dropdown, $model_info->template_id,
"class='select2 validate-hidden' id='cycle_template_id'"); ?>
</div>
</div>
<!-- Review Period -->
<div class="form-group">
<label class="col-md-3"><?php echo lang("review_period"); ?></label>
<div class="col-md-4">
<?php echo form_input(array(
"id" => "period_start",
"name" => "period_start",
"value" => $model_info->period_start,
"class" => "form-control",
"placeholder" => lang("start_date"),
"autocomplete" => "off",
"data-rule-required" => true
)); ?>
</div>
<div class="col-md-1 text-center" style="padding-top: 7px;">
<i class="fa fa-arrow-right"></i>
</div>
<div class="col-md-4">
<?php echo form_input(array(
"id" => "period_end",
"name" => "period_end",
"value" => $model_info->period_end,
"class" => "form-control",
"placeholder" => lang("end_date"),
"autocomplete" => "off",
"data-rule-required" => true
)); ?>
</div>
</div>
<!-- Description -->
<div class="form-group">
<label class="col-md-3"><?php echo lang("description"); ?></label>
<div class="col-md-9">
<?php echo form_textarea(array(
"id" => "cycle_description",
"name" => "description",
"value" => $model_info->description,
"class" => "form-control",
"rows" => 3
)); ?>
</div>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-primary">
<span class="fa fa-check-circle"></span> <?php echo lang('save'); ?>
</button>
</div>
</form>
</div>
<script>
$(document).ready(function() {
$("#cycle_template_id").select2();
setDatePicker("#period_start, #period_end");
$("#cycle-form").appForm({
onSuccess: function(result) {
if (typeof window.reviewCycleTable !== 'undefined') {
window.reviewCycleTable.appTable({reload: true});
}
$("#kpi-review-cycles-table").appTable({reload: true});
}
});
});
</script>
6.3 Cycle Action Dropdown
private function _make_cycle_row($data) {
$actions = "";
// Edit (draft only)
if ($data->status === 'draft' && $this->with_permission("kpi_matrix_update")) {
$actions .= '<li role="presentation">' . modal_anchor(
get_uri("kpi_matrix/cycle_modal_form"),
"<i class='fa fa-pencil'></i> " . lang('edit'),
array("class" => "edit", "title" => lang('edit'), "data-post-id" => $data->id)
) . "</li>";
}
// Activate (draft → active)
if ($data->status === 'draft' && $this->with_permission("kpi_matrix_update")) {
$actions .= '<li role="presentation">' . js_anchor(
"<i class='fa fa-play'></i> " . lang('activate'),
array(
"data-action-url" => get_uri("kpi_matrix/update_cycle_status/active/" . $data->id),
"data-action" => "update",
"data-post-id" => $data->id
)
) . "</li>";
}
// Run Calculation (active cycles)
if ($data->status === 'active' && $this->with_permission("kpi_matrix_update")) {
$actions .= '<li role="presentation">' . modal_anchor(
get_uri("kpi_matrix/run_calculation_modal/" . $data->id),
"<i class='fa fa-calculator'></i> " . lang('run_calculation'),
array("title" => lang('run_calculation'), "data-modal-lg" => "1")
) . "</li>";
}
// Complete (active → completed)
if ($data->status === 'active' && $this->with_permission("kpi_matrix_update")) {
$actions .= '<li role="presentation">' . js_anchor(
"<i class='fa fa-check'></i> " . lang('complete'),
array(
"data-action-url" => get_uri("kpi_matrix/update_cycle_status/completed/" . $data->id),
"data-action" => "update",
"data-post-id" => $data->id
)
) . "</li>";
}
// Delete (draft only)
if ($data->status === 'draft' && $this->with_permission("kpi_matrix_delete")) {
$actions .= '<li role="presentation">' . js_anchor(
"<i class='fa fa-times fa-fw'></i> " . lang('delete'),
array(
"data-id" => $data->id,
"data-action-url" => get_uri("kpi_matrix/delete_cycle"),
"data-action" => "delete-confirmation"
)
) . "</li>";
}
// Status badge
$status_colors = array(
'draft' => '#6c757d', 'active' => '#28a745',
'completed' => '#17a2b8', 'cancelled' => '#dc3545'
);
$status_badge = '<span class="label" style="background-color: '
. get_array_value($status_colors, $data->status, '#6c757d') . ';">'
. ucfirst($data->status) . '</span>';
return array(
$data->cycle_name,
$data->template_name,
format_to_date($data->period_start) . ' — ' . format_to_date($data->period_end),
$data->assigned_count,
$data->scored_count,
($data->avg_score ? number_format($data->avg_score, 2) : '—'),
$status_badge,
format_to_relative_time($data->updated_at ?: $data->date_created),
get_dropdown_options($actions)
);
}
7. Tab 5 — Overview / Analytics Dashboard
View: modules/KpiMatrix/views/kpi_matrix/overview_tab.php
Pattern: Follows ERPat Cost Analytics Dashboard Pattern (see E&D summary_tab)
7.1 Banner KPIs
<div class="summary-banner" style="background: linear-gradient(135deg, #1d97a6 0%, #10b1af 100%);
color: white; border-radius: 8px; padding: 30px; margin-bottom: 25px;">
<div class="kpi-cards-row" style="display: grid; grid-template-columns: repeat(5, 1fr); gap: 20px;">
<div class="kpi-card" style="text-align: center;">
<div class="kpi-value" style="font-size: 32px; font-weight: bold;">
<?php echo $total_employees; ?>
</div>
<div class="kpi-label" style="font-size: 13px; opacity: 0.9;">
<?php echo lang('employees_evaluated'); ?>
</div>
</div>
<div class="kpi-card" style="text-align: center;">
<div class="kpi-value" style="font-size: 32px; font-weight: bold;">
<?php echo number_format($avg_composite, 2); ?>
</div>
<div class="kpi-label" style="font-size: 13px; opacity: 0.9;">
<?php echo lang('avg_composite_score'); ?>
</div>
</div>
<div class="kpi-card" style="text-align: center;">
<div class="kpi-value" style="font-size: 32px; font-weight: bold;">
<?php echo $outstanding_count; ?>
</div>
<div class="kpi-label" style="font-size: 13px; opacity: 0.9;">
<?php echo lang('outstanding_performers'); ?>
</div>
</div>
<div class="kpi-card" style="text-align: center;">
<div class="kpi-value" style="font-size: 32px; font-weight: bold;">
<?php echo $needs_improvement_count; ?>
</div>
<div class="kpi-label" style="font-size: 13px; opacity: 0.9;">
<?php echo lang('needs_improvement'); ?>
</div>
</div>
<div class="kpi-card" style="text-align: center;">
<div class="kpi-value" style="font-size: 32px; font-weight: bold;">
<?php echo $salary_recommendations_pending; ?>
</div>
<div class="kpi-label" style="font-size: 13px; opacity: 0.9;">
<?php echo lang('pending_salary_actions'); ?>
</div>
</div>
</div>
</div>
7.2 Rating Distribution Chart
<div class="col-md-6">
<div class="panel panel-default">
<div class="panel-heading"><h4><?php echo lang('rating_distribution'); ?></h4></div>
<div class="panel-body">
<!-- Horizontal bar chart -->
<?php foreach ($rating_distribution as $rating => $count): ?>
<div style="display: flex; align-items: center; margin-bottom: 12px;">
<span style="width: 180px; font-size: 13px;"><?php echo $rating; ?></span>
<div class="progress" style="flex: 1; margin-bottom: 0; height: 24px;">
<div class="progress-bar" style="width: <?php echo ($count / $total_employees) * 100; ?>%;
background-color: <?php echo $this->_rating_color($rating); ?>; line-height: 24px;">
<?php echo $count; ?> (<?php echo round(($count / $total_employees) * 100); ?>%)
</div>
</div>
</div>
<?php endforeach; ?>
</div>
</div>
</div>
7.3 Department Comparison Table
<div class="col-md-6">
<div class="panel panel-default">
<div class="panel-heading"><h4><?php echo lang('department_comparison'); ?></h4></div>
<div class="panel-body">
<table class="table table-bordered">
<thead>
<tr>
<th><?php echo lang('department'); ?></th>
<th class="text-center"><?php echo lang('employees'); ?></th>
<th class="text-center"><?php echo lang('avg_score'); ?></th>
<th class="text-center"><?php echo lang('top_performer'); ?></th>
</tr>
</thead>
<tbody>
<?php foreach ($department_stats as $dept): ?>
<tr>
<td><?php echo $dept['name']; ?></td>
<td class="text-center"><?php echo $dept['employee_count']; ?></td>
<td class="text-center">
<span class="label" style="background-color: <?php echo $this->_score_to_bg_color($dept['avg_score']); ?>; color: #333;">
<?php echo number_format($dept['avg_score'], 2); ?>
</span>
</td>
<td>
<small>
<?php echo $dept['top_performer_name']; ?>
(<?php echo number_format($dept['top_score'], 2); ?>)
</small>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
</div>
7.4 Salary Recommendation Summary
<div class="panel panel-default">
<div class="panel-heading">
<h4>
<?php echo lang('salary_recommendations'); ?>
<?php if ($salary_auto_trigger_enabled): ?>
<span class="badge" style="background-color: #28a745; margin-left: 10px;">
<i class="fa fa-bolt"></i> <?php echo lang('auto_trigger_enabled'); ?>
</span>
<?php endif; ?>
</h4>
</div>
<div class="panel-body">
<table class="table table-bordered">
<thead>
<tr>
<th><?php echo lang('rating'); ?></th>
<th class="text-center"><?php echo lang('employees'); ?></th>
<th class="text-center"><?php echo lang('recommended_action'); ?></th>
<th class="text-center"><?php echo lang('pending'); ?></th>
<th class="text-center"><?php echo lang('approved'); ?></th>
<th class="text-center"><?php echo lang('applied'); ?></th>
</tr>
</thead>
<tbody>
<?php foreach ($salary_summary_by_rating as $row): ?>
<tr>
<td>
<span class="label" style="background-color: <?php echo $row['color']; ?>;">
<?php echo $row['rating']; ?>
</span>
</td>
<td class="text-center"><?php echo $row['employee_count']; ?></td>
<td class="text-center"><?php echo $row['action_label']; ?></td>
<td class="text-center"><?php echo $row['pending_count']; ?></td>
<td class="text-center"><?php echo $row['approved_count']; ?></td>
<td class="text-center"><?php echo $row['applied_count']; ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
8. Tab 6 — KPI Settings (Admin Only)
View: modules/KpiMatrix/views/kpi_matrix/settings_tab.php
8.1 Template Management DataTable
window.kpiTemplateTable = $("#kpi-templates-table").appTable({
source: '<?php echo_uri("kpi_matrix/templates_list_data"); ?>',
columns: [
{title: '<?php echo lang("template_name"); ?>'},
{title: '<?php echo lang("description"); ?>'},
{title: '<?php echo lang("metrics_count"); ?>', "class": "text-center w100"},
{title: '<?php echo lang("cycles_using"); ?>', "class": "text-center w100"},
{title: '<?php echo lang("status"); ?>', "class": "text-center w80"},
{title: '<?php echo lang("last_updated"); ?>'},
{title: '<i class="fa fa-bars"></i>', "class": "text-center dropdown-option w100"}
]
});
8.2 Weight Distribution Presets
Settings tab also provides quick-load weight distribution presets:
<div class="panel panel-default">
<div class="panel-heading"><h4><?php echo lang('weight_presets'); ?></h4></div>
<div class="panel-body">
<div class="row">
<?php foreach ($weight_presets as $key => $preset): ?>
<div class="col-md-4 col-sm-6" style="margin-bottom: 15px;">
<button class="btn btn-block btn-default apply-preset-btn" data-preset="<?php echo $key; ?>"
style="padding: 15px; min-height: 100px; text-align: left;">
<strong><?php echo $preset['name']; ?></strong><br>
<small class="text-muted"><?php echo $preset['description']; ?></small><br>
<small>
<?php foreach ($preset['weights'] as $cat => $w): ?>
<span class="label label-default"><?php echo $cat; ?>: <?php echo $w; ?>%</span>
<?php endforeach; ?>
</small>
</button>
</div>
<?php endforeach; ?>
</div>
</div>
</div>
9. Profile Tab Integration
Controller Method: Team_members::kpi_info($user_id)
View: modules/KpiMatrix/views/team_members/kpi_tab.php
9.1 Controller Integration
// In Team_members controller
function kpi_info($user_id) {
$user_id = (int) $user_id;
$is_self = ($this->login_user->id == $user_id);
$has_permission = $this->with_permission("kpi_matrix");
if (!$is_self && !$has_permission) {
redirect('forbidden');
}
// If not self and has permission, check access type
if (!$is_self && $has_permission) {
$access_info = $this->get_access_info("kpi_matrix");
if ($access_info->access_type !== 'all' && !in_array($user_id, $access_info->allowed_members)) {
redirect('forbidden');
}
}
$view_data['user_id'] = $user_id;
$view_data['readonly'] = ($is_self && !$has_permission);
$this->load->view("team_members/kpi_tab", $view_data);
}
9.2 Profile Tab View
The profile tab reuses the My KPIs components in a compact layout:
<!-- Score summary card (same gradient banner as My KPIs but narrower) -->
<div class="kpi-profile-summary">
<!-- Compact version of Section 4.1 -->
</div>
<!-- Radar chart (smaller) -->
<div class="row">
<div class="col-md-5">
<canvas id="profile-kpi-radar" width="300" height="300"></canvas>
</div>
<div class="col-md-7">
<!-- Score trend mini-chart -->
<canvas id="profile-kpi-trend" width="400" height="200"></canvas>
</div>
</div>
<!-- Metric scores DataTable (readonly for self, actions for managers) -->
<table id="profile-kpi-metrics-table" class="display" cellspacing="0" width="100%"></table>
<script>
$(document).ready(function() {
$("#profile-kpi-metrics-table").appTable({
source: '<?php echo_uri("kpi_matrix/my_metrics_list/" . $user_id . ($readonly ? "?readonly=1" : "")); ?>',
columns: [
{title: '<?php echo lang("category"); ?>'},
{title: '<?php echo lang("metric"); ?>'},
{title: '<?php echo lang("raw_value"); ?>', "class": "text-right"},
{title: '<?php echo lang("score"); ?>', "class": "text-center w80"},
{title: '<?php echo lang("weight"); ?>', "class": "text-center w80"},
{title: '<?php echo lang("weighted_score"); ?>', "class": "text-right w100"}
<?php if (!$readonly): ?>
,{title: '<i class="fa fa-bars"></i>', "class": "text-center option w80"}
<?php endif; ?>
]
});
});
</script>
9.3 Tab Registration in view.php
// In application/views/team_members/view.php
<?php if ($show_kpi_matrix): ?>
<li><a href="#"
data-target="#tab-kpi-info">
<i class="fa fa-bar-chart"></i> <?php echo lang('kpi_matrix'); ?>
</a></li>
<?php endif; ?>
10. Settings > Component > Salary Options Integration
Location: application/views/settings/components/salary_options.php (existing or new section)
This is where the auto-trigger salary upgrade toggle lives — as a dedicated panel within the existing Salary Options settings page.
10.1 Settings Panel
<!-- KPI Auto-Trigger Salary Upgrade Section -->
<div class="panel panel-default">
<div class="panel-heading" style="background-color: #f8f9fa; border-bottom: 2px solid #1d97a6;">
<h4 style="margin: 0;">
<i class="fa fa-bolt" style="color: #1d97a6;"></i>
<?php echo lang('kpi_salary_auto_trigger'); ?>
</h4>
<small class="text-muted"><?php echo lang('kpi_salary_auto_trigger_description'); ?></small>
</div>
<div class="panel-body">
<!-- Master Toggle -->
<div class="form-group">
<label class="col-md-3 control-label"><?php echo lang('enable_auto_trigger'); ?></label>
<div class="col-md-9">
<?php echo form_dropdown("kpi_salary_auto_trigger", array(
"0" => lang("no"),
"1" => lang("yes")
), get_setting('kpi_salary_auto_trigger', '0'),
"class='select2' id='kpi_salary_auto_trigger'"); ?>
<span class="help-block"><?php echo lang('kpi_salary_auto_trigger_help'); ?></span>
</div>
</div>
<!-- Conditional fields (shown only when enabled) -->
<div id="kpi_salary_auto_trigger_options" style="<?php echo get_setting('kpi_salary_auto_trigger', '0') == '1' ? '' : 'display:none;'; ?>">
<!-- Require Approval -->
<div class="form-group">
<label class="col-md-3 control-label"><?php echo lang('require_approval'); ?></label>
<div class="col-md-9">
<?php echo form_dropdown("kpi_salary_require_approval", array(
"1" => lang("yes_require_approval"),
"0" => lang("no_auto_apply")
), get_setting('kpi_salary_require_approval', '1'),
"class='select2' id='kpi_salary_require_approval'"); ?>
<span class="help-block"><?php echo lang('kpi_salary_require_approval_help'); ?></span>
</div>
</div>
<!-- Minimum Review Cycles -->
<div class="form-group">
<label class="col-md-3 control-label"><?php echo lang('minimum_review_cycles'); ?></label>
<div class="col-md-9">
<?php echo form_input(array(
"id" => "kpi_salary_min_cycles",
"name" => "kpi_salary_min_cycles",
"type" => "number",
"value" => get_setting('kpi_salary_min_cycles', '2'),
"class" => "form-control",
"min" => "1",
"max" => "12"
)); ?>
<span class="help-block"><?php echo lang('kpi_salary_min_cycles_help'); ?></span>
</div>
</div>
<!-- Effective Date Mode -->
<div class="form-group">
<label class="col-md-3 control-label"><?php echo lang('effective_date_mode'); ?></label>
<div class="col-md-9">
<?php echo form_dropdown("kpi_salary_effective_date_mode", array(
"immediately" => lang("immediately"),
"next_period" => lang("next_pay_period"),
"next_month" => lang("first_of_next_month")
), get_setting('kpi_salary_effective_date_mode', 'next_month'),
"class='select2' id='kpi_salary_effective_date_mode'"); ?>
</div>
</div>
<!-- Max Step Increase -->
<div class="form-group">
<label class="col-md-3 control-label"><?php echo lang('max_step_increase'); ?></label>
<div class="col-md-9">
<?php echo form_input(array(
"id" => "kpi_salary_max_step_increase",
"name" => "kpi_salary_max_step_increase",
"type" => "number",
"value" => get_setting('kpi_salary_max_step_increase', '2'),
"class" => "form-control",
"min" => "1",
"max" => "5"
)); ?>
<span class="help-block"><?php echo lang('kpi_salary_max_step_increase_help'); ?></span>
</div>
</div>
<!-- Allow Downgrade -->
<div class="form-group">
<label class="col-md-3 control-label"><?php echo lang('allow_salary_downgrade'); ?></label>
<div class="col-md-9">
<?php echo form_dropdown("kpi_salary_downgrade_enabled", array(
"0" => lang("no"),
"1" => lang("yes")
), get_setting('kpi_salary_downgrade_enabled', '0'),
"class='select2' id='kpi_salary_downgrade_enabled'"); ?>
<span class="help-block text-danger">
<i class="fa fa-exclamation-triangle"></i>
<?php echo lang('kpi_salary_downgrade_warning'); ?>
</span>
</div>
</div>
<!-- Rating-to-Step Mapping (JSON Editor) -->
<div class="form-group">
<label class="col-md-3 control-label"><?php echo lang('rating_step_mapping'); ?></label>
<div class="col-md-9">
<table class="table table-bordered table-condensed" style="max-width: 500px;">
<thead>
<tr style="background-color: #f8f9fa;">
<th><?php echo lang('rating'); ?></th>
<th class="text-center" style="width: 120px;"><?php echo lang('step_change'); ?></th>
</tr>
</thead>
<tbody>
<?php
$mapping = json_decode(get_setting('kpi_salary_step_mapping', '{}'), true);
$defaults = array(
'outstanding' => '+2',
'exceeds' => '+1',
'meets' => '0',
'needs_improvement' => '0',
'unsatisfactory' => '-1'
);
foreach ($defaults as $rating => $default_step):
$current = get_array_value($mapping, $rating, $default_step);
?>
<tr>
<td>
<span class="label" style="background-color: <?php echo $this->_rating_color_by_key($rating); ?>;">
<?php echo lang($rating); ?>
</span>
</td>
<td>
<input type="text" name="kpi_step_mapping[<?php echo $rating; ?>]"
value="<?php echo $current; ?>"
class="form-control input-sm text-center"
style="width: 80px; margin: 0 auto;"
placeholder="e.g., +1">
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<span class="help-block"><?php echo lang('rating_step_mapping_help'); ?></span>
</div>
</div>
</div>
</div>
</div>
<script>
$(document).ready(function() {
// Select2 init for all dropdowns
$("#kpi_salary_auto_trigger, #kpi_salary_require_approval, #kpi_salary_effective_date_mode, #kpi_salary_downgrade_enabled").select2();
// Toggle conditional fields
$("#kpi_salary_auto_trigger").on("change", function() {
var enabled = $(this).val() === "1";
$("#kpi_salary_auto_trigger_options").toggle(enabled);
});
});
</script>
10.2 Settings Keys Summary
| Setting Key | Type | Default | Description |
|---|---|---|---|
kpi_salary_auto_trigger |
boolean(0/1) | 0 |
Master toggle for auto salary recommendations |
kpi_salary_require_approval |
boolean(0/1) | 1 |
Whether recs need manual approval |
kpi_salary_min_cycles |
int | 2 |
Min completed cycles before triggering |
kpi_salary_effective_date_mode |
string | next_month |
When salary change takes effect |
kpi_salary_max_step_increase |
int | 2 |
Max steps per cycle (prevents over-promotion) |
kpi_salary_downgrade_enabled |
boolean(0/1) | 0 |
Allow step decrease for poor performers |
kpi_salary_step_mapping |
JSON | {...} |
Rating-to-step-delta mapping |
11. Manual Entry Modal Form
View: modules/KpiMatrix/views/kpi_matrix/manual_entry_modal.php
For metrics that cannot be auto-computed (e.g., customer_satisfaction, innovation_index):
<div class="modal-body clearfix">
<form id="manual-entry-form" class="general-form form-horizontal" role="form"
action="<?php echo get_uri("kpi_matrix/save_manual_entry"); ?>"
data-callBack="onManualEntrySaved">
<input type="hidden" name="assignment_id" value="<?php echo $assignment_id; ?>" />
<input type="hidden" name="metric_code" value="<?php echo $metric_code; ?>" />
<!-- Employee (display only) -->
<div class="form-group">
<label class="col-md-3"><?php echo lang("employee"); ?></label>
<div class="col-md-9">
<p class="form-control-static"><?php echo $employee_name; ?></p>
</div>
</div>
<!-- Metric (display only) -->
<div class="form-group">
<label class="col-md-3"><?php echo lang("metric"); ?></label>
<div class="col-md-9">
<p class="form-control-static">
<?php echo $metric_name; ?>
<br><small class="text-muted"><?php echo $metric_description; ?></small>
</p>
</div>
</div>
<!-- Score Input -->
<div class="form-group">
<label class="col-md-3"><?php echo lang("score"); ?></label>
<div class="col-md-9">
<div class="input-group" style="max-width: 200px;">
<?php echo form_input(array(
"id" => "manual_score",
"name" => "score",
"type" => "number",
"value" => $model_info->score,
"class" => "form-control",
"min" => "1.00",
"max" => "5.00",
"step" => "0.01",
"data-rule-required" => true,
"data-rule-min" => 1,
"data-rule-max" => 5
)); ?>
<span class="input-group-addon">/ 5.00</span>
</div>
<!-- Score preview badge -->
<div id="score-preview" style="margin-top: 10px;">
<span class="label" id="score-badge" style="font-size: 14px;">—</span>
</div>
</div>
</div>
<!-- Evidence / Notes -->
<div class="form-group">
<label class="col-md-3"><?php echo lang("evidence_notes"); ?></label>
<div class="col-md-9">
<?php echo form_textarea(array(
"id" => "manual_notes",
"name" => "evaluator_notes",
"value" => $model_info->evaluator_notes,
"class" => "form-control",
"rows" => 4,
"placeholder" => lang("provide_evidence_for_score")
)); ?>
</div>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-primary">
<span class="fa fa-check-circle"></span> <?php echo lang('save'); ?>
</button>
</div>
</form>
</div>
<script>
$(document).ready(function() {
// Live score preview
$("#manual_score").on("input", function() {
var score = parseFloat($(this).val());
if (isNaN(score) || score < 1 || score > 5) {
$("#score-badge").text("—").css("background-color", "#6c757d");
return;
}
var rating, color;
if (score >= 4.50) { rating = "<?php echo lang('outstanding'); ?>"; color = "#28a745"; }
else if (score >= 3.50) { rating = "<?php echo lang('exceeds_expectations'); ?>"; color = "#17a2b8"; }
else if (score >= 2.50) { rating = "<?php echo lang('meets_expectations'); ?>"; color = "#ffc107"; }
else if (score >= 2.00) { rating = "<?php echo lang('needs_improvement'); ?>"; color = "#fd7e14"; }
else { rating = "<?php echo lang('unsatisfactory'); ?>"; color = "#dc3545"; }
$("#score-badge").text(score.toFixed(2) + " — " + rating).css("background-color", color);
});
$("#manual-entry-form").appForm({
onSuccess: function(result) {
// Reload whichever table is visible
$(".dataTable:visible").appTable({reload: true});
}
});
});
</script>
12. Calculation Progress Modal
View: modules/KpiMatrix/views/kpi_matrix/calculation_progress_modal.php
When running a calculation cycle for many employees, show a progress modal:
<div class="modal-body">
<div class="modal-header" style="background: linear-gradient(135deg, #1d97a6 0%, #10b1af 100%); color: white;">
<h4><?php echo lang('running_kpi_calculation'); ?> — <?php echo $cycle_name; ?></h4>
</div>
<!-- Step Indicators -->
<div class="progress-steps" style="display: flex; justify-content: space-between; margin: 25px 0;">
<div class="step pending" data-step="1">
<i class="fa fa-circle-o"></i>
<span><?php echo lang('collecting_data'); ?></span>
</div>
<div class="step pending" data-step="2">
<i class="fa fa-circle-o"></i>
<span><?php echo lang('computing_metrics'); ?></span>
</div>
<div class="step pending" data-step="3">
<i class="fa fa-circle-o"></i>
<span><?php echo lang('scoring_normalization'); ?></span>
</div>
<div class="step pending" data-step="4">
<i class="fa fa-circle-o"></i>
<span><?php echo lang('composite_aggregation'); ?></span>
</div>
<div class="step pending" data-step="5">
<i class="fa fa-circle-o"></i>
<span><?php echo lang('salary_recommendations'); ?></span>
</div>
</div>
<!-- Progress Bar -->
<div class="progress" style="height: 24px;">
<div class="progress-bar" id="calc-progress-bar" style="width: 0%; transition: width 0.3s;">
<span id="calc-progress-text">0%</span>
</div>
</div>
<!-- Log Console -->
<div id="calc-log-console" style="background: #2c3e50; color: #ecf0f1; padding: 15px;
border-radius: 5px; height: 250px; overflow-y: auto; font-family: monospace; font-size: 12px; margin-top: 15px;">
<div class="log-entry info">ℹ <?php echo lang('initializing_calculation'); ?>...</div>
</div>
<!-- Counters -->
<div style="display: grid; grid-template-columns: repeat(4, 1fr); gap: 15px; margin-top: 20px;">
<div class="text-center" style="padding: 15px; background: #ecf0f1; border-radius: 8px;">
<div id="cnt-processed" style="font-size: 28px; font-weight: bold; color: #1d97a6;">0</div>
<small><?php echo lang('processed'); ?></small>
</div>
<div class="text-center" style="padding: 15px; background: #ecf0f1; border-radius: 8px;">
<div id="cnt-scored" style="font-size: 28px; font-weight: bold; color: #28a745;">0</div>
<small><?php echo lang('scored'); ?></small>
</div>
<div class="text-center" style="padding: 15px; background: #ecf0f1; border-radius: 8px;">
<div id="cnt-skipped" style="font-size: 28px; font-weight: bold; color: #ffc107;">0</div>
<small><?php echo lang('skipped'); ?></small>
</div>
<div class="text-center" style="padding: 15px; background: #ecf0f1; border-radius: 8px;">
<div id="cnt-recommendations" style="font-size: 28px; font-weight: bold; color: #17a2b8;">0</div>
<small><?php echo lang('salary_recs'); ?></small>
</div>
</div>
</div>
<script>
$(document).ready(function() {
var cycleId = <?php echo $cycle_id; ?>;
var userIds = <?php echo json_encode($user_ids); ?>;
async function runCalculation() {
var batchSize = 5;
var total = userIds.length;
var chunks = Math.ceil(total / batchSize);
var processed = 0, scored = 0, skipped = 0, recommendations = 0;
// Step 1: Collecting data
updateStep(1, 'active');
appendLog('info', '<?php echo lang("collecting_data_for"); ?> ' + total + ' <?php echo lang("employees"); ?>...');
for (var i = 0; i < chunks; i++) {
var batch = userIds.slice(i * batchSize, (i + 1) * batchSize);
if (i === 0) { updateStep(1, 'completed'); updateStep(2, 'active'); }
var result = await $.ajax({
url: '<?php echo get_uri("kpi_matrix/calculate_batch"); ?>',
method: 'POST',
data: { cycle_id: cycleId, user_ids: batch }
});
// Update counters
processed += result.processed;
scored += result.scored;
skipped += result.skipped;
recommendations += result.recommendations;
$('#cnt-processed').text(processed);
$('#cnt-scored').text(scored);
$('#cnt-skipped').text(skipped);
$('#cnt-recommendations').text(recommendations);
// Update progress
var pct = Math.round(((i + 1) / chunks) * 100);
$('#calc-progress-bar').css('width', pct + '%');
$('#calc-progress-text').text(pct + '%');
// Log results
result.logs.forEach(function(msg) { appendLog(msg.type, msg.text); });
}
// Final steps
updateStep(2, 'completed');
updateStep(3, 'completed');
updateStep(4, 'completed');
updateStep(5, 'completed');
appendLog('success', '✓ <?php echo lang("calculation_complete"); ?>. ' + scored + ' <?php echo lang("employees_scored"); ?>.');
if (recommendations > 0) {
appendLog('info', '⚡ ' + recommendations + ' <?php echo lang("salary_recommendations_generated"); ?>.');
}
}
function updateStep(num, state) {
var $step = $('[data-step="' + num + '"]');
$step.removeClass('pending active completed');
$step.addClass(state);
var icons = { pending: 'fa-circle-o', active: 'fa-spinner fa-spin', completed: 'fa-check-circle' };
$step.find('i').attr('class', 'fa ' + icons[state]);
}
function appendLog(type, text) {
var symbols = { success: '✓', warning: '⚠', error: '✗', info: 'ℹ' };
var colors = { success: '#2ecc71', warning: '#f39c12', error: '#e74c3c', info: '#3498db' };
var $entry = $('<div class="log-entry">').css('color', colors[type]).text((symbols[type] || 'ℹ') + ' ' + text);
$('#calc-log-console').append($entry).scrollTop($('#calc-log-console')[0].scrollHeight);
}
runCalculation();
});
</script>
13. Notification Design
13.1 In-App Notifications
| Event | Recipient | Message |
|---|---|---|
| Review cycle activated | All assigned employees | "KPI Review '{cycle_name}' has started for {period}." |
| Scores calculated | Employee | "Your KPI scores for '{cycle_name}' are ready. Composite: {score}." |
| Manual score submitted | Employee | "A manual score was submitted for {metric_name} by {evaluator}." |
| Salary recommendation generated | HR Admin | "{count} salary recommendations from '{cycle_name}' are pending review." |
| Salary recommendation approved | Employee | "A salary adjustment has been approved based on your KPI performance." |
| Cycle completed | All assigned employees | "KPI Review '{cycle_name}' has been finalized." |
13.2 Notification Implementation
// Using existing ERPat notification system
log_notification(
$user_id,
"kpi_scores_ready",
array(
"cycle_name" => $cycle->cycle_name,
"composite_score" => number_format($composite, 2),
"rating" => $rating_label
)
);
14. Chart.js Integration
14.1 Script Loading
<!-- Add to template or specific views that need charts -->
<script src=""></script>
<script src=""></script>
14.2 Chart Types Used
| Chart | Purpose | Location |
|---|---|---|
| Radar | Category score visualization | My KPIs, Profile tab |
| Line | Score trend over time | My KPIs, Profile tab |
| Horizontal Bar | Rating distribution | Overview |
| Doughnut | Score source breakdown (auto vs manual) | Overview |
15. Export & Reporting
15.1 Excel Export (Matrix)
function export_matrix() {
$this->load->library('PHPExcel'); // Or PhpSpreadsheet
$cycle_id = $this->input->get('cycle_id');
$data = $this->Kpi_composite_results_model->get_matrix_data($cycle_id);
// Headers: Employee, Department, [Category1], [Category2], ..., Composite, Rating
// Data: One row per employee
// Formatting: Conditional color fills based on score bands
$filename = 'KPI_Matrix_' . date('Y-m-d') . '.xlsx';
// ... build and download
}
15.2 PDF Report (Individual)
function export_individual_pdf($user_id, $cycle_id) {
$this->load->library('pdf');
// Individual KPI report card:
// - Header: Employee info, cycle period, overall score
// - Radar chart (rendered as image)
// - Category breakdown table
// - Metric detail table
// - Score trend (rendered as image)
// - Salary recommendation (if any)
$this->pdf->Output('KPI_Report_' . $user_id . '.pdf', 'D');
}
16. Responsive Design
16.1 Breakpoint Considerations
| Component | Desktop (>1200px) | Tablet (768-1200px) | Mobile (<768px) |
|---|---|---|---|
| Team Matrix | Full pivot table | Scroll horizontally | Card view per employee |
| KPI Banner | 5 KPI cards in row | 3 + 2 rows | 1 per row stack |
| Radar Chart | 400×400 | 300×300 | 250×250 |
| Filter Bar | Inline row | 2-row wrap | Stacked vertical |
| Progress Modal | Full width | Full width | Full width, steps vertical |
16.2 CSS Variables
:root {
--kpi-primary: #1d97a6;
--kpi-primary-light: #10b1af;
--kpi-primary-dark: #0d7a87;
--kpi-outstanding: #28a745;
--kpi-exceeds: #17a2b8;
--kpi-meets: #ffc107;
--kpi-needs-improvement: #fd7e14;
--kpi-unsatisfactory: #dc3545;
--kpi-gray: #58585a;
}
17. View File Inventory
| File | Purpose | Lines (est.) |
|---|---|---|
kpi_matrix/index.php |
Tab container with buttons | ~120 |
kpi_matrix/timeline.php |
Activity feed | ~700 |
kpi_matrix/my_kpis.php |
Employee self-view | ~350 |
kpi_matrix/team_matrix.php |
Heat map pivot table | ~250 |
kpi_matrix/review_cycles_tab.php |
Cycle management DataTable | ~100 |
kpi_matrix/overview_tab.php |
Analytics dashboard | ~500 |
kpi_matrix/settings_tab.php |
Template management | ~200 |
kpi_matrix/cycle_modal_form.php |
Cycle create/edit | ~120 |
kpi_matrix/template_modal_form.php |
Template create/edit | ~200 |
kpi_matrix/manual_entry_modal.php |
Manual score entry | ~120 |
kpi_matrix/calculation_progress_modal.php |
Batch calc progress | ~250 |
team_members/kpi_tab.php |
Profile integration | ~200 |
settings/components/salary_options.php |
Auto-trigger config (section) | ~150 |
Total estimated view code: ~3,260 lines across 13 files.
18. Language Keys Required
// KPI Matrix module language keys (to add to default_lang.php)
$lang["kpi_matrix"] = "KPI Matrix";
$lang["my_kpis"] = "My KPIs";
$lang["team_matrix"] = "Team Matrix";
$lang["review_cycles"] = "Review Cycles";
$lang["composite_score"] = "Composite Score";
$lang["category_breakdown"] = "Category Breakdown";
$lang["performance_radar"] = "Performance Radar";
$lang["current_scores"] = "Current Scores";
$lang["previous_cycle"] = "Previous Cycle";
$lang["score_history"] = "Score History";
$lang["new_review_cycle"] = "New Review Cycle";
$lang["new_template"] = "New Template";
$lang["run_calculation"] = "Run Calculation";
$lang["rating_distribution"] = "Rating Distribution";
$lang["department_comparison"] = "Department Comparison";
$lang["avg_composite_score"] = "Avg Composite Score";
$lang["outstanding_performers"] = "Outstanding Performers";
$lang["pending_salary_actions"] = "Pending Salary Actions";
$lang["auto_trigger_enabled"] = "Auto-Trigger Enabled";
$lang["employees_evaluated"] = "Employees Evaluated";
$lang["weight_presets"] = "Weight Presets";
$lang["kpi_salary_auto_trigger"] = "KPI Auto-Trigger Salary Upgrade";
$lang["kpi_salary_auto_trigger_description"] = "Automatically generate salary upgrade recommendations based on KPI performance scores.";
$lang["enable_auto_trigger"] = "Enable Auto-Trigger";
$lang["kpi_salary_auto_trigger_help"] = "When enabled, the system will automatically generate salary step recommendations when a review cycle is completed.";
$lang["require_approval"] = "Require Approval";
$lang["kpi_salary_require_approval_help"] = "If enabled, recommendations require HR/admin approval before creating salary history records.";
$lang["minimum_review_cycles"] = "Minimum Review Cycles";
$lang["kpi_salary_min_cycles_help"] = "Employee must have completed this many review cycles before salary changes are recommended.";
$lang["effective_date_mode"] = "Effective Date Mode";
$lang["max_step_increase"] = "Max Step Increase Per Cycle";
$lang["kpi_salary_max_step_increase_help"] = "Maximum number of salary grade steps that can change in a single cycle.";
$lang["allow_salary_downgrade"] = "Allow Salary Downgrade";
$lang["kpi_salary_downgrade_warning"] = "Enabling this allows the system to recommend salary decreases for consistently poor performers. Use with caution.";
$lang["rating_step_mapping"] = "Rating-to-Step Mapping";
$lang["rating_step_mapping_help"] = "Define how many salary grade steps to move for each KPI rating. Use + for increase, - for decrease, 0 for no change.";
$lang["outstanding"] = "Outstanding";
$lang["exceeds_expectations"] = "Exceeds Expectations";
$lang["meets_expectations"] = "Meets Expectations";
$lang["needs_improvement"] = "Needs Improvement";
$lang["unsatisfactory"] = "Unsatisfactory";
$lang["running_kpi_calculation"] = "Running KPI Calculation";
$lang["collecting_data"] = "Collecting Data";
$lang["computing_metrics"] = "Computing Metrics";
$lang["scoring_normalization"] = "Scoring & Normalization";
$lang["composite_aggregation"] = "Composite Aggregation";
$lang["salary_recommendations"] = "Salary Recommendations";
$lang["calculation_complete"] = "Calculation complete";
$lang["employees_scored"] = "employees scored";
$lang["salary_recommendations_generated"] = "salary recommendations generated";
$lang["provide_evidence_for_score"] = "Provide supporting evidence or notes for this score...";
$lang["evidence_notes"] = "Evidence / Notes";
$lang["raw_value"] = "Raw Value";
$lang["weighted_score"] = "Weighted Score";
$lang["score_changes"] = "Score Changes";
$lang["immediately"] = "Immediately";
$lang["next_pay_period"] = "Next Pay Period";
$lang["first_of_next_month"] = "First of Next Month";
$lang["yes_require_approval"] = "Yes — Require Approval";
$lang["no_auto_apply"] = "No — Auto-Apply Directly";
Permissions & access control: role-based access, self-view/manager/HR visibility rules, and who can configure the salary auto-trigger settings are defined in code — see the module's permission config modules/KpiMatrix/config/permissions.php (permissions kpi_matrix and kpi_matrix_manage). This is the last document in the package; see README.md for the full document index.