KPI Matrix How-to Guide Public

06 — UI & Dashboards

06 — UI & Dashboards Visual design specifications for the KPI Matrix module — employee cards, manager dashboards, HR overview, review forms, salary auto-trigger settings, and…

Guide version: r1 Module version: 1.4.0 Updated: 2026-07-22 Estimated time: 36 min 2 views

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 under modules/KpiMatrix/views/kpi_matrix/. June 2026 reconciliation. KPI Matrix is now a standalone top-level module; the routes are registered at top-level kpi_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.

Was this guide helpful?

Report a content problem