prepare($sql); $stmt->execute($params); return $stmt->fetchColumn(); } catch (Throwable $e) { return 0; } } function getRows(PDO $db, string $sql, array $params = []) { try { $stmt = $db->prepare($sql); $stmt->execute($params); return $stmt->fetchAll(PDO::FETCH_ASSOC); } catch (Throwable $e) { return []; } } $dashboardPage = 'dashboard'; $availableWidgets = [ 'kpi_companies', 'kpi_brands', 'kpi_departments', 'kpi_users', 'chart_structure', 'setup_progress', 'quick_actions', 'recent_companies', ]; $defaultLayout = [ 'kpi_companies', 'kpi_brands', 'kpi_departments', 'kpi_users', 'chart_structure', 'setup_progress', 'quick_actions', 'recent_companies', ]; /* * AJAX: save/reset dashboard layout. */ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) { try { if ($_POST['action'] === 'save_dashboard_layout') { $layoutRaw = $_POST['layout'] ?? '[]'; $layout = json_decode($layoutRaw, true); if (!is_array($layout)) { jsonResponse([ 'success' => false, 'message' => 'Invalid dashboard layout.' ]); } $cleanLayout = []; foreach ($layout as $widgetKey) { if (in_array($widgetKey, $availableWidgets, true) && !in_array($widgetKey, $cleanLayout, true)) { $cleanLayout[] = $widgetKey; } } foreach ($defaultLayout as $widgetKey) { if (!in_array($widgetKey, $cleanLayout, true)) { $cleanLayout[] = $widgetKey; } } $layoutJson = json_encode($cleanLayout); $stmt = $db->prepare(" INSERT INTO user_dashboard_layouts ( iduser, page, layout_json, created_at, updated_at ) VALUES ( :iduser, :page, :layout_json, NOW(), NOW() ) ON DUPLICATE KEY UPDATE layout_json = VALUES(layout_json), updated_at = NOW() "); $stmt->execute([ ':iduser' => $iduserlogin, ':page' => $dashboardPage, ':layout_json' => $layoutJson, ]); jsonResponse([ 'success' => true, 'message' => 'Dashboard layout saved.' ]); } if ($_POST['action'] === 'reset_dashboard_layout') { $stmt = $db->prepare(" DELETE FROM user_dashboard_layouts WHERE iduser = :iduser AND page = :page "); $stmt->execute([ ':iduser' => $iduserlogin, ':page' => $dashboardPage, ]); jsonResponse([ 'success' => true, 'message' => 'Dashboard layout reset.' ]); } jsonResponse([ 'success' => false, 'message' => 'Unknown action.' ]); } catch (Throwable $e) { jsonResponse([ 'success' => false, 'message' => $e->getMessage() ]); } } /* * Dashboard counters. */ $totalCompanies = (int) getScalar($db, "SELECT COUNT(*) FROM companies"); $activeCompanies = (int) getScalar($db, "SELECT COUNT(*) FROM companies WHERE status = 'active'"); $totalBrands = (int) getScalar($db, "SELECT COUNT(*) FROM brands"); $activeBrands = (int) getScalar($db, "SELECT COUNT(*) FROM brands WHERE status = 'active'"); $totalDepartments = (int) getScalar($db, "SELECT COUNT(*) FROM departments"); $activeDepartments = (int) getScalar($db, "SELECT COUNT(*) FROM departments WHERE status = 'active'"); $totalCompanyUsers = (int) getScalar($db, "SELECT COUNT(*) FROM company_users"); $activeCompanyUsers = (int) getScalar($db, "SELECT COUNT(*) FROM company_users WHERE status = 'active'"); /* * Recent companies. */ $recentCompanies = getRows($db, " SELECT c.idcompany, c.company_name, c.legal_name, c.status, c.created_at, COUNT(DISTINCT b.idbrand) AS brand_count, COUNT(DISTINCT d.iddepartment) AS department_count, COUNT(DISTINCT cu.idcompanyuser) AS user_count FROM companies c LEFT JOIN brands b ON b.idcompany = c.idcompany LEFT JOIN departments d ON d.idcompany = c.idcompany LEFT JOIN company_users cu ON cu.idcompany = c.idcompany GROUP BY c.idcompany, c.company_name, c.legal_name, c.status, c.created_at ORDER BY c.created_at DESC, c.idcompany DESC LIMIT 8 "); /* * Chart data. */ $companyDistribution = getRows($db, " SELECT c.company_name, COUNT(DISTINCT b.idbrand) AS brands, COUNT(DISTINCT d.iddepartment) AS departments, COUNT(DISTINCT cu.idcompanyuser) AS users FROM companies c LEFT JOIN brands b ON b.idcompany = c.idcompany LEFT JOIN departments d ON d.idcompany = c.idcompany LEFT JOIN company_users cu ON cu.idcompany = c.idcompany GROUP BY c.idcompany, c.company_name ORDER BY c.company_name ASC LIMIT 10 "); $chartCompanyLabels = []; $chartBrands = []; $chartDepartments = []; $chartUsers = []; foreach ($companyDistribution as $row) { $chartCompanyLabels[] = $row['company_name']; $chartBrands[] = (int) $row['brands']; $chartDepartments[] = (int) $row['departments']; $chartUsers[] = (int) $row['users']; } /* * Setup progress. */ $setupItems = [ [ 'label' => 'Companies', 'completed' => $totalCompanies > 0, 'icon' => 'bx bx-buildings', ], [ 'label' => 'Brands', 'completed' => $totalBrands > 0, 'icon' => 'bx bx-purchase-tag-alt', ], [ 'label' => 'Departments', 'completed' => $totalDepartments > 0, 'icon' => 'bx bx-sitemap', ], [ 'label' => 'User access', 'completed' => $totalCompanyUsers > 0, 'icon' => 'bx bx-user-check', ], ]; $completedSetupItems = count(array_filter($setupItems, function ($item) { return $item['completed']; })); $setupProgress = count($setupItems) > 0 ? round(($completedSetupItems / count($setupItems)) * 100) : 0; /* * Load user dashboard layout. */ $userLayout = $defaultLayout; try { $stmt = $db->prepare(" SELECT layout_json FROM user_dashboard_layouts WHERE iduser = :iduser AND page = :page LIMIT 1 "); $stmt->execute([ ':iduser' => $iduserlogin, ':page' => $dashboardPage, ]); $savedLayoutJson = $stmt->fetchColumn(); if ($savedLayoutJson) { $savedLayout = json_decode($savedLayoutJson, true); if (is_array($savedLayout)) { $cleanLayout = []; foreach ($savedLayout as $widgetKey) { if (in_array($widgetKey, $availableWidgets, true) && !in_array($widgetKey, $cleanLayout, true)) { $cleanLayout[] = $widgetKey; } } foreach ($defaultLayout as $widgetKey) { if (!in_array($widgetKey, $cleanLayout, true)) { $cleanLayout[] = $widgetKey; } } $userLayout = $cleanLayout; } } } catch (Throwable $e) { $userLayout = $defaultLayout; } $pageTitle = 'TRFgo Dashboard'; ?> <?= e($pageTitle); ?> - <?= isset($titlewebsite) ? e($titlewebsite) : 'TRFgo'; ?>
TRFgo Platform

Welcome,

Manage companies, brands, departments and user access from one central workspace. TRFgo is the customer-side platform for digital test request forms, sample tracking and laboratory result exchange.

Your Dashboard

Drag widgets using the handle. The layout is saved for your user account.

Companies
active companies
Brands
active brands
Departments
active departments
Company Users
active assignments
Company Structure Overview

Brands, departments and user assignments by company

0): ?>
No chart data available

Create your first company, brand or department to populate this chart.

TRFgo Setup

Initial configuration progress

% completed /
Ready Missing
Recent Companies

Latest configured organizations in TRFgo

0): ?>
Company Status Brands Departments Users Created
Active Suspended Inactive
No companies configured yet

Create your first company to start using TRFgo.

Add first company