# 📘 BLUEPRINT ARSITEKTUR: STANDARISASI LAYOUT AKHIR HISTORY TRANSAKSI EVEREST
**Versi:** 1.1 (Final dengan Onboarding Banner & Default Mode)  
**Target Lingkungan:** PHP 5.6 | CodeIgniter 3.1.8 | MariaDB 10 / MySQL | jQuery DataTables 1.10+  
**Ruang Lingkup:** Seluruh Modul Transaksi Everest (`penjualan`, `distribusifg`, `pembelian`, `penerimaan`, `produksi`, `distribusisupplies`, dll.)

---

## 1. Latar Belakang & Tujuan
Halaman riwayat (*History*) pada sistem transaksi sebelumnya memuat seluruh dataset ribuan hingga ratusan ribu transaksi ke dalam DOM browser secara *client-side*. Hal ini menimbulkan:
1. Beban memori browser sangat tinggi (*browser freezing/lag*).
2. Query database besar tanpa limitasi bertahap (*server resource starvation*).
3. Inkonsistensi tata letak (*layout*) antar-tahapan transaksi (*steps*) dan antar-modul.

**Blueprint ini menetapkan standar baku 12 pilar arsitektur Server-Side DataTables** yang siap direplikasi secara plug-and-play ke seluruh modul transaksi Everest.

---

## 2. Rangkuman 12 Pilar Standar Layout Akhir History

| No | Pilar Arsitektur | Deskripsi & Spesifikasi Teknis |
|:---|:---|:---|
| **1** | **Server-Side DataTables** | Paging, sorting, dan filtering diproses 100% di server melalui library `DatatablesHistory.php` dan endpoint AJAX `History::showDataAjax()`. |
| **2** | **Tab-Tab Diganti Filter Step & Unduh Mentah** | Tab navigasi konvensional (`nav-tabs`) disembunyikan. Digantikan tombol filter step instan (`Semua`, `Step 1`, `Step 2`, ...) dan dropdown collection DataTables `[Unduh Data Mentah ▾]`. |
| **3** | **Pencarian Bertingkat & Reset Cerdas** | Filter 3 lapis: Rentang Tanggal + Filter Step + Kata Kunci Global. Jika hasil 0 data (`recordsDisplay === 0`), muncul dialog interaktif untuk reset filter per layer atau global. |
| **4** | **Sorting Multi-Kolom Database Aman** | Sorting DataTables diarahkan langsung ke kolom database riil melalui mapping `$safeDbSortCols` (25+ kolom umum). Fallback aman jika kolom relasi/virtual. |
| **5** | **Penanganan Data Relasi / Pair / Registry** | Pemuatan data pendukung via batch querying `transaksi_data_registry` mencakup `id` dan `id_top`, unpacking compound keys `__`, serta fallback `item_fields` agar kolom pair tidak kosong. |
| **6** | **Tombol Toggle Mode (Lama ↔ Baru)** | Tombol switch instan di header berbasis Session/Cookie (`pref_history_serverside`), memungkinkan pengguna beralih mulus tanpa merusak konfigurasi lama. |
| **7** | **Kolom Seragam Antar-Step** | Definisi kolom seragam untuk seluruh step transaksi melalui helper/fungsi `getUniformHistoryFields($jenisTr)` sehingga struktur tabel tidak bergeser saat ganti step. |
| **8** | **Dual Pagination (Atas & Bawah)** | Kontrol pagination DataTables diletakkan di toolbar atas (sebelah kanan page length) dan toolbar bawah (sebelah kanan info record). |
| **9** | **UX/UI Toolbar Tombol Rapi & Compact** | Tata letak toolbar seragam: `[Cetak] [Unduh Tabel] [Unduh Data Mentah ▾] | [Semua] [Step A] [Step B]...` di kiri, pencarian di kanan. |
| **10** | **Toolbar Rentang Tanggal AJAX di Area Tabel** | Panel filter tanggal lama di template wrapper disembunyikan via CSS (`.legacy-date-filter-panel`). Digantikan toolbar compact date range di atas tabel yang reload murni via AJAX tanpa refresh halaman. |
| **11** | **Standardisasi Tag Tabel Detail Transaksi** | Seluruh tabel rincian item (`viewDetailTransaksi` di `he_misc_helper.php`) wajib menggunakan class dan style seragam: `<table name="tableHistoryShow" class="table dataTable table-bordered $show_css compact" style="margin-top:0px;font-size:10px;">`. |
| **12** | **Default Mode Baru & Onboarding Banner (LocalStorage)** | Default tampilan untuk pengguna yang baru membuka (belum ada jejak setting) adalah **Mode Baru (Server-Side)**. Banner notifikasi interaktif muncul memberi tahu bahwa sistem sedang ditingkatkan, dilengkapi tombol pintas **[Paksa Kembali Ke Mode Lama]** dan **[Saya Mengerti]** yang tersimpan di `localStorage`. |

---

## 3. Struktur Berkas yang Terlibat per Modul

Untuk menerapkan arsitektur ini ke sebuah modul baru (misal: `[nama_modul]`), berkas-berkas yang dimodifikasi/dibuat meliputi:

```
application/
├── helpers/
│   └── he_misc_helper.php                     # Standarisasi tableHistoryShow (Poin 11)
├── modules/
│   └── [nama_modul]/
│       ├── controllers/
│       │   └── History.php                    # Controller endpoint AJAX, Toggle, & Default Mode (Poin 1, 6, 12)
│       ├── libraries/
│       │   └── DatatablesHistory.php          # Engine query server-side (atau shared) (Poin 1, 4, 5)
│       ├── template/
│       │   └── history.html                   # Wrapper template (tambahkan class legacy filter) (Poin 10)
│       ├── views/
│       │   └── history.php                    # View DataTables, banner onboarding, toolbar AJAX (Poin 2, 8, 9, 10, 12)
│       └── UAT_HISTORY.md                     # Instrumen pengujian spesifik modul (Metode Golden Circle 5W+1H)
```

---

## 4. Panduan Implementasi Langkah Demi Langkah

### Langkah 1: Modifikasi Template Wrapper (`template/history.html`)
Beri identitas class `legacy-date-filter-panel` dan `id="legacy_date_filter"` pada baris pembungkus filter tanggal lama.

```html
<!-- START OF COMPLETE REPEATED LOGIC -->
            <div class="row legacy-date-filter-panel" id="legacy_date_filter">
                <div class="col-md-12">
<!-- END OF COMPLETE REPEATED LOGIC -->
                    <div class="panel panel-default">
                        <div class="panel-body">
                            <div class="table-responsive">
                                <table class="table table-condensed no-padding no-border">
                                    <tr>
                                        <td>
                                            <span class="glyphicon glyphicon-calendar"></span> start date
                                        </td>
                                        <td>
                                            <input id="date1" class="form-control" type="date" value="{date1}">
                                        </td>
                                        <td>
                                            <span class="glyphicon glyphicon-calendar"></span> to date
                                        </td>
                                        <td>
                                            <input id="date2" class="form-control" type="date" value="{date2}">
                                        </td>
                                        <td>
                                            <a class="btn btn-primary btn-block" href="javascript:void(0)"
                                               onclick="location.href='{url}?date1='+document.getElementById('date1').value+'&date2='+document.getElementById('date2').value+'{getSearch}';">
                                                <span class="fa fa-arrow-right"></span>
                                            </a>
                                        </td>
                                    </tr>
                                </table>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
```

---

### Langkah 2: Controller `History.php` (Default Mode, Endpoint AJAX, Toggle, & Kolom Seragam)

Di dalam controller `History.php`, tetapkan default mode **BARU (Server-Side)** jika belum ada preferensi tersimpan, dan siapkan endpoint terkait:

```php
// START OF COMPLETE REPEATED LOGIC
    /**
     * Dispatcher tampilan riwayat: Default ke Mode Baru ($isServerSide = true)
     */
    public function viewHistory()
    {
        if (!isset($this->session->login['id'])) {
            gotoLogin();
        }
        $userId = $this->session->login['id'];
        $prefKey = "pref_history_serverside_" . $userId;
        $isServerSide = true; // Default aktif ke mode baru bagi user baru (Poin 12)

        if (isset($_SESSION['login']['pref_history_serverside'])) {
            $isServerSide = ($_SESSION['login']['pref_history_serverside'] == '1');
        } elseif (isset($_COOKIE[$prefKey])) {
            $isServerSide = ($_COOKIE[$prefKey] == '1');
            $_SESSION['login']['pref_history_serverside'] = $_COOKIE[$prefKey];
        }

        if ($isServerSide) {
            return $this->viewHistoryServerSide();
        } else {
            return $this->viewHistoryLegacy();
        }
    }

    /**
     * Toggle mode tampilan riwayat (Lama vs Baru)
     */
    public function toggleHistoryMode()
    {
        $mode = $this->input->get("mode");
        $redirect = $this->input->get("redirect");
        $userId = isset($this->session->login['id']) ? $this->session->login['id'] : 0;
        $prefKey = "pref_history_serverside_" . $userId;

        $cookieVal = ($mode === "new") ? "1" : "0";
        setcookie($prefKey, $cookieVal, time() + (365 * 86400), "/");
        if (isset($_SESSION['login'])) {
            $_SESSION['login']['pref_history_serverside'] = $cookieVal;
        }

        if ($this->input->is_ajax_request()) {
            $this->output
                ->set_content_type('application/json')
                ->set_output(json_encode(array("status" => "success", "mode" => $mode)));
            return;
        }

        if (!empty($redirect)) {
            redirect($redirect);
        } else {
            redirect(base_url() . $this->uri->segment(1) . "/History/data");
        }
    }

    /**
     * Definisi struktur kolom seragam antar seluruh step
     */
    protected function getUniformHistoryFields($jenisTr)
    {
        // Sesuaikan mapping kolom seragam modul di sini
        $fields = array(
            "nomer" => array("label" => "Nomer Transaksi", "type" => "link_detail"),
            "tanggal" => array("label" => "Tanggal", "type" => "date"),
            "customer_nama" => array("label" => "Pihak Terkait", "type" => "text"),
            "gudang_asal_nama" => array("label" => "Gudang Asal", "type" => "text"),
            "gudang_tujuan_nama" => array("label" => "Gudang Tujuan", "type" => "text"),
            "item_fields" => array("label" => "Rincian Item", "type" => "items_preview"),
            "status_dokumen" => array("label" => "Status", "type" => "status_badge"),
            "action" => array("label" => "Aksi", "type" => "action_buttons")
        );
        return $fields;
    }

    /**
     * Handler Server-Side DataTables AJAX
     */
    public function showDataAjax($jenisTr = "", $currentState = "all")
    {
        if (!$this->input->is_ajax_request()) {
            show_404();
            return;
        }

        $this->load->library("DatatablesHistory");
        $dt = new DatatablesHistory();

        $postData = $this->input->post();
        $date1 = isset($postData['date1']) ? $postData['date1'] : "";
        $date2 = isset($postData['date2']) ? $postData['date2'] : "";
        $isGlobalDate = (isset($postData['date_global']) && $postData['date_global'] == "1");
        $stepFilter = isset($postData['currentState']) ? $postData['currentState'] : $currentState;

        $response = $dt->getData($jenisTr, $stepFilter, $date1, $date2, $isGlobalDate, $postData);
        echo json_encode($response);
        exit();
    }
// END OF COMPLETE REPEATED LOGIC
```

---

### Langkah 3: View `views/history.php` (CSS, Banner Onboarding, Toolbar AJAX, & DataTables)

Di bagian atas `views/history.php`, pasang tombol switch, isolasi CSS filter lama, dan fungsi bantuan `localStorage`:

```php
// START OF COMPLETE REPEATED LOGIC
$isServerSide = (isset($isServerSide) && $isServerSide) ? true : false;
$currentFullUrl = current_url() . (!empty($_SERVER['QUERY_STRING']) ? '?' . $_SERVER['QUERY_STRING'] : '');
if ($isServerSide) {
    $switchUrl = base_url() . $this->uri->segment(1) . "/History/toggleHistoryMode?mode=old&redirect=" . urlencode($currentFullUrl);
    $btnSwitchHistory = "<button type='button' class='btn btn-sm btn-warning' title='Beralih kembali ke tampilan tabel lama' onclick=\"switchHistoryMode('old', '$switchUrl');\" style='font-weight: bold; border-radius: 4px; padding: 4px 10px; margin-bottom: 2px;'><i class='fa fa-backward'></i> Kembali Ke Tampilan History Lama</button>";
    $btnSwitchHistory .= "<style type='text/css'>.legacy-date-filter-panel, #legacy_date_filter { display: none !important; }</style>";
} else {
    $switchUrl = base_url() . $this->uri->segment(1) . "/History/toggleHistoryMode?mode=new&redirect=" . urlencode($currentFullUrl);
    $btnSwitchHistory = "<button type='button' class='btn btn-sm btn-success' title='Coba tampilan baru yang lebih cepat dengan server-side paging & pencarian instan' onclick=\"switchHistoryMode('new', '$switchUrl');\" style='font-weight: bold; border-radius: 4px; padding: 4px 10px; margin-bottom: 2px;'><i class='fa fa-rocket'></i> Coba Tampilan History Baru</button>";
}
$btnSwitchHistory .= "<script>
if (typeof switchHistoryMode === 'undefined') {
    function switchHistoryMode(mode, url) {
        var msg = (mode === 'old') 
            ? 'Beralih kembali ke tampilan history lama?' 
            : 'Beralih ke tampilan history baru (Mode Cepat & Paging Server)?';
        if (confirm(msg)) {
            try {
                localStorage.setItem('pref_history_mode', mode);
                if (mode === 'old') {
                    localStorage.setItem('history_new_mode_notice_dismissed', '1');
                }
            } catch(e) {}
            $.ajax({
                url: url,
                type: 'GET',
                dataType: 'json',
                success: function() {
                    location.reload();
                },
                error: function() {
                    location.href = url;
                }
            });
        }
    }
}
if (typeof dismissHistoryUpgradeNotice === 'undefined') {
    function dismissHistoryUpgradeNotice() {
        try {
            localStorage.setItem('history_new_mode_notice_dismissed', '1');
        } catch(e) {}
        $('.history-upgrade-notice-banner').slideUp(200);
    }
}
</script>";
// END OF COMPLETE REPEATED LOGIC
```

Pada blok render tabel server-side:

```php
// START OF COMPLETE REPEATED LOGIC
$content .= "<div class=\"box-body\">";
if ($isServerSide) {
    $date1_val = isset($filters['date1']) ? $filters['date1'] : '';
    $date2_val = isset($filters['date2']) ? $filters['date2'] : '';
    $switchOldUrl = base_url() . $this->uri->segment(1) . "/History/toggleHistoryMode?mode=old&redirect=" . urlencode($currentFullUrl);

    // 1. Banner Pemberitahuan Peningkatan Sistem (Poin 12)
    $content .= "<div class='history-upgrade-notice-banner alert alert-info alert-dismissible' style='display:none; margin-bottom:10px; padding:10px 14px; border-left:4px solid #0284c7; background-color:#f0f9ff; color:#0369a1; border-radius:4px; font-size:12px;'>";
    $content .= "<button type='button' class='close' onclick='dismissHistoryUpgradeNotice();' style='color:#0369a1; opacity:0.8; font-size:18px; line-height:1;'>&times;</button>";
    $content .= "<div class='row' style='display:flex; align-items:center; justify-content:space-between; flex-wrap:wrap; margin:0;'>";
    $content .= "<div style='flex:1; min-width:280px; padding:2px 0;'>";
    $content .= "<i class='fa fa-info-circle fa-lg text-primary' style='margin-right:6px; vertical-align:middle;'></i> ";
    $content .= "<strong>Pemberitahuan Peningkatan Sistem:</strong> Tampilan riwayat transaksi sedang ditingkatkan ke sistem baru (lebih cepat &amp; hemat memori). Jika Anda mengalami kendala, menemukan bug, atau merasa tidak nyaman, Anda dapat beralih kembali ke mode lama kapan saja.";
    $content .= "</div>";
    $content .= "<div style='padding:2px 0; white-space:nowrap;'>";
    $content .= "<button type='button' class='btn btn-xs btn-warning' onclick=\"switchHistoryMode('old', '$switchOldUrl');\" style='margin-right:6px; padding:3px 8px; font-weight:700;'><i class='fa fa-backward'></i> Paksa Kembali Ke Mode Lama</button>";
    $content .= "<button type='button' class='btn btn-xs btn-default' onclick='dismissHistoryUpgradeNotice();' style='padding:3px 8px;'><i class='fa fa-check'></i> Saya Mengerti</button>";
    $content .= "</div>";
    $content .= "</div>";
    $content .= "</div>";

    // 2. Toolbar Rentang Tanggal AJAX Compact (Poin 10)
    $content .= "<div class='row dt-toolbar-date' style='margin-bottom:8px; padding:6px 10px; background:#f8fafc; border:1px solid #e2e8f0; border-radius:4px;'>";
    $content .= "<div class='col-sm-8 col-xs-12 form-inline' style='padding-left:0;'>";
    $content .= "<span style='font-weight:600; font-size:12px; color:#475569; margin-right:6px;'><i class='fa fa-calendar text-primary'></i> Rentang Tanggal:</span> ";
    $content .= "<input type='date' id='filter_date1_$tableHtmlId' class='form-control input-sm' value='$date1_val' style='width:135px; height:28px; font-size:11px; padding:2px 6px;'> ";
    $content .= "<span style='font-size:11px; color:#64748b; margin:0 4px;'>s/d</span> ";
    $content .= "<input type='date' id='filter_date2_$tableHtmlId' class='form-control input-sm' value='$date2_val' style='width:135px; height:28px; font-size:11px; padding:2px 6px;'> ";
    $content .= "<button type='button' class='btn btn-sm btn-primary' id='btn_apply_date_$tableHtmlId' style='height:28px; padding:2px 10px; font-size:11px; font-weight:600; margin-left:4px;'><i class='fa fa-filter'></i> Terapkan</button> ";
    $content .= "<button type='button' class='btn btn-sm btn-default' id='btn_reset_date_$tableHtmlId' style='height:28px; padding:2px 10px; font-size:11px; margin-left:2px;'><i class='fa fa-refresh'></i> Semua Tanggal</button>";
    $content .= "</div>";
    $content .= "<div class='col-sm-4 col-xs-12 text-right' id='label_date_active_$tableHtmlId' style='padding-right:0; padding-top:4px; font-size:11px;'></div>";
    $content .= "</div>";

    // 3. Kontainer Tabel
    $content .= "<div class='table-responsive $tableHtmlId'>";
    $content .= "<table id='$tableHtmlId' class='table display no-padding'>";
    $content .= "<thead><tr bgcolor='#f0f0f0'>";
    foreach ($arrayHistoryLabels as $key => $label) {
        $lblText = is_array($label) ? $label['label'] : $label;
        $content .= "<th class='text-uppercase text-center'>$lblText</th>";
    }
    $content .= "</tr></thead>";
    $content .= "<tbody></tbody>";
    $content .= "<tfoot><tr class='tfooter bg-grey-1'>";
    foreach ($arrayHistoryLabels as $key => $label) {
        $content .= "<th>--</th>";
    }
    $content .= "</tr></tfoot>";
    $content .= "</table></div>";

    // 4. Script DataTables & Interaksi Filter
    $content .= "<script>
    var activeStep = '$currentState';
    var isGlobalDate = false;
    var table;

    function updateDateFilterLabel_$tableHtmlId() {
        var d1 = $('#filter_date1_$tableHtmlId').val();
        var d2 = $('#filter_date2_$tableHtmlId').val();
        if (isGlobalDate || (!d1 && !d2)) {
            $('#label_date_active_$tableHtmlId').html('<span class=\"label label-info\" style=\"font-size:11px; padding:3px 8px;\"><i class=\"fa fa-globe\"></i> Semua Tanggal</span>');
        } else {
            var str = (d1 ? d1 : '...') + ' s/d ' + (d2 ? d2 : '...');
            $('#label_date_active_$tableHtmlId').html('<span class=\"label label-default\" style=\"font-size:11px; padding:3px 8px;\"><i class=\"fa fa-calendar\"></i> ' + str + '</span>');
        }
    }

    function resetStepFilter_$tableHtmlId() {
        activeStep = 'all';
        $('.btn-step-filter').removeClass('active btn-primary').addClass('btn-default');
        $('.btn-step-all').removeClass('btn-default').addClass('active btn-primary');
        table.ajax.reload();
    }

    function resetDateFilter_$tableHtmlId() {
        isGlobalDate = true;
        $('#filter_date1_$tableHtmlId').val('');
        $('#filter_date2_$tableHtmlId').val('');
        updateDateFilterLabel_$tableHtmlId();
        table.ajax.reload();
    }

    function resetAllFilters_$tableHtmlId() {
        activeStep = 'all';
        isGlobalDate = true;
        $('#filter_date1_$tableHtmlId').val('');
        $('#filter_date2_$tableHtmlId').val('');
        updateDateFilterLabel_$tableHtmlId();
        $('.btn-step-filter').removeClass('active btn-primary').addClass('btn-default');
        $('.btn-step-all').removeClass('btn-default').addClass('active btn-primary');
        table.ajax.reload();
    }

    $(document).ready(function(){
        // Cek jejak simpan di localStorage (Poin 12)
        try {
            if (!localStorage.getItem('history_new_mode_notice_dismissed')) {
                $('.history-upgrade-notice-banner').show();
            }
        } catch(e) {}

        updateDateFilterLabel_$tableHtmlId();

        $('#btn_apply_date_$tableHtmlId').on('click', function() {
            var d1 = $('#filter_date1_$tableHtmlId').val();
            var d2 = $('#filter_date2_$tableHtmlId').val();
            isGlobalDate = (!d1 && !d2);
            updateDateFilterLabel_$tableHtmlId();
            table.ajax.reload();
        });

        $('#btn_reset_date_$tableHtmlId').on('click', function() {
            resetDateFilter_$tableHtmlId();
        });

        table = $('#$tableHtmlId').DataTable({
            dom: \"<'row dt-toolbar-top'<'col-sm-7 col-xs-12'B><'col-sm-5 col-xs-12'f>>\" +
                 \"<'row dt-toolbar-sub'<'col-sm-6 col-xs-12'l><'col-sm-6 col-xs-12 text-right'p>>\" +
                 \"<'row'<'col-sm-12'tr>>\" +
                 \"<'row dt-toolbar-bottom'<'col-sm-5 col-xs-12'i><'col-sm-7 col-xs-12 text-right'p>>\",
            fixedHeader: true,
            processing: true,
            serverSide: true,
            searchDelay: 800,
            pageLength: 10,
            lengthMenu: [[10, 20, 50, 100], [10, 20, 50, 100]],
            ajax: {
                url: '" . MODUL_PATH . "History/showDataAjax/$jenisTr/$currentState',
                type: 'POST',
                data: function (d) {
                    d.currentState = activeStep;
                    d.date1 = isGlobalDate ? '' : $('#filter_date1_$tableHtmlId').val();
                    d.date2 = isGlobalDate ? '' : $('#filter_date2_$tableHtmlId').val();
                    d.date_global = isGlobalDate ? 1 : 0;
                }
            },
            columns: [ $columnsJsStr ],
            buttons: [
                { extend: 'print', text: '<i class=\"fa fa-print\"></i> Cetak', className: 'btn-sm btn-default', footer: true },
                {
                    text: '<i class=\"fa fa-file-excel-o\"></i> Unduh Tabel',
                    className: 'btn-sm btn-success',
                    action: function (e, dt, node, config) {
                        fnExcelReport('$tableHtmlId');
                    }
                },
                $download_step_buttons
                $step_filter_buttons
            ],
            drawCallback: function (settings) {
                var api = this.api();
                var pageInfo = api.page.info();
                var searchVal = api.search();
                var hasSearchVal = (searchVal && searchVal.trim().length > 0);
                var isStepFiltered = (activeStep !== 'all');
                var curDate1 = $('#filter_date1_$tableHtmlId').val();
                var curDate2 = $('#filter_date2_$tableHtmlId').val();
                var isDateFiltered = (!isGlobalDate && (curDate1 !== '' || curDate2 !== ''));

                $('#search_empty_filter_alert_$tableHtmlId').remove();

                if (pageInfo.recordsDisplay === 0 && (hasSearchVal || isStepFiltered || isDateFiltered)) {
                    var alertHtml = '<div id=\"search_empty_filter_alert_$tableHtmlId\" class=\"alert alert-warning\" style=\"margin: 10px 0; padding: 12px 15px; border-radius: 4px; font-size: 13px;\">';
                    alertHtml += '<i class=\"fa fa-info-circle fa-lg\"></i> <strong>Data tidak ditemukan</strong> untuk pencarian saat ini dengan filter yang sedang aktif.<br>';
                    alertHtml += '<div style=\"margin-top: 8px;\">';
                    if (isStepFiltered) {
                        alertHtml += '<button type=\"button\" class=\"btn btn-xs btn-primary\" onclick=\"resetStepFilter_$tableHtmlId();\" style=\"margin-right: 6px; font-weight: bold;\"><i class=\"fa fa-list\"></i> Cari di Semua Tahapan</button>';
                    }
                    if (isDateFiltered) {
                        alertHtml += '<button type=\"button\" class=\"btn btn-xs btn-info\" onclick=\"resetDateFilter_$tableHtmlId();\" style=\"margin-right: 6px; font-weight: bold;\"><i class=\"fa fa-calendar-times-o\"></i> Cari di Semua Tanggal</button>';
                    }
                    if (isStepFiltered || isDateFiltered) {
                        alertHtml += '<button type=\"button\" class=\"btn btn-xs btn-warning\" onclick=\"resetAllFilters_$tableHtmlId();\" style=\"font-weight: bold;\"><i class=\"fa fa-globe\"></i> Cari di Seluruh Data Global</button>';
                    }
                    alertHtml += '</div></div>';

                    var emptyCell = $('#$tableHtmlId tbody td.dataTables_empty');
                    if (emptyCell.length) {
                        emptyCell.html(alertHtml);
                    } else {
                        $(api.table().container()).prepend(alertHtml);
                    }
                }
            }
        });
    });
    </script>";
}
// END OF COMPLETE REPEATED LOGIC
```

---

### Langkah 4: Helper `he_misc_helper.php` (Tag Tabel Rincian Detail Transaksi)

Pastikan semua tag pembuka tabel rincian transaksi menggunakan class dan style baku:

```php
// START OF COMPLETE REPEATED LOGIC
$tblItems .= "<table name='tableHistoryShow' class='table dataTable table-bordered $show_css compact' style='margin-top:0px;font-size:10px;'>";
// END OF COMPLETE REPEATED LOGIC
```

---

## 5. Checklist Validasi & Uji Replikasi Modul

Sebelum menyatakan implementasi selesai pada modul baru, jalankan uji verifikasi berikut:

- [ ] **Sintaks PHP 5.6**: Jalankan lint `php -l` pada semua berkas yang diubah (tidak ada `??`, `[]`, arrow function, atau spread operator).
- [ ] **Default Mode Pengguna Baru**: User yang belum memiliki jejak setting cookie/session langsung mendapatkan tampilan **Mode Baru (Server-Side)**.
- [ ] **Onboarding Banner (LocalStorage)**: Pada kunjungan pertama, banner info peningkatan muncul dengan opsi `[Paksa Kembali Ke Mode Lama]` dan `[Saya Mengerti]`.
- [ ] **Dismiss Banner**: Klik `[Saya Mengerti]` menghilangkan banner dan tidak muncul kembali pada reload berikutnya (`localStorage.getItem('history_new_mode_notice_dismissed') == '1'`).
- [ ] **Paksa Revert ke Mode Lama**: Klik `[Paksa Kembali Ke Mode Lama]` memunculkan konfirmasi dan berhasil memindahkan user ke tampilan legacy.
- [ ] **Toggle Mode Manual**: Tombol "Coba Tampilan History Baru" dan "Kembali Ke Tampilan History Lama" di header berfungsi mulus.
- [ ] **Isolasi Filter Tanggal**: Saat mode baru aktif, panel tanggal lama di `template/history.html` tersembunyi sempurna, dan toolbar tanggal AJAX baru muncul di atas tabel.
- [ ] **Filter Tanggal AJAX**: Mengubah rentang tanggal dan menekan tombol "Terapkan" memicu `table.ajax.reload()` tanpa reload halaman.
- [ ] **Reset Filter**: Jika mencari kata kunci yang tidak ada, muncul alert box kuning dengan tombol pintasan reset (Step, Tanggal, dan Global) yang berfungsi normal.
- [ ] **Sorting Data**: Kolom nomor, tanggal, status, customer dapat diurutkan (ASC/DESC) tanpa menghasilkan error database.
- [ ] **Data Mentah & Export**: Dropdown collection `[Unduh Data Mentah ▾]` menampilkan daftar ekspor per-step sesuai konfigurasi modul, dan `[Unduh Tabel]` mengunduh tabel yang sedang aktif.
- [ ] **Detail Transaksi**: Klik lihat detail item transaksi merender tabel dengan class `table dataTable table-bordered $show_css compact` dan font 10px rapi.
- [ ] **Dokumen UAT Modul**: Berkas `UAT_HISTORY.md` khusus modul tersebut telah di-generate dengan konfigurasi step, aktor, dan kolom yang relevan.
