get('docs', 'Docs\DocsController::index'); * $routes->get('docs/(:segment)', 'Docs\DocsController::page/$1'); */ class DocsController extends BaseController { // ─── APP-LEVEL DEFAULTS ─────────────────────────────────────────────────── // Change these once here; they propagate to every page automatically. protected string $appName = 'Nhance PAM'; protected string $appVersion = 'v1.1.0'; // ─── SIDEBAR NAV ────────────────────────────────────────────────────────── // Add / remove pages here. 'id' must match the key used in page configs below. protected array $nav = [ [ 'label' => 'Getting Started', 'items' => [ ['id' => 'introduction', 'label' => 'Introduction', 'url' => 'docs/introduction'], ['id' => 'installation', 'label' => 'Installation', 'url' => 'docs/installation'], ['id' => 'configuration', 'label' => 'Configuration', 'url' => 'docs/configuration'], ['id' => 'env-setup', 'label' => 'Environment Setup', 'url' => 'docs/env-setup'], ], ], [ 'label' => 'Architecture', 'items' => [ ['id' => 'project-structure', 'label' => 'Project Structure', 'url' => 'docs/project-structure'], ['id' => 'routing', 'label' => 'Routing', 'url' => 'docs/routing'], ['id' => 'controllers', 'label' => 'Controllers', 'url' => 'docs/controllers'], ['id' => 'models', 'label' => 'Models', 'url' => 'docs/models'], ['id' => 'services', 'label' => 'Services', 'url' => 'docs/services'], ['id' => 'helpers', 'label' => 'Helpers', 'url' => 'docs/helpers'], ], ], [ 'label' => 'Features', 'items' => [ ['id' => 'authentication', 'label' => 'Authentication', 'url' => 'docs/authentication'], ['id' => 'acl', 'label' => 'ACL / Access Control','url' => 'docs/acl'], ['id' => 'input-security', 'label' => 'Input Security Guard','url' => 'docs/input-security'], ['id' => 'file-uploads', 'label' => 'File Upload Guard', 'url' => 'docs/file-uploads'], ['id' => 'background-jobs', 'label' => 'Background Jobs', 'url' => 'docs/background-jobs'], ['id' => 'visit-onboard', 'label' => 'Visit onboard', 'url' => 'docs/visit-onboard'], ['id' => 'visit-offboard', 'label' => 'Visit offboard', 'url' => 'docs/visit-offboard'], ['id' => 'notifications', 'label' => 'Email / Notifications', 'url' => 'docs/notifications'], ['id' => 'api-rate-limiter', 'label' => 'API Rate Limiter', 'url' => 'docs/api-rate-limiter'], ['id' => 'tpa-recon', 'label' => 'TPA Recon', 'url' => 'docs/tpa-recon'], ['id' => 'eb-rack-rate-config', 'label' => 'EB rack rate config', 'url' => 'docs/eb-rack-rate-config'], ['id' => 'eb-rack-rate-calculation', 'label' => 'EB rack rate calculation', 'url' => 'docs/eb-rack-rate-calculation'], ['id' => 'bds-insurer-statement', 'label' => 'BDS Insurer statement', 'url' => 'docs/bds-insurer-statement'], ['id' => 'bds-commission', 'label' => 'BDS commission', 'url' => 'docs/bds-commission'], ['id' => 'inception', 'label' => 'Inception', 'url' => 'docs/inception'], ['id' => 'deletion', 'label' => 'Deletion', 'url' => 'docs/deletion'], ['id' => 'correction', 'label' => 'Correction', 'url' => 'docs/correction'], ['id' => 'si-enhancement', 'label' => 'SI Enhancement', 'url' => 'docs/si-enhancement'], ['id' => 'non-eb-opportunities', 'label' => 'Non-EB Opportunities', 'url' => 'docs/non-eb-opportunities'], ['id' => 'non-eb-claims', 'label' => 'Non-EB Claims', 'url' => 'docs/non-eb-claims'], ], ], [ 'label' => 'API Reference', 'items' => [ ['id' => 'endpoints', 'label' => 'Endpoints', 'url' => 'docs/endpoints'], ['id' => 'request-response', 'label' => 'Request / Response', 'url' => 'docs/request-response'], ['id' => 'error-codes', 'label' => 'Error Codes', 'url' => 'docs/error-codes'], ], ], [ 'label' => 'DevOps', 'items' => [ ['id' => 'deployment', 'label' => 'Deployment', 'url' => 'docs/deployment'], ['id' => 'cicd', 'label' => 'CI/CD Pipeline', 'url' => 'docs/cicd'], ['id' => 's3-cloudfront', 'label' => 'S3 & CloudFront', 'url' => 'docs/s3-cloudfront'], ], ], [ 'label' => 'Reference', 'items' => [ ['id' => 'changelog', 'label' => 'Changelog', 'url' => 'docs/changelog'], ['id' => 'contributing', 'label' => 'Contributing', 'url' => 'docs/contributing'], ], ], ]; // ─── PAGE REGISTRY ──────────────────────────────────────────────────────── // One entry per docs page. // 'view' — path inside app/Views/ (without .php) // Other keys are passed straight to the partials — add/remove as needed. protected array $pages = [ 'introduction' => [ 'view' => 'docs/introduction', 'title' => 'Introduction', 'breadcrumb' => 'Getting Started', 'last_updated' => 'May 2025', 'author' => 'Core Team', 'read_time' => '3 min read', 'toc' => [ ['label' => 'What is MyApp?', 'href' => '#what-is-myapp'], ['label' => 'Tech stack', 'href' => '#tech-stack'], ['label' => 'Conventions', 'href' => '#conventions'], ], 'prev' => null, 'next' => ['label' => 'Installation', 'url' => 'docs/installation'], ], 'installation' => [ 'view' => 'docs/installation', 'title' => 'Installation', 'breadcrumb' => 'Getting Started', 'last_updated' => 'May 2025', 'author' => 'Core Team', 'read_time' => '5 min read', 'toc' => [ ['label' => 'Requirements', 'href' => '#requirements'], ['label' => 'Steps', 'href' => '#steps'], ['label' => 'Clone repo', 'href' => '#clone', 'level' => 'h3'], ['label' => 'Run migrations', 'href' => '#migrate', 'level' => 'h3'], ['label' => 'Configuration', 'href' => '#configuration'], ['label' => 'Sample flowchart', 'href' => '#sample-flowchart'], ], 'prev' => ['label' => 'Introduction', 'url' => 'docs/introduction'], 'next' => ['label' => 'Configuration', 'url' => 'docs/configuration'], ], 'configuration' => [ 'view' => 'docs/configuration', 'title' => 'Configuration', 'breadcrumb' => 'Getting Started', 'last_updated' => 'May 2025', 'author' => 'Core Team', 'read_time' => '4 min read', 'toc' => [ ['label' => 'Environment file', 'href' => '#env-file'], ['label' => 'Database', 'href' => '#database'], ['label' => 'Mail', 'href' => '#mail'], ['label' => 'File storage', 'href' => '#storage'], ], 'prev' => ['label' => 'Installation', 'url' => 'docs/installation'], 'next' => ['label' => 'Environment Setup','url' => 'docs/env-setup'], ], 'acl' => [ 'view' => 'docs/acl', 'title' => 'ACL / Access Control', 'breadcrumb' => 'Features', 'last_updated' => 'May 2026', 'author' => 'Core Team', 'read_time' => '9 min read', 'toc' => [ ['label' => 'Overview', 'href' => '#overview'], ['label' => 'Where it is wired', 'href' => '#where-it-is-wired'], ['label' => 'Rule format', 'href' => '#rule-format'], ['label' => 'Matching behavior', 'href' => '#matching-behavior'], ['label' => 'Auth context', 'href' => '#auth-context'], ['label' => 'Allow and deny flow', 'href' => '#allow-and-deny-flow'], ['label' => 'Developer steps', 'href' => '#developer-steps'], ['label' => 'Examples', 'href' => '#examples'], ['label' => 'Do and don’t', 'href' => '#do-and-dont'], ['label' => 'Common pitfalls', 'href' => '#common-pitfalls'], ], 'prev' => null, 'next' => ['label' => 'Input Security Guard', 'url' => 'docs/input-security'], ], 'input-security' => [ 'view' => 'docs/input-security', 'title' => 'Input Security Guard', 'breadcrumb' => 'Features', 'last_updated' => 'May 2026', 'author' => 'Core Team', 'read_time' => '8 min read', 'toc' => [ ['label' => 'Overview', 'href' => '#overview'], ['label' => 'Where it runs', 'href' => '#where-it-runs'], ['label' => 'What it checks', 'href' => '#what-it-checks'], ['label' => 'Canonicalization', 'href' => '#canonicalization'], ['label' => 'Block behavior', 'href' => '#block-behavior'], ['label' => 'Filter exceptions', 'href' => '#filter-exceptions'], ['label' => 'Developer steps', 'href' => '#developer-steps'], ['label' => 'Common pitfalls', 'href' => '#common-pitfalls'], ], 'prev' => ['label' => 'ACL / Access Control', 'url' => 'docs/acl'], 'next' => ['label' => 'File Upload Guard', 'url' => 'docs/file-uploads'], ], 'file-uploads' => [ 'view' => 'docs/file-uploads', 'title' => 'File Upload Guard', 'breadcrumb' => 'Features', 'last_updated' => 'May 2026', 'author' => 'Core Team', 'read_time' => '8 min read', 'toc' => [ ['label' => 'Overview', 'href' => '#overview'], ['label' => 'When it runs', 'href' => '#when-it-runs'], ['label' => 'Allowed file types', 'href' => '#allowed-file-types'], ['label' => 'Blocked extensions', 'href' => '#blocked-extensions'], ['label' => 'Validation flow', 'href' => '#validation-flow'], ['label' => 'Magic bytes check', 'href' => '#magic-bytes-check'], ['label' => 'Route coverage', 'href' => '#route-coverage'], ['label' => 'Blocked response', 'href' => '#blocked-response'], ['label' => 'Operational notes', 'href' => '#operational-notes'], ], 'prev' => ['label' => 'Input Security Guard', 'url' => 'docs/input-security'], 'next' => ['label' => 'Background Jobs', 'url' => 'docs/background-jobs'], ], 'background-jobs' => [ 'view' => 'docs/background-jobs', 'title' => 'Background Jobs', 'breadcrumb' => 'Features', 'last_updated' => 'May 2026', 'author' => 'Core Team', 'read_time' => '8 min read', 'toc' => [ ['label' => 'Overview', 'href' => '#overview'], ['label' => 'Queueing jobs', 'href' => '#queueing-jobs'], ['label' => 'Worker lifecycle', 'href' => '#worker-lifecycle'], ['label' => 'Handler registry', 'href' => '#handler-registry'], ['label' => 'Sample handlers', 'href' => '#sample-handlers'], ['label' => 'Status lifecycle', 'href' => '#status-lifecycle'], ['label' => 'Failure behavior', 'href' => '#failure-behavior'], ['label' => 'Running via CLI', 'href' => '#running-via-cli'], ['label' => 'Live runner script', 'href' => '#live-runner-script'], ['label' => 'Systemd service', 'href' => '#systemd-service'], ['label' => 'Permissions setup', 'href' => '#permissions-setup'], ['label' => 'Checking status', 'href' => '#checking-status'], ['label' => 'Adding a new handler','href' => '#adding-a-new-handler'], ], 'prev' => ['label' => 'File Upload Guard','url' => 'docs/file-uploads'], 'next' => ['label' => 'Visit onboard', 'url' => 'docs/visit-onboard'], ], 'visit-onboard' => [ 'view' => 'docs/visit-onboard', 'title' => 'Visit onboard', 'breadcrumb' => 'Features', 'last_updated' => 'May 2026', 'author' => 'Core Team', 'read_time' => '8 min read', 'toc' => [ ['label' => 'Overview', 'href' => '#overview'], ['label' => 'Prerequisites', 'href' => '#prerequisites'], ['label' => 'Entry points', 'href' => '#entry-points'], ['label' => 'Eligibility rules', 'href' => '#eligibility-rules'], ['label' => 'Async flow', 'href' => '#async-flow'], ['label' => 'Family payload', 'href' => '#family-payload'], ['label' => 'API integration', 'href' => '#api-integration'], ['label' => 'Response examples', 'href' => '#response-examples'], ['label' => 'Database updates', 'href' => '#database-updates'], ['label' => 'Failure behavior', 'href' => '#failure-behavior'], ['label' => 'Developer steps', 'href' => '#developer-steps'], ['label' => 'Common pitfalls', 'href' => '#common-pitfalls'], ], 'prev' => ['label' => 'Background Jobs', 'url' => 'docs/background-jobs'], 'next' => ['label' => 'Visit offboard', 'url' => 'docs/visit-offboard'], ], 'visit-offboard' => [ 'view' => 'docs/visit-offboard', 'title' => 'Visit offboard', 'breadcrumb' => 'Features', 'last_updated' => 'May 2026', 'author' => 'Core Team', 'read_time' => '6 min read', 'toc' => [ ['label' => 'Overview', 'href' => '#overview'], ['label' => 'When it is queued', 'href' => '#when-it-is-queued'], ['label' => 'visitOffBoard()', 'href' => '#visit-off-board-api'], ['label' => 'updateVisitoffboardStatus()', 'href' => '#update-status'], ['label' => 'Manual test route', 'href' => '#manual-test-route'], ['label' => 'Developer steps', 'href' => '#developer-steps'], ['label' => 'Common pitfalls', 'href' => '#common-pitfalls'], ], 'prev' => ['label' => 'Visit onboard', 'url' => 'docs/visit-onboard'], 'next' => ['label' => 'Deployment', 'url' => 'docs/deployment'], ], 'deployment' => [ 'view' => 'docs/deployment', 'title' => 'Deployment', 'breadcrumb' => 'DevOps', 'last_updated' => 'May 2026', 'author' => 'Core Team', 'read_time' => '8 min read', 'toc' => [ ['label' => 'Overview', 'href' => '#overview'], ['label' => 'Dev cPanel flow', 'href' => '#dev-cpanel-flow', 'tag' => 'DEV'], ['label' => 'cPanel deploy scripts', 'href' => '#cpanel-deploy-scripts', 'tag' => 'DEV'], ['label' => 'Branch promotion flow', 'href' => '#branch-promotion-flow', 'tag' => 'UAT/LIVE'], ['label' => 'Auto merge script', 'href' => '#auto-merge-script', 'tag' => 'UAT/LIVE'], ['label' => 'Script usage', 'href' => '#script-usage', 'tag' => 'UAT/LIVE'], ['label' => 'Log output', 'href' => '#log-output', 'tag' => 'UAT/LIVE'], ['label' => 'Operational notes', 'href' => '#operational-notes', 'tag' => 'UAT/LIVE'], ['label' => 'Code move scripts', 'href' => '#code-move-scripts', 'tag' => 'UAT/LIVE'], ['label' => 'Manual fallback', 'href' => '#manual-fallback', 'tag' => 'DEV'], ], 'prev' => ['label' => 'Visit offboard', 'url' => 'docs/visit-offboard'], 'next' => ['label' => 'CI/CD Pipeline', 'url' => 'docs/cicd'], ], 'cicd' => [ 'view' => 'docs/cicd', 'title' => 'CI/CD Pipeline', 'breadcrumb' => 'DevOps', 'last_updated' => 'May 2026', 'author' => 'Core Team', 'read_time' => '4 min read', 'toc' => [ ['label' => 'Overview', 'href' => '#overview'], ['label' => 'Dev pipeline', 'href' => '#dev-pipeline', 'tag' => 'DEV'], ['label' => 'UAT and live flow', 'href' => '#uat-live-flow', 'tag' => 'UAT/LIVE'], ['label' => 'Related docs', 'href' => '#related-docs'], ], 'prev' => ['label' => 'Deployment', 'url' => 'docs/deployment'], 'next' => ['label' => 'S3 & CloudFront', 'url' => 'docs/s3-cloudfront'], ], 's3-cloudfront' => [ 'view' => 'docs/s3-cloudfront', 'title' => 'S3 & CloudFront', 'breadcrumb' => 'DevOps', 'last_updated' => 'May 2026', 'author' => 'Core Team', 'read_time' => '7 min read', 'toc' => [ ['label' => 'Overview', 'href' => '#overview'], ['label' => 'Prerequisites', 'href' => '#prerequisites'], ['label' => 'Selection flow', 'href' => '#selection-flow'], ['label' => 'Deployment steps', 'href' => '#deployment-steps'], ['label' => 'Invalidation polling', 'href' => '#invalidation-polling'], ['label' => 'Operational notes', 'href' => '#operational-notes'], ['label' => 'Full script', 'href' => '#full-script'], ], 'prev' => ['label' => 'CI/CD Pipeline', 'url' => 'docs/cicd'], 'next' => ['label' => 'Changelog', 'url' => 'docs/changelog'], ], 'api-rate-limiter' => [ 'view' => 'docs/api-rate-limiter', 'title' => 'API Rate Limiter', 'breadcrumb' => 'Features', 'last_updated' => 'May 2026', 'author' => 'Core Team', 'read_time' => '10 min read', 'toc' => [ ['label' => 'Overview', 'href' => '#overview'], ['label' => 'Core service', 'href' => '#core-service'], ['label' => 'Auth API filter', 'href' => '#auth-api-filter'], ['label' => 'JWT API filter', 'href' => '#jwt-api-filter'], ['label' => 'Configuration', 'href' => '#configuration'], ['label' => 'Change block counts and durations', 'href' => '#change-block-count-duration'], ['label' => 'Manual unblock samples', 'href' => '#manual-unblock-samples'], ['label' => 'Cache TTL and auto-release', 'href' => '#cache-ttl-auto-release'], ['label' => 'DB reconciliation (cron)', 'href' => '#db-reconciliation-cron'], ['label' => 'HTTP responses', 'href' => '#http-responses'], ['label' => 'Wiring routes', 'href' => '#wiring-routes'], ['label' => 'Blocked list URL', 'href' => '#blocked-list-url'], ['label' => 'Smoke test command', 'href' => '#smoke-test-command'], ['label' => 'Operational notes', 'href' => '#operational-notes'], ], 'prev' => ['label' => 'Email / Notifications', 'url' => 'docs/notifications'], 'next' => ['label' => 'TPA Recon', 'url' => 'docs/tpa-recon'], ], 'tpa-recon' => [ 'view' => 'docs/tpa-recon', 'title' => 'TPA Recon', 'breadcrumb' => 'Features', 'last_updated' => 'May 2026', 'author' => 'Core Team', 'read_time' => '18 min read', 'toc' => [ ['label' => 'What this is', 'href' => '#what-this-is'], ['label' => 'Glossary', 'href' => '#glossary'], ['label' => 'Key files and routes', 'href' => '#key-files-routes'], ['label' => 'End-to-end flow', 'href' => '#end-to-end-flow'], ['label' => 'Variation report', 'href' => '#variation-report'], ['label' => 'Classifying rec_type', 'href' => '#classifying-rec-type'], ['label' => 'Linking ref column', 'href' => '#linking-ref-column'], ['label' => 'Proceed next step', 'href' => '#proceed-next-step'], ['label' => 'Not in Nhance → inception', 'href' => '#not-in-nhance-inception'], ['label' => 'Need to review → DB sync', 'href' => '#need-to-review-sync'], ['label' => 'Deletion initialization', 'href' => '#deletion-initialization'], ['label' => 'Background jobs chain', 'href' => '#background-jobs-chain'], ['label' => 'New developer checklist', 'href' => '#new-developer-checklist'], ], 'prev' => ['label' => 'API Rate Limiter', 'url' => 'docs/api-rate-limiter'], 'next' => ['label' => 'EB rack rate config', 'url' => 'docs/eb-rack-rate-config'], ], 'eb-rack-rate-config' => [ 'view' => 'docs/eb-rack-rate-config', 'title' => 'EB rack rate config', 'breadcrumb' => 'Features', 'last_updated' => 'May 2026', 'author' => 'Core Team', 'read_time' => '20 min read', 'toc' => [ ['label' => 'Purpose', 'href' => '#purpose'], ['label' => 'Premium calculation (all)', 'href' => '#premium-calculation-modes'], ['label' => 'Applicable family members', 'href' => '#applicable-family-members'], ['label' => 'Member radios (Yes / No / …)', 'href' => '#applicable-members-radio-meanings'], ['label' => 'Rack matching vs family', 'href' => '#applicable-members-matching'], ['label' => 'Rack vs family flowchart', 'href' => '#rack-rate-family-matching-flowchart'], ['label' => 'How to configure a rack rate', 'href' => '#how-to-configure-rack-rate'], ['label' => 'Where it appears in the UI', 'href' => '#ui-entry'], ['label' => 'Load grid data (backend)', 'href' => '#getpolicy-grid-data'], ['label' => 'Save rack rate (backend)', 'href' => '#create-premium'], ['label' => 'Frontend flow', 'href' => '#frontend-flow'], ['label' => 'Grid IDs and storage', 'href' => '#grid-ids-storage'], ['label' => 'Grid types (1–13)', 'href' => '#grid-types-1-13'], ['label' => 'Excel paste path', 'href' => '#excel-paste'], ['label' => 'Related routes', 'href' => '#related-routes'], ], 'prev' => ['label' => 'TPA Recon', 'url' => 'docs/tpa-recon'], 'next' => ['label' => 'EB rack rate calculation', 'url' => 'docs/eb-rack-rate-calculation'], ], 'eb-rack-rate-calculation' => [ 'view' => 'docs/eb-rack-rate-calculation', 'title' => 'EB rack rate calculation', 'breadcrumb' => 'Features', 'last_updated' => 'May 2026', 'author' => 'Core Team', 'read_time' => '18 min read', 'toc' => [ ['label' => 'Scope and entry point', 'href' => '#scope'], ['label' => 'Data loaded before premium', 'href' => '#inputs'], ['label' => 'Excel row shape (numeric columns)', 'href' => '#excel-columns'], ['label' => 'Flow: employeesOnboardPreprocess', 'href' => '#flow-preprocess'], ['label' => 'Flow: calculate_premium_new', 'href' => '#flow-calculate-premium'], ['label' => 'group_slab_rates_basedon_name', 'href' => '#flow-group-slabs'], ['label' => 'get_familiy_composition', 'href' => '#flow-family-composition'], ['label' => 'compare_incoming…_slab', 'href' => '#flow-compare-slab'], ['label' => 'get_applicable_familiy_members', 'href' => '#flow-applicable-members'], ['label' => 'Per-member transform and gate', 'href' => '#flow-per-member'], ['label' => 'premium_calculation_manager', 'href' => '#flow-premium-manager'], ['label' => 'Dependent addition extras', 'href' => '#dependent-addition'], ['label' => 'Failure: zero successful families', 'href' => '#failure-modes'], ['label' => 'Related', 'href' => '#related'], ], 'prev' => ['label' => 'EB rack rate config', 'url' => 'docs/eb-rack-rate-config'], 'next' => ['label' => 'BDS Insurer statement', 'url' => 'docs/bds-insurer-statement'], ], 'bds-insurer-statement' => [ 'view' => 'docs/bds-insurer-statement', 'title' => 'BDS Insurer statement', 'breadcrumb' => 'Features', 'last_updated' => 'May 2026', 'author' => 'Core Team', 'read_time' => '12 min read', 'toc' => [ ['label' => 'Overview', 'href' => '#overview'], ['label' => 'Row validation logic', 'href' => '#row-validation-flowchart'], ['label' => 'Key files and routes', 'href' => '#key-files-routes'], ['label' => 'Statement list UI', 'href' => '#statement-list-ui'], ['label' => 'uploadInsurerStatement','href' => '#upload-flow'], ['label' => 'validateInsurerStatement', 'href' => '#validate-flow'], ['label' => 'Validation steps', 'href' => '#validate-steps', 'level' => 'h3'], ['label' => 'Validation error codes', 'href' => '#validation-errors', 'level' => 'h3'], ['label' => 'updateInsurerStatement', 'href' => '#update-flow'], ['label' => 'Excel column mapping', 'href' => '#excel-columns'], ['label' => 'NHance source query', 'href' => '#nhance-source-query'], ['label' => 'Invoice and delete', 'href' => '#invoice-and-delete'], ['label' => 'Developer steps', 'href' => '#developer-steps'], ['label' => 'Common pitfalls', 'href' => '#common-pitfalls'], ['label' => 'Related BDS features', 'href' => '#related-bds'], ], 'prev' => ['label' => 'EB rack rate calculation', 'url' => 'docs/eb-rack-rate-calculation'], 'next' => ['label' => 'BDS commission', 'url' => 'docs/bds-commission'], ], 'bds-commission' => [ 'view' => 'docs/bds-commission', 'title' => 'BDS commission', 'breadcrumb' => 'Features', 'last_updated' => 'May 2026', 'author' => 'Core Team', 'read_time' => '10 min read', 'toc' => [ ['label' => 'Overview', 'href' => '#overview'], ['label' => 'Key files', 'href' => '#key-files'], ['label' => 'Routes', 'href' => '#routes'], ['label' => 'Commission file format', 'href' => '#commission-file-format'], ['label' => 'Required columns', 'href' => '#required-columns', 'level' => 'h3'], ['label' => 'Sample rows', 'href' => '#commission-file-samples', 'level' => 'h3'], ['label' => 'Rule upload', 'href' => '#upload-flow'], ['label' => 'Rules editor', 'href' => '#rules-editor'], ['label' => 'Rule JSON shape', 'href' => '#rule-json'], ['label' => 'Calculation API', 'href' => '#calculation-api'], ['label' => 'Developer steps', 'href' => '#developer-steps'], ['label' => 'Common pitfalls', 'href' => '#pitfalls'], ], 'prev' => ['label' => 'BDS Insurer statement', 'url' => 'docs/bds-insurer-statement'], 'next' => ['label' => 'Inception', 'url' => 'docs/inception'], ], 'inception' => [ 'view' => 'docs/inception', 'title' => 'Inception', 'breadcrumb' => 'Features', 'last_updated' => 'May 2026', 'author' => 'Core Team', 'read_time' => '8 min read', 'toc' => [ ['label' => 'Overview', 'href' => '#overview'], ['label' => 'Key files and routes', 'href' => '#key-files-routes'], ['label' => 'Sync vs jobs', 'href' => '#sync-vs-jobs'], ['label' => 'Format validation', 'href' => '#format-validation'], ['label' => 'Format error codes', 'href' => '#format-errors', 'level' => 'h3'], ['label' => 'reason JSON', 'href' => '#reason-json', 'level' => 'h3'], ['label' => 'Data validation', 'href' => '#data-validation'], ['label' => 'Data error codes', 'href' => '#data-errors', 'level' => 'h3'], ['label' => 'Onboard preprocess', 'href' => '#preprocess'], ['label' => 'Excel columns', 'href' => '#excel-columns'], ['label' => 'Family row example', 'href' => '#family-example'], ['label' => 'Developer steps', 'href' => '#developer-steps'], ['label' => 'Common pitfalls', 'href' => '#pitfalls'], ], 'prev' => ['label' => 'BDS commission', 'url' => 'docs/bds-commission'], 'next' => ['label' => 'Deletion', 'url' => 'docs/deletion'], ], 'deletion' => [ 'view' => 'docs/deletion', 'title' => 'Deletion', 'breadcrumb' => 'Features', 'last_updated' => 'May 2026', 'author' => 'Core Team', 'read_time' => '7 min read', 'toc' => [ ['label' => 'Overview', 'href' => '#overview'], ['label' => 'Key files and routes', 'href' => '#key-files-routes'], ['label' => 'Sync vs jobs', 'href' => '#sync-vs-jobs'], ['label' => 'Format validation', 'href' => '#format-validation'], ['label' => 'Format error codes', 'href' => '#format-errors', 'level' => 'h3'], ['label' => 'Data validation', 'href' => '#data-validation'], ['label' => 'employeeDisembark', 'href' => '#disembark'], ['label' => 'TPA auto-deletion', 'href' => '#tpa-auto'], ['label' => 'Excel columns', 'href' => '#excel-columns'], ['label' => 'Row examples', 'href' => '#row-example'], ['label' => 'Developer steps', 'href' => '#developer-steps'], ['label' => 'Common pitfalls', 'href' => '#pitfalls'], ], 'prev' => ['label' => 'Inception', 'url' => 'docs/inception'], 'next' => ['label' => 'Correction', 'url' => 'docs/correction'], ], 'correction' => [ 'view' => 'docs/correction', 'title' => 'Correction', 'breadcrumb' => 'Features', 'last_updated' => 'May 2026', 'author' => 'Core Team', 'read_time' => '7 min read', 'toc' => [ ['label' => 'Overview', 'href' => '#overview'], ['label' => 'Key files and routes', 'href' => '#key-files-routes'], ['label' => 'Sync vs jobs', 'href' => '#sync-vs-jobs'], ['label' => 'Format validation', 'href' => '#format-validation'], ['label' => 'Format error codes', 'href' => '#format-errors', 'level' => 'h3'], ['label' => 'Data validation', 'href' => '#data-validation'], ['label' => 'employeesCorrectionProcess', 'href' => '#correction-process'], ['label' => 'Excel columns', 'href' => '#excel-columns'], ['label' => 'Row examples', 'href' => '#row-example'], ['label' => 'Developer steps', 'href' => '#developer-steps'], ['label' => 'Common pitfalls', 'href' => '#pitfalls'], ], 'prev' => ['label' => 'Deletion', 'url' => 'docs/deletion'], 'next' => ['label' => 'SI Enhancement', 'url' => 'docs/si-enhancement'], ], 'si-enhancement' => [ 'view' => 'docs/si-enhancement', 'title' => 'SI Enhancement', 'breadcrumb' => 'Features', 'last_updated' => 'May 2026', 'author' => 'Core Team', 'read_time' => '7 min read', 'toc' => [ ['label' => 'Overview', 'href' => '#overview'], ['label' => 'Key files and routes', 'href' => '#key-files-routes'], ['label' => 'Sync vs jobs', 'href' => '#sync-vs-jobs'], ['label' => 'Format validation', 'href' => '#format-validation'], ['label' => 'Format error codes', 'href' => '#format-errors', 'level' => 'h3'], ['label' => 'Data validation', 'href' => '#data-validation'], ['label' => 'employeesSIEnhanceProcess', 'href' => '#si-process'], ['label' => 'Excel columns', 'href' => '#excel-columns'], ['label' => 'Row examples', 'href' => '#row-example'], ['label' => 'Developer steps', 'href' => '#developer-steps'], ['label' => 'Common pitfalls', 'href' => '#pitfalls'], ], 'prev' => ['label' => 'Correction', 'url' => 'docs/correction'], 'next' => ['label' => 'Non-EB Opportunities', 'url' => 'docs/non-eb-opportunities'], ], 'non-eb-opportunities' => [ 'view' => 'docs/non-eb-opportunities', 'title' => 'Non-EB Opportunities', 'breadcrumb' => 'Features', 'last_updated' => 'May 2026', 'author' => 'Core Team', 'read_time' => '18 min read', 'toc' => [ ['label' => 'End-to-end flow', 'href' => '#overview'], ['label' => 'Key files', 'href' => '#key-files'], ['label' => 'Google Sheet config', 'href' => '#google-sheet-config'], ['label' => 'App ↔ Drive flow', 'href' => '#gsheet-app-drive-flow', 'level' => 'h3'], ['label' => 'Before RFQ / QCR', 'href' => '#gsheet-prerequisites', 'level' => 'h3'], ['label' => 'Sheet ID per product', 'href' => '#gsheet-product-config', 'level' => 'h3'], ['label' => 'App-level config', 'href' => '#gsheet-app-config', 'level' => 'h3'], ['label' => 'Create opportunity', 'href' => '#create-edit'], ['label' => 'Edit flow', 'href' => '#edit-flow'], ['label' => 'List table actions', 'href' => '#list-actions'], ['label' => 'Action endpoint map', 'href' => '#action-map', 'level' => 'h3'], ['label' => 'RFQ', 'href' => '#step-rfq'], ['label' => 'QCR', 'href' => '#step-qcr'], ['label' => 'Mail actions', 'href' => '#step-mails'], ['label' => 'Placement', 'href' => '#step-placement'], ['label' => 'Status lifecycle', 'href' => '#status-lifecycle'], ['label' => 'Controller reference', 'href' => '#controller-reference'], ['label' => 'Developer checklist', 'href' => '#developer-steps'], ['label' => 'Common pitfalls', 'href' => '#pitfalls'], ], 'prev' => ['label' => 'SI Enhancement', 'url' => 'docs/si-enhancement'], 'next' => ['label' => 'Non-EB Claims', 'url' => 'docs/non-eb-claims'], ], 'non-eb-claims' => [ 'view' => 'docs/non-eb-claims', 'title' => 'Non-EB Claims', 'breadcrumb' => 'Features', 'last_updated' => 'May 2026', 'author' => 'Core Team', 'read_time' => '16 min read', 'toc' => [ ['label' => 'End-to-end flow', 'href' => '#overview'], ['label' => 'Why policy type 50', 'href' => '#policy-type-50-default'], ['label' => 'Design assumption', 'href' => '#policy-type-50-assumption', 'level' => 'h3'], ['label' => '50 vs selected policy', 'href' => '#policy-type-50-vs-selected-policy', 'level' => 'h3'], ['label' => 'Where 50 is hardcoded', 'href' => '#policy-type-50-where-hardcoded', 'level' => 'h3'], ['label' => 'Different status set', 'href' => '#policy-type-50-future', 'level' => 'h3'], ['label' => 'Key files and routes', 'href' => '#key-files'], ['label' => 'Route map', 'href' => '#route-map', 'level' => 'h3'], ['label' => 'Access control', 'href' => '#access'], ['label' => 'List and filter', 'href' => '#list-flow'], ['label' => 'Create and edit', 'href' => '#create-edit'], ['label' => 'Status-driven sections', 'href' => '#status-sections'], ['label' => 'Auto-mail', 'href' => '#auto-mail'], ['label' => 'Mail template CRUD', 'href' => '#mail-template-crud'], ['label' => 'Template fields', 'href' => '#template-fields', 'level' => 'h3'], ['label' => 'Template actions', 'href' => '#template-actions', 'level' => 'h3'], ['label' => 'Notes', 'href' => '#notes'], ['label' => 'Documents', 'href' => '#documents'], ['label' => 'Reports', 'href' => '#reports'], ['label' => 'Data model', 'href' => '#data-model'], ['label' => 'Controller reference', 'href' => '#controller-reference'], ['label' => 'Developer checklist', 'href' => '#developer-steps'], ['label' => 'Common pitfalls', 'href' => '#pitfalls'], ], 'prev' => ['label' => 'Non-EB Opportunities', 'url' => 'docs/non-eb-opportunities'], 'next' => ['label' => 'Endpoints', 'url' => 'docs/endpoints'], ], // ── Add more pages here following the same pattern ────────────────── ]; // ========================================================================= // PUBLIC ROUTES // ========================================================================= /** * GET /docs → redirect to first page (introduction) */ public function index(): \CodeIgniter\HTTP\RedirectResponse { return redirect()->to(base_url('docs/introduction')); } /** * GET /docs/(:segment) * * Looks up the slug in $pages, assembles layout partials, and returns HTML. * The individual view only contains its own content — no layout boilerplate. */ public function page(string $slug): string { // 1. Resolve page config $config = $this->getPageConfig($slug); // 2. Render the inner content view (must exist) $content = $this->renderContentView($config['view'], $config); // 3. Assemble full layout and return return $this->renderDocPage($config, $content); } // ========================================================================= // CORE LAYOUT ASSEMBLER // ========================================================================= /** * Wraps any content string with the full docs layout (header, sidebar, * main open/close, footer) and returns the complete HTML page. * * @param array $config Page config entry from $this->pages * @param string $content Rendered HTML from the content-only view */ protected function renderDocPage(array $config, string $content): string { $sharedLayout = [ 'app_name' => $this->appName, 'app_version' => $this->appVersion, 'nav' => $this->nav, // sidebar needs the full nav ]; $header = view('docs/partials/docs_header', array_merge($sharedLayout, [ 'doc_title' => $config['title'], ])); $sidebar = view('docs/partials/docs_sidebar', array_merge($sharedLayout, [ 'active_page' => $config['id'], ])); $mainOpen = view('docs/partials/docs_main_open', [ 'doc_title' => $config['title'], 'breadcrumb' => $config['breadcrumb'] ?? '', 'last_updated' => $config['last_updated'] ?? '', 'author' => $config['author'] ?? '', 'read_time' => $config['read_time'] ?? '', 'toc' => $config['toc'] ?? [], ]); $mainClose = view('docs/partials/docs_main_close', [ 'toc' => $config['toc'] ?? [], 'prev_label' => $config['prev']['label'] ?? '', 'prev_url' => $config['prev']['url'] ?? '', 'next_label' => $config['next']['label'] ?? '', 'next_url' => $config['next']['url'] ?? '', ]); $footer = view('docs/partials/docs_footer', [ 'app_name' => $this->appName, ]); return $header . $sidebar . $mainOpen . $content . $mainClose . $footer; } // ========================================================================= // HELPERS // ========================================================================= /** * Resolve a URL slug to its page config array. * Injects 'id' (the slug) so partials can use it without repeating it. * * @throws PageNotFoundException */ protected function getPageConfig(string $slug): array { if (! array_key_exists($slug, $this->pages)) { throw new PageNotFoundException("Docs page not found: {$slug}"); } return array_merge($this->pages[$slug], ['id' => $slug]); } /** * Render the content-only view file. * Passes the full config as view data so the content view * can access $title, $toc, etc. if needed. * * @throws PageNotFoundException if the view file does not exist on disk */ protected function renderContentView(string $viewPath, array $data = []): string { $fullPath = APPPATH . 'Views/' . $viewPath . '.php'; if (! file_exists($fullPath)) { throw new PageNotFoundException("Docs view file not found: {$viewPath}.php"); } // Remove 'view' key so it doesn't shadow anything inside the view unset($data['view']); return view($viewPath, $data); } }