FIX_DIAGONSIS_COMMENTS

This commit is contained in:
VENKATESHWARAN 2026-08-01 11:56:03 +05:30
parent e30c17ae37
commit bf2908723a
2 changed files with 326 additions and 84 deletions

View File

@ -81,7 +81,11 @@ class HrLoginDiagnoseController extends AdminController
'HR_NOT_FOUND',
'high',
'No active HR found in post level_contacts for the given email/mobile (contact_type=client).',
'Create/activate the HR contact under the correct client branch, or verify the email/mobile.'
[
'Option A: Verify email/mobile spelling and that level_contacts.contact_type = client and is_active = 1.',
'Option B: Create/activate the HR contact under the correct client_branch (ref_id = branch id).',
'Option C: If HR exists only in pre DB, create the matching post level_contacts row first.',
]
);
return [
@ -89,6 +93,7 @@ class HrLoginDiagnoseController extends AdminController
'overall_status' => 'FAIL',
'summary' => 'HR not found in post DB.',
'issues' => $issues,
'problems' => $issues,
'post' => ['contacts' => []],
'hr_access_control'=> [],
'pre' => ['reachable' => null, 'mappings' => []],
@ -102,6 +107,7 @@ class HrLoginDiagnoseController extends AdminController
$accessRows = [];
$preMaps = [];
$loginCards = [];
$pairRows = [];
$preReachable = null;
try {
@ -115,7 +121,11 @@ class HrLoginDiagnoseController extends AdminController
'PRE_DB_UNREACHABLE',
'high',
'Could not connect to preDB: ' . $e->getMessage(),
'Check preDB credentials in Database config / .env.'
[
'Option A: Check preDB hostname/database/username/password in app/Config/Database.php or .env.',
'Option B: Confirm pre DB server is reachable from this host (network/firewall).',
'Option C: Re-run diagnose after connectivity is restored to validate PRE mapping.',
]
);
}
@ -130,16 +140,24 @@ class HrLoginDiagnoseController extends AdminController
$issues[] = $this->issue(
'BRANCH_INACTIVE',
'high',
"Branch inactive for post_hr_id={$contact['post_hr_id']} (branch #{$contact['post_branch_id']}).",
'Activate client_branch.is_active=1 for this branch.'
"Branch inactive for post_hr_id={$contact['post_hr_id']} (branch #{$contact['post_branch_id']}, {$contact['branch_name']}).",
[
"Option A: UPDATE client_branch SET is_active=1 WHERE id={$contact['post_branch_id']};",
'Option B: In Client Onboarding, activate this branch and re-check login.',
'Note: Login join requires client_branch.is_active=1.',
]
);
}
if (!$flags['CLIENT_ACTIVE']) {
$issues[] = $this->issue(
'CLIENT_INACTIVE',
'high',
"Client inactive for post_hr_id={$contact['post_hr_id']} (client #{$contact['post_client_id']}).",
'Activate clients.is_active=1 for this client.'
"Client inactive for post_hr_id={$contact['post_hr_id']} (client #{$contact['post_client_id']}, {$contact['client_name']}).",
[
"Option A: UPDATE clients SET is_active=1 WHERE id={$contact['post_client_id']};",
'Option B: Activate the client in admin and re-run diagnose.',
'Note: Login join requires clients.is_active=1.',
]
);
}
@ -160,8 +178,12 @@ class HrLoginDiagnoseController extends AdminController
$issues[] = $this->issue(
'NO_HR_ACCESS_CONTROL_ROW',
'high',
"No hr_access_control row for post_hr_id={$contact['post_hr_id']}, post_branch_id={$contact['post_branch_id']}, post_client_id={$contact['post_client_id']}.",
'Open Client Onboarding → HR Access and save at least one module for this HR/branch.'
"No hr_access_control row for post_hr_id={$contact['post_hr_id']}, post_branch_id={$contact['post_branch_id']}, post_client_id={$contact['post_client_id']} ({$contact['client_name']} / {$contact['branch_name']}).",
[
'Option A: Open Client Onboarding → HR Access for this client, select at least one module for this HR/branch, then Save.',
"Option B: Insert hr_access_control with post_hr_id={$contact['post_hr_id']}, post_branch_id={$contact['post_branch_id']}, post_client_id={$contact['post_client_id']} and allowed_modules JSON.",
'Note: Without this row, getVerifiedHrData returns empty token for this card.',
]
);
} else {
$modules = json_decode($access['allowed_modules'] ?? '[]', true);
@ -176,8 +198,12 @@ class HrLoginDiagnoseController extends AdminController
$issues[] = $this->issue(
'EMPTY_ALLOWED_MODULES',
'high',
"hr_access_control id={$access['id']} exists but allowed_modules is empty.",
'Assign pre/post modules in HR Access Control and save.'
"hr_access_control id={$access['id']} exists but allowed_modules is empty for post_hr_id={$contact['post_hr_id']}.",
[
'Option A: Open HR Access Control, tick PRE/POST modules for this HR, Save.',
"Option B: UPDATE hr_access_control SET allowed_modules='{\"pre\":[1],\"post\":[2,3,4]}' WHERE id={$access['id']}; (adjust module ids as needed).",
'Note: Empty modules → token is treated as not ready.',
]
);
}
}
@ -206,25 +232,52 @@ class HrLoginDiagnoseController extends AdminController
if ($preReachable === true) {
if (empty($preMap['PRE_BRANCH_LINKED'])) {
$postBranchId = (int) ($contact['post_branch_id'] ?? 0);
$issues[] = $this->issue(
'PRE_BRANCH_NOT_LINKED',
'medium',
"Post branch #{$contact['post_branch_id']} has no valid pre_branch_id link.",
'Set client_branch.pre_branch_id to the matching pre branch id.'
"Post branch #{$postBranchId} ({$contact['branch_name']}) has no valid pre_branch_id link (current pre_branch_id=" . ($contact['pre_branch_id'] ?? 'null') . ").",
[
"Option A: Set client_branch.pre_branch_id on post branch #{$postBranchId} to the matching pre client_branch.id.",
'Option B: Use Testing/mapping tools to sync post↔pre branch ids, then re-run diagnose.',
'Option C: Confirm the pre branch exists and is_active=1 before linking.',
'Note: Without this link, PRE HR/client mapping cannot be resolved.',
]
);
} elseif (empty($preMap['PRE_HR_MATCHED'])) {
$issues[] = $this->issue(
'PRE_HR_NOT_MATCHED',
'medium',
"No pre level_contacts match for email+mobile under pre client #{$preMap['pre_client_id']}.",
'Align email and mobile between pre and post HR contacts.'
"No pre level_contacts match for email/mobile under pre client #{$preMap['pre_client_id']} "
. "(post_hr_id={$contact['post_hr_id']}, email={$contact['hr_mail']}, mobile={$contact['hr_mobile']}).",
[
'Option A: Align email AND mobile between pre and post level_contacts for this HR.',
"Option B: Create/activate pre HR contact under pre client #{$preMap['pre_client_id']} with the same email/mobile.",
'Option C: Check pre contact_type=client and is_active=1 on the matching branch.',
]
);
} elseif (empty($preMap['PRE_IDS_CONSISTENT']) && !empty($access)) {
$storedHr = (int) ($access['pre_hr_id'] ?? 0);
$storedClient = (int) ($access['pre_client_id'] ?? 0);
$storedBranch = (int) ($access['pre_branch_id'] ?? 0);
$liveHr = (int) ($preMap['pre_hr_id'] ?? 0);
$liveClient = (int) ($preMap['pre_client_id'] ?? 0);
$liveBranch = (int) ($preMap['pre_branch_id'] ?? 0);
$accessId = (int) ($access['id'] ?? 0);
$issues[] = $this->issue(
'PRE_IDS_INCONSISTENT',
'medium',
"hr_access_control pre ids do not match live pre data for post_hr_id={$contact['post_hr_id']}.",
'Re-save HR Access Control so pre_hr_id / pre_client_id / pre_branch_id are refreshed.'
"hr_access_control pre ids do not match live pre data for post_hr_id={$contact['post_hr_id']} "
. "(client #{$contact['post_client_id']}, branch #{$contact['post_branch_id']}). "
. "Stored (hr_access_control id={$accessId}): pre_hr_id={$storedHr}, pre_client_id={$storedClient}, pre_branch_id={$storedBranch}. "
. "Live pre: pre_hr_id={$liveHr}, pre_client_id={$liveClient}, pre_branch_id={$liveBranch}.",
[
'Option A: Open Client Onboarding → HR Access for this client, select modules for this HR, Save (form must send live pre ids).',
"Option B (recommended): UPDATE hr_access_control SET pre_hr_id={$liveHr}, pre_client_id={$liveClient}, pre_branch_id={$liveBranch} WHERE id={$accessId};",
'Option C: If UI re-save keeps old ids, auto-refresh in saveHrAccessData is commented out — enable it or use Option B.',
'Note: POST login can still work; this mainly affects PRE modules/JWT pre mapping.',
]
);
}
}
@ -244,6 +297,29 @@ class HrLoginDiagnoseController extends AdminController
: (!$flags['CLIENT_ACTIVE'] ? 'CLIENT_INACTIVE' : 'UNKNOWN'))),
];
$pairRows[] = [
'post' => [
'post_hr_id' => (int) $contact['post_hr_id'],
'hr_name' => $contact['hr_name'] ?? '',
'hr_mail' => $contact['hr_mail'] ?? '',
'hr_mobile' => $contact['hr_mobile'] ?? '',
'is_active' => (int) ($contact['is_active'] ?? 0),
'post_branch_id' => (int) ($contact['post_branch_id'] ?? 0),
'branch_name' => $contact['branch_name'] ?? '',
'branch_active' => (int) ($contact['branch_active'] ?? 0),
'pre_branch_id' => $contact['pre_branch_id'] ?? null,
'post_client_id' => (int) ($contact['post_client_id'] ?? 0),
'client_name' => $contact['client_name'] ?? '',
'client_active' => (int) ($contact['client_active'] ?? 0),
'has_access' => ! empty($access),
'token_ready' => $tokenReady,
'hr_active' => (int) ($contact['is_active'] ?? 0) === 1,
],
'pre' => $preMap,
'access_id' => isset($access['id']) ? (int) $access['id'] : null,
'has_token' => $canLoginCard,
];
$contact['hr_found'] = true;
$contact['flags'] = $flags;
}
@ -307,6 +383,26 @@ class HrLoginDiagnoseController extends AdminController
$tokenCards = array_filter($loginCards, static fn ($c) => ! empty($c['has_token']));
$preMatched = array_filter($preMaps, static fn ($m) => ! empty($m['PRE_HR_MATCHED']));
// Group PRE/POST pairs by post client for client-wise display.
$byClientMap = [];
foreach ($pairRows as $pair) {
$cid = (int) ($pair['post']['post_client_id'] ?? 0);
$key = (string) $cid;
if (! isset($byClientMap[$key])) {
$byClientMap[$key] = [
'post_client_id' => $cid,
'client_name' => $pair['post']['client_name'] ?? '',
'client_active' => (int) ($pair['post']['client_active'] ?? 0),
'rows' => [],
];
}
$byClientMap[$key]['rows'][] = $pair;
}
$byClient = array_values($byClientMap);
usort($byClient, static function ($a, $b) {
return strcasecmp((string) ($a['client_name'] ?? ''), (string) ($b['client_name'] ?? ''));
});
return [
'input' => ['email' => $email, 'mobile' => $mobile],
'overall_status' => $overall,
@ -315,6 +411,7 @@ class HrLoginDiagnoseController extends AdminController
'summary' => $summary,
'issues' => $deduped,
'problems' => $deduped,
'by_client' => $byClient,
'post' => [
'contacts' => $postContacts,
'raw_found' => count($postContacts) > 0,
@ -355,6 +452,7 @@ class HrLoginDiagnoseController extends AdminController
'pre_matched' => count($preMatched),
'token_cards' => count($tokenCards),
'access_rows' => count(array_filter($accessRows, static fn ($a) => ! empty($a['id']))),
'client_count' => count($byClient),
'would_return_both' => $anyToken && count($preMatched) > 0,
'winner' => ($anyToken && count($preMatched) > 0) ? 'both' : ($anyToken ? 'post' : (count($preMatched) > 0 ? 'pre' : 'none')),
'reason' => $summary,
@ -514,13 +612,25 @@ class HrLoginDiagnoseController extends AdminController
return $result;
}
private function issue(string $code, string $severity, string $message, string $fix = ''): array
/**
* @param string|array<int, string> $fix
* @return array{code:string,severity:string,message:string,fix:string,fixes:array<int,string>}
*/
private function issue(string $code, string $severity, string $message, $fix = ''): array
{
if (is_array($fix)) {
$fixes = array_values(array_filter(array_map('strval', $fix)));
} else {
$fixes = preg_split("/\r\n|\n|\r/", (string) $fix) ?: [];
$fixes = array_values(array_filter(array_map('trim', $fixes)));
}
return [
'code' => $code,
'severity' => $severity,
'message' => $message,
'fix' => $fix,
'fix' => implode("\n", $fixes),
'fixes' => $fixes,
];
}
}

View File

@ -132,8 +132,46 @@
.badge-high{background:var(--high-badge-bg);color:var(--high-badge-text);}
.code{font-family:var(--mono);font-size:12.5px;color:var(--danger-code);font-weight:600;}
.fix-text{color:var(--text-secondary);}
.fix-list{margin:0;padding-left:18px;}
.fix-list li{margin-bottom:6px;line-height:1.35;}
.fix-list li:last-child{margin-bottom:0;}
.problems-table td.fix-cell{min-width:320px;max-width:480px;}
.problems-table td.msg-cell{min-width:260px;}
.compare-grid{display:grid;grid-template-columns:1fr 1fr;gap:16px;margin-bottom:20px;}
.compare-grid{display:grid;grid-template-columns:1fr 1fr;gap:16px;margin-bottom:12px;}
.client-block{margin-bottom:0;}
.client-block-head{
display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap;
padding:12px 16px;background:#F8F9FA;border-bottom:1px solid var(--border);
font-size:13.5px;font-weight:700;
}
.client-block-head .meta{font-weight:400;color:var(--text-muted);font-size:12.5px;}
.client-block .compare-grid{margin:0;padding:12px;background:#fff;border-radius:0;}
.client-block .compare-grid + .compare-grid{border-top:1px dashed var(--border);}
.branch-label{font-size:12px;color:var(--text-secondary);font-weight:600;margin:0 0 8px;padding:0 4px;}
.client-tabs-wrap{margin-bottom:20px;}
.client-tabs{
display:flex;gap:4px;border-bottom:1px solid var(--border);margin-bottom:0;padding:0 4px;
flex-wrap:nowrap;overflow-x:auto;-webkit-overflow-scrolling:touch;
}
.client-tab{
padding:10px 14px;font-size:12.5px;font-weight:600;color:var(--text-secondary);
cursor:pointer;border-bottom:2px solid transparent;background:none;border-top:none;border-left:none;border-right:none;
font-family:var(--sans);white-space:nowrap;flex:0 0 auto;max-width:260px;overflow:hidden;text-overflow:ellipsis;
}
.client-tab.active{color:var(--teal-dark);border-bottom-color:var(--teal);}
.client-tab:hover:not(.active){color:var(--text-primary);}
.client-tab .badge-count{
display:inline-block;margin-left:6px;font-size:10px;font-weight:700;padding:1px 6px;border-radius:999px;
background:var(--low-badge-bg);color:var(--low-badge-text);
}
.client-tab.active .badge-count{background:var(--teal-tint);color:var(--teal-dark);}
.client-panel{
display:none;border:1px solid var(--border);border-top:none;
border-radius:0 0 var(--radius-md) var(--radius-md);background:#fff;overflow:hidden;
}
.client-panel.active{display:block;}
.compare-card{border:1px solid var(--border);border-radius:var(--radius-md);overflow:hidden;background:#fff;}
.compare-head{
padding:14px 20px;background:#F8F9FA;border-bottom:1px solid var(--border);
@ -246,6 +284,7 @@
$problems = $result['problems'] ?? ($result['issues'] ?? []);
$accessRows = $result['hr_access_control'] ?? [];
$loginSim = $result['login_simulation'] ?? [];
$byClient = $result['by_client'] ?? [];
$postPrimary = $post['primary'] ?? [];
$prePrimary = $pre['primary'] ?? [];
$postFs = $post['filters_summary'] ?? [];
@ -265,87 +304,156 @@
<table class="problems-table">
<tr>
<th style="width:80px;">Severity</th>
<th style="width:220px;">Code</th>
<th style="width:200px;">Code</th>
<th>Message</th>
<th style="width:340px;">Fix</th>
<th style="width:420px;">Fix</th>
</tr>
<?php if (empty($problems)): ?>
<tr><td class="empty" colspan="4">No problems detected.</td></tr>
<?php else: ?>
<?php foreach ($problems as $p): ?>
<?php $sev = strtolower((string) ($p['severity'] ?? 'low')); ?>
<?php
$sev = strtolower((string) ($p['severity'] ?? 'low'));
$fixLines = $p['fixes'] ?? null;
if (! is_array($fixLines) || $fixLines === []) {
$fixLines = preg_split("/\r\n|\n|\r/", (string) ($p['fix'] ?? '')) ?: [];
$fixLines = array_values(array_filter(array_map('trim', $fixLines)));
}
?>
<tr class="sev-<?= esc($sev) ?>">
<td><span class="badge badge-<?= esc($sev) ?>"><?= esc(strtoupper($sev)) ?></span></td>
<td><span class="code"><?= esc($p['code'] ?? '') ?></span></td>
<td><?= esc($p['message'] ?? '') ?></td>
<td class="fix-text"><?= esc($p['fix'] ?? '') ?></td>
<td class="msg-cell"><?= esc($p['message'] ?? '') ?></td>
<td class="fix-text fix-cell">
<?php if (count($fixLines) <= 1): ?>
<?= esc($fixLines[0] ?? '') ?>
<?php else: ?>
<ol class="fix-list">
<?php foreach ($fixLines as $line): ?>
<li><?= esc($line) ?></li>
<?php endforeach; ?>
</ol>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</table>
<div class="compare-grid">
<div class="compare-card">
<div class="compare-head">PRE <span>(enrollment)</span></div>
<?php if (! empty($pre['error'])): ?>
<div class="alert-inline"><?= esc($pre['error']) ?></div>
<?php endif; ?>
<table class="kv-table">
<tr>
<td class="kv-key">Raw found</td>
<td class="kv-val"><?= ! empty($pre['raw_found']) ? $mark(true) . ' Yes' : $mark(false) . ' No' ?></td>
</tr>
<tr>
<td class="kv-key">Eligible (matched)</td>
<td class="kv-val"><?= ! empty($pre['eligible']) ? $mark(true) . ' Yes' : $mark(false) . ' No' ?></td>
</tr>
<tr><td class="kv-key">preDB reachable</td><td class="kv-val"><?= isset($preFs['db_reachable']) ? $mark((bool) $preFs['db_reachable']) : '—' ?></td></tr>
<tr><td class="kv-key">PRE_BRANCH_LINKED</td><td class="kv-val"><?= isset($preFs['PRE_BRANCH_LINKED']) ? $mark((bool) $preFs['PRE_BRANCH_LINKED']) : '—' ?></td></tr>
<tr><td class="kv-key">PRE_HR_MATCHED</td><td class="kv-val"><?= isset($preFs['PRE_HR_MATCHED']) ? $mark((bool) $preFs['PRE_HR_MATCHED']) : '—' ?></td></tr>
<tr><td class="kv-key">PRE_IDS_CONSISTENT</td><td class="kv-val"><?= isset($preFs['PRE_IDS_CONSISTENT']) ? $mark((bool) $preFs['PRE_IDS_CONSISTENT']) : '—' ?></td></tr>
<tr><td class="kv-key">PRE_CLIENT_MAPPED</td><td class="kv-val"><?= isset($preFs['PRE_CLIENT_MAPPED']) ? $mark((bool) $preFs['PRE_CLIENT_MAPPED']) : '—' ?></td></tr>
<tr><td class="kv-key">pre_hr_id</td><td class="kv-val"><?= esc((string) ($prePrimary['pre_hr_id'] ?? '—')) ?></td></tr>
<tr><td class="kv-key">name</td><td class="kv-val"><?= esc((string) ($prePrimary['hr_name'] ?? '—')) ?></td></tr>
<tr><td class="kv-key">email</td><td class="kv-val"><?= esc((string) ($prePrimary['hr_mail'] ?? '—')) ?></td></tr>
<tr><td class="kv-key">mobile</td><td class="kv-val"><?= esc((string) ($prePrimary['hr_mobile'] ?? '—')) ?></td></tr>
<tr><td class="kv-key">client</td><td class="kv-val"><?= esc((string) (($prePrimary['pre_client_name'] ?? '') . ' #' . ($prePrimary['pre_client_id'] ?? ''))) ?></td></tr>
<tr><td class="kv-key">branch</td><td class="kv-val"><?= esc((string) (($prePrimary['pre_branch_name'] ?? '') . ' #' . ($prePrimary['pre_branch_id'] ?? ''))) ?></td></tr>
<tr><td class="kv-key">pre_client_id</td><td class="kv-val"><?= esc((string) ($prePrimary['pre_client_id'] ?? '—')) ?></td></tr>
<tr><td class="kv-key">pre_branch_id</td><td class="kv-val"><?= esc((string) ($prePrimary['pre_branch_id'] ?? '—')) ?></td></tr>
</table>
</div>
<div class="section-title">Client-wise PRE / POST <span>(<?= count($byClient) ?> client<?= count($byClient) === 1 ? '' : 's' ?>)</span></div>
<?php if (! empty($pre['error'])): ?>
<div class="alert-inline" style="margin:0 0 12px;"><?= esc($pre['error']) ?></div>
<?php endif; ?>
<div class="compare-card">
<div class="compare-head">POST <span>(live)</span></div>
<table class="kv-table">
<tr>
<td class="kv-key">Raw found</td>
<td class="kv-val">
<?= ! empty($post['raw_found'])
? $mark(true) . ' Yes (' . count($post['contacts'] ?? []) . ')'
: $mark(false) . ' No' ?>
</td>
</tr>
<tr>
<td class="kv-key">Eligible for login</td>
<td class="kv-val"><?= ! empty($post['eligible']) ? $mark(true) . ' Yes' : $mark(false) . ' No' ?></td>
</tr>
<tr><td class="kv-key">hr active</td><td class="kv-val"><?= isset($postFs['hr_active']) ? $mark((bool) $postFs['hr_active']) : '—' ?></td></tr>
<tr><td class="kv-key">branch active</td><td class="kv-val"><?= isset($postFs['branch_active']) ? $mark((bool) $postFs['branch_active']) : '—' ?></td></tr>
<tr><td class="kv-key">client active</td><td class="kv-val"><?= isset($postFs['client_active']) ? $mark((bool) $postFs['client_active']) : '—' ?></td></tr>
<tr><td class="kv-key">has access row</td><td class="kv-val"><?= isset($postFs['has_access']) ? $mark((bool) $postFs['has_access']) : '—' ?></td></tr>
<tr><td class="kv-key">token ready</td><td class="kv-val"><?= isset($postFs['token_ready']) ? $mark((bool) $postFs['token_ready']) : '—' ?></td></tr>
<tr><td class="kv-key">post_hr_id</td><td class="kv-val"><?= esc((string) ($postPrimary['post_hr_id'] ?? '—')) ?></td></tr>
<tr><td class="kv-key">name</td><td class="kv-val"><?= esc((string) ($postPrimary['hr_name'] ?? '—')) ?></td></tr>
<tr><td class="kv-key">email</td><td class="kv-val"><?= esc((string) ($postPrimary['hr_mail'] ?? '—')) ?></td></tr>
<tr><td class="kv-key">mobile</td><td class="kv-val"><?= esc((string) ($postPrimary['hr_mobile'] ?? '—')) ?></td></tr>
<tr><td class="kv-key">client</td><td class="kv-val"><?= esc((string) (($postPrimary['client_name'] ?? '') . ' #' . ($postPrimary['post_client_id'] ?? ''))) ?></td></tr>
<tr><td class="kv-key">branch</td><td class="kv-val"><?= esc((string) (($postPrimary['branch_name'] ?? '') . ' #' . ($postPrimary['post_branch_id'] ?? ''))) ?></td></tr>
</table>
<?php if (empty($byClient)): ?>
<div class="card" style="padding:16px 20px;margin-bottom:20px;">
<p class="muted" style="padding:0;margin:0;">No client mappings to display.</p>
</div>
</div>
<?php else: ?>
<div class="client-tabs-wrap">
<div class="client-tabs" role="tablist" aria-label="Clients">
<?php foreach ($byClient as $ci => $clientBlock): ?>
<?php
$clientRows = $clientBlock['rows'] ?? [];
$tokenReadyCount = 0;
foreach ($clientRows as $cr) {
if (! empty($cr['has_token'])) {
$tokenReadyCount++;
}
}
$tabLabel = (string) ($clientBlock['client_name'] ?? 'Unknown client');
$tabId = 'client-' . (int) ($clientBlock['post_client_id'] ?? $ci);
?>
<button
type="button"
class="client-tab<?= $ci === 0 ? ' active' : '' ?>"
data-client-panel="<?= esc($tabId) ?>"
title="<?= esc($tabLabel . ' #' . ($clientBlock['post_client_id'] ?? '')) ?>"
>
<?= esc($tabLabel) ?>
<span class="badge-count"><?= (int) $tokenReadyCount ?>/<?= count($clientRows) ?></span>
</button>
<?php endforeach; ?>
</div>
<div class="tabs" role="tablist">
<?php foreach ($byClient as $ci => $clientBlock): ?>
<?php
$clientRows = $clientBlock['rows'] ?? [];
$tokenReadyCount = 0;
foreach ($clientRows as $cr) {
if (! empty($cr['has_token'])) {
$tokenReadyCount++;
}
}
$tabId = 'client-' . (int) ($clientBlock['post_client_id'] ?? $ci);
?>
<div class="client-panel<?= $ci === 0 ? ' active' : '' ?>" id="<?= esc($tabId) ?>">
<div class="client-block">
<div class="client-block-head">
<div>
<?= esc((string) ($clientBlock['client_name'] ?? 'Unknown client')) ?>
<span class="meta">#<?= esc((string) ($clientBlock['post_client_id'] ?? '')) ?></span>
</div>
<div class="meta">
<?= count($clientRows) ?> branch/HR row<?= count($clientRows) === 1 ? '' : 's' ?>
&middot; token ready <?= (int) $tokenReadyCount ?>/<?= count($clientRows) ?>
&middot; client <?= ((int) ($clientBlock['client_active'] ?? 0) === 1) ? 'active' : 'inactive' ?>
</div>
</div>
<?php foreach ($clientRows as $row): ?>
<?php
$rowPost = $row['post'] ?? [];
$rowPre = $row['pre'] ?? [];
?>
<div class="compare-grid">
<div class="compare-card" style="border:none;border-radius:0;">
<div class="compare-head">PRE <span>(enrollment)</span></div>
<p class="branch-label">Branch: <?= esc((string) (($rowPre['pre_branch_name'] ?? '—') . ' #' . ($rowPre['pre_branch_id'] ?? ''))) ?></p>
<table class="kv-table">
<tr><td class="kv-key">PRE_BRANCH_LINKED</td><td class="kv-val"><?= $mark(! empty($rowPre['PRE_BRANCH_LINKED'])) ?></td></tr>
<tr><td class="kv-key">PRE_HR_MATCHED</td><td class="kv-val"><?= $mark(! empty($rowPre['PRE_HR_MATCHED'])) ?></td></tr>
<tr><td class="kv-key">PRE_IDS_CONSISTENT</td><td class="kv-val"><?= $mark(! empty($rowPre['PRE_IDS_CONSISTENT'])) ?></td></tr>
<tr><td class="kv-key">PRE_CLIENT_MAPPED</td><td class="kv-val"><?= $mark(! empty($rowPre['PRE_CLIENT_MAPPED'])) ?></td></tr>
<tr><td class="kv-key">pre_hr_id</td><td class="kv-val"><?= esc((string) ($rowPre['pre_hr_id'] ?? '—')) ?></td></tr>
<tr><td class="kv-key">name</td><td class="kv-val"><?= esc((string) ($rowPre['hr_name'] ?? '—')) ?></td></tr>
<tr><td class="kv-key">email</td><td class="kv-val"><?= esc((string) ($rowPre['hr_mail'] ?? '—')) ?></td></tr>
<tr><td class="kv-key">mobile</td><td class="kv-val"><?= esc((string) ($rowPre['hr_mobile'] ?? '—')) ?></td></tr>
<tr><td class="kv-key">client</td><td class="kv-val"><?= esc((string) (($rowPre['pre_client_name'] ?? '') . ' #' . ($rowPre['pre_client_id'] ?? ''))) ?></td></tr>
<tr><td class="kv-key">branch</td><td class="kv-val"><?= esc((string) (($rowPre['pre_branch_name'] ?? '') . ' #' . ($rowPre['pre_branch_id'] ?? ''))) ?></td></tr>
<tr><td class="kv-key">pre_client_id</td><td class="kv-val"><?= esc((string) ($rowPre['pre_client_id'] ?? '—')) ?></td></tr>
<tr><td class="kv-key">pre_branch_id</td><td class="kv-val"><?= esc((string) ($rowPre['pre_branch_id'] ?? '—')) ?></td></tr>
</table>
</div>
<div class="compare-card" style="border:none;border-radius:0;">
<div class="compare-head">POST <span>(live)</span></div>
<p class="branch-label">Branch: <?= esc((string) (($rowPost['branch_name'] ?? '—') . ' #' . ($rowPost['post_branch_id'] ?? ''))) ?></p>
<table class="kv-table">
<tr><td class="kv-key">Eligible for login</td><td class="kv-val"><?= ! empty($row['has_token']) ? $mark(true) . ' Yes' : $mark(false) . ' No' ?></td></tr>
<tr><td class="kv-key">hr active</td><td class="kv-val"><?= $mark(! empty($rowPost['hr_active'])) ?></td></tr>
<tr><td class="kv-key">branch active</td><td class="kv-val"><?= $mark((int) ($rowPost['branch_active'] ?? 0) === 1) ?></td></tr>
<tr><td class="kv-key">client active</td><td class="kv-val"><?= $mark((int) ($rowPost['client_active'] ?? 0) === 1) ?></td></tr>
<tr><td class="kv-key">has access row</td><td class="kv-val"><?= $mark(! empty($rowPost['has_access'])) ?></td></tr>
<tr><td class="kv-key">token ready</td><td class="kv-val"><?= $mark(! empty($rowPost['token_ready'])) ?></td></tr>
<tr><td class="kv-key">post_hr_id</td><td class="kv-val"><?= esc((string) ($rowPost['post_hr_id'] ?? '—')) ?></td></tr>
<tr><td class="kv-key">name</td><td class="kv-val"><?= esc((string) ($rowPost['hr_name'] ?? '—')) ?></td></tr>
<tr><td class="kv-key">email</td><td class="kv-val"><?= esc((string) ($rowPost['hr_mail'] ?? '—')) ?></td></tr>
<tr><td class="kv-key">mobile</td><td class="kv-val"><?= esc((string) ($rowPost['hr_mobile'] ?? '—')) ?></td></tr>
<tr><td class="kv-key">client</td><td class="kv-val"><?= esc((string) (($rowPost['client_name'] ?? '') . ' #' . ($rowPost['post_client_id'] ?? ''))) ?></td></tr>
<tr><td class="kv-key">branch</td><td class="kv-val"><?= esc((string) (($rowPost['branch_name'] ?? '') . ' #' . ($rowPost['post_branch_id'] ?? ''))) ?></td></tr>
<tr><td class="kv-key">access id</td><td class="kv-val"><?= esc((string) ($row['access_id'] ?? '—')) ?></td></tr>
</table>
</div>
</div>
<?php endforeach; ?>
</div>
</div>
<?php endforeach; ?>
</div>
<?php endif; ?>
<div class="tabs detail-tabs" role="tablist">
<button type="button" class="tab active" data-tab="merge">Summary</button>
<button type="button" class="tab" data-tab="access">Access</button>
<button type="button" class="tab" data-tab="login">Login sim</button>
@ -359,6 +467,7 @@
<tr><td class="cred-key">POST eligible</td><td><?= ! empty($merge['post_eligible']) ? 'Yes' : 'No' ?></td></tr>
<tr><td class="cred-key">PRE eligible</td><td><?= ! empty($merge['pre_eligible']) ? 'Yes' : 'No' ?></td></tr>
<tr><td class="cred-key">POST contacts</td><td class="val-raw"><?= esc((string) ($merge['post_contacts'] ?? 0)) ?></td></tr>
<tr><td class="cred-key">Clients mapped</td><td class="val-raw"><?= esc((string) ($merge['client_count'] ?? count($byClient))) ?></td></tr>
<tr><td class="cred-key">PRE matched</td><td class="val-raw"><?= esc((string) ($merge['pre_matched'] ?? 0)) ?></td></tr>
<tr><td class="cred-key">Access rows with id</td><td class="val-raw"><?= esc((string) ($merge['access_rows'] ?? 0)) ?></td></tr>
<tr><td class="cred-key">Token-ready cards</td><td class="val-raw"><?= esc((string) ($merge['token_cards'] ?? 0)) ?></td></tr>
@ -537,7 +646,20 @@
<script>
(function () {
var tabs = document.querySelectorAll('.tab');
var clientTabs = document.querySelectorAll('.client-tab');
var clientPanels = document.querySelectorAll('.client-panel');
clientTabs.forEach(function (tab) {
tab.addEventListener('click', function () {
var target = tab.getAttribute('data-client-panel');
clientTabs.forEach(function (t) { t.classList.remove('active'); });
clientPanels.forEach(function (p) { p.classList.remove('active'); });
tab.classList.add('active');
var panel = document.getElementById(target);
if (panel) panel.classList.add('active');
});
});
var tabs = document.querySelectorAll('.detail-tabs .tab');
var panels = {
merge: document.getElementById('panel-merge'),
access: document.getElementById('panel-access'),
@ -566,6 +688,16 @@
lines.push('Winner: <?= esc($merge['winner'] ?? '', 'js') ?>');
<?php foreach ($problems as $p): ?>
lines.push('[<?= esc(strtoupper((string) ($p['severity'] ?? '')), 'js') ?>] <?= esc($p['code'] ?? '', 'js') ?>: <?= esc($p['message'] ?? '', 'js') ?>');
<?php
$copyFixes = $p['fixes'] ?? [];
if (! is_array($copyFixes) || $copyFixes === []) {
$copyFixes = preg_split("/\r\n|\n|\r/", (string) ($p['fix'] ?? '')) ?: [];
$copyFixes = array_values(array_filter(array_map('trim', $copyFixes)));
}
foreach ($copyFixes as $fixLine):
?>
lines.push(' - <?= esc($fixLine, 'js') ?>');
<?php endforeach; ?>
<?php endforeach; ?>
navigator.clipboard.writeText(lines.join('\n')).then(function () {
btn.textContent = 'Copied';