Files
trfgo/public/userarea/dashboard-admin.php
T
2026-06-15 16:10:44 +02:00

1419 lines
53 KiB
PHP

<?php include('include/headscript.php'); ?>
<?php
/*
* TRFgo Dashboard
* Draggable user widgets with persistent layout.
*/
function e($value)
{
return htmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8');
}
function jsonResponse(array $payload): void
{
header('Content-Type: application/json');
echo json_encode($payload);
exit;
}
function getScalar(PDO $db, string $sql, array $params = [])
{
try {
$stmt = $db->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';
?>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" href="assets/images/favicon-32x32.png" type="image/png" />
<?php include('cssinclude.php'); ?>
<title><?= e($pageTitle); ?> - <?= isset($titlewebsite) ? e($titlewebsite) : 'TRFgo'; ?></title>
<style>
:root {
--trfgo-primary: #2563eb;
--trfgo-primary-dark: #1e40af;
--trfgo-secondary: #0f172a;
--trfgo-soft-blue: #eff6ff;
--trfgo-soft-green: #ecfdf5;
--trfgo-soft-orange: #fff7ed;
--trfgo-soft-purple: #f5f3ff;
--trfgo-border: #e5e7eb;
--trfgo-muted: #64748b;
}
.dashboard-hero {
position: relative;
overflow: hidden;
border-radius: 20px;
background:
radial-gradient(circle at top right, rgba(59, 130, 246, 0.35), transparent 32%),
linear-gradient(135deg, #0f172a 0%, #1d4ed8 58%, #38bdf8 100%);
color: #fff;
padding: 28px;
min-height: 210px;
box-shadow: 0 18px 45px rgba(15, 23, 42, 0.22);
}
.dashboard-hero h1,
.dashboard-hero h2,
.dashboard-hero h3,
.dashboard-hero h4,
.dashboard-hero h5,
.dashboard-hero h6 {
color: #ffffff;
}
.dashboard-hero::before {
content: "";
position: absolute;
width: 260px;
height: 260px;
border-radius: 50%;
background: rgba(255, 255, 255, 0.09);
right: -70px;
bottom: -100px;
}
.dashboard-hero::after {
content: "";
position: absolute;
width: 130px;
height: 130px;
border-radius: 50%;
background: rgba(255, 255, 255, 0.08);
right: 210px;
top: -50px;
}
.dashboard-hero-content {
position: relative;
z-index: 2;
}
.dashboard-eyebrow {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 6px 12px;
border-radius: 999px;
background: rgba(255, 255, 255, 0.16);
color: rgba(255, 255, 255, 0.92);
font-size: 12px;
font-weight: 700;
letter-spacing: 0.04em;
text-transform: uppercase;
margin-bottom: 14px;
}
.dashboard-title {
font-size: 32px;
font-weight: 800;
letter-spacing: -0.03em;
margin-bottom: 8px;
color: #ffffff;
}
.dashboard-subtitle {
max-width: 780px;
color: rgba(255, 255, 255, 0.82);
font-size: 15px;
line-height: 1.6;
margin-bottom: 22px;
}
.hero-actions {
display: flex;
flex-wrap: wrap;
gap: 10px;
}
.btn-hero-light {
background: #fff;
color: #1d4ed8;
border: 0;
border-radius: 12px;
padding: 10px 16px;
font-weight: 700;
display: inline-flex;
align-items: center;
gap: 8px;
box-shadow: 0 10px 24px rgba(15, 23, 42, 0.16);
text-decoration: none;
}
.btn-hero-outline {
background: rgba(255, 255, 255, 0.12);
color: #fff;
border: 1px solid rgba(255, 255, 255, 0.32);
border-radius: 12px;
padding: 10px 16px;
font-weight: 700;
display: inline-flex;
align-items: center;
gap: 8px;
text-decoration: none;
}
.dashboard-toolbar {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
flex-wrap: wrap;
margin-bottom: 14px;
}
.dashboard-toolbar-title {
font-weight: 800;
color: #0f172a;
font-size: 18px;
margin-bottom: 2px;
}
.dashboard-toolbar-subtitle {
color: var(--trfgo-muted);
font-size: 13px;
margin: 0;
}
.dashboard-widget {
margin-bottom: 24px;
}
.dashboard-widget.sortable-ghost {
opacity: 0.35;
}
.dashboard-widget.sortable-chosen .widget-card {
box-shadow: 0 22px 60px rgba(37, 99, 235, 0.20);
}
.widget-card {
border: 0;
border-radius: 18px;
box-shadow: 0 10px 30px rgba(15, 23, 42, 0.06);
overflow: hidden;
height: 100%;
transition: all 0.18s ease;
}
.widget-card:hover {
transform: translateY(-2px);
box-shadow: 0 16px 40px rgba(15, 23, 42, 0.10);
}
.widget-header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
}
.widget-title {
color: #0f172a;
font-size: 18px;
font-weight: 800;
margin-bottom: 4px;
}
.widget-subtitle {
color: var(--trfgo-muted);
font-size: 13px;
margin-bottom: 0;
}
.drag-handle {
width: 34px;
height: 34px;
border-radius: 11px;
display: inline-flex;
align-items: center;
justify-content: center;
background: #f8fafc;
border: 1px solid #e5e7eb;
color: #64748b;
cursor: grab;
flex: 0 0 auto;
}
.drag-handle:active {
cursor: grabbing;
}
.stat-icon {
width: 48px;
height: 48px;
border-radius: 14px;
display: inline-flex;
align-items: center;
justify-content: center;
font-size: 25px;
}
.stat-icon.blue {
background: var(--trfgo-soft-blue);
color: #2563eb;
}
.stat-icon.green {
background: var(--trfgo-soft-green);
color: #059669;
}
.stat-icon.orange {
background: var(--trfgo-soft-orange);
color: #ea580c;
}
.stat-icon.purple {
background: var(--trfgo-soft-purple);
color: #7c3aed;
}
.stat-label {
color: var(--trfgo-muted);
font-size: 13px;
font-weight: 700;
margin-bottom: 4px;
}
.stat-value {
color: #0f172a;
font-size: 28px;
font-weight: 800;
line-height: 1.1;
}
.stat-caption {
color: var(--trfgo-muted);
font-size: 12px;
margin-top: 8px;
}
.quick-action {
display: flex;
align-items: center;
gap: 14px;
padding: 15px;
border: 1px solid var(--trfgo-border);
border-radius: 16px;
color: #0f172a;
text-decoration: none;
transition: all 0.18s ease;
background: #fff;
}
.quick-action:hover {
color: #1d4ed8;
border-color: rgba(37, 99, 235, 0.25);
background: #f8fbff;
transform: translateX(3px);
}
.quick-action-icon {
width: 42px;
height: 42px;
border-radius: 13px;
background: #eff6ff;
color: #2563eb;
display: inline-flex;
align-items: center;
justify-content: center;
font-size: 22px;
flex: 0 0 auto;
}
.quick-action-title {
font-weight: 800;
font-size: 14px;
margin-bottom: 2px;
}
.quick-action-text {
color: var(--trfgo-muted);
font-size: 12px;
margin: 0;
}
.setup-item {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
padding: 13px 0;
border-bottom: 1px solid #eef2f7;
}
.setup-item:last-child {
border-bottom: 0;
}
.setup-left {
display: flex;
align-items: center;
gap: 12px;
}
.setup-icon {
width: 38px;
height: 38px;
border-radius: 12px;
display: inline-flex;
align-items: center;
justify-content: center;
background: #f1f5f9;
color: #475569;
font-size: 20px;
}
.setup-icon.completed {
background: #ecfdf5;
color: #059669;
}
.setup-label {
font-weight: 700;
color: #0f172a;
}
.badge-soft-success {
background: #dcfce7;
color: #166534;
border-radius: 999px;
font-weight: 700;
padding: 6px 10px;
white-space: nowrap;
}
.badge-soft-warning {
background: #ffedd5;
color: #9a3412;
border-radius: 999px;
font-weight: 700;
padding: 6px 10px;
white-space: nowrap;
}
.badge-soft-muted {
background: #f1f5f9;
color: #475569;
border-radius: 999px;
font-weight: 700;
padding: 6px 10px;
white-space: nowrap;
}
.company-table th {
color: #64748b;
font-size: 12px;
text-transform: uppercase;
letter-spacing: 0.04em;
border-bottom: 1px solid #e5e7eb;
}
.company-table td {
vertical-align: middle;
border-color: #eef2f7;
}
.company-name {
font-weight: 800;
color: #0f172a;
}
.company-legal {
font-size: 12px;
color: var(--trfgo-muted);
}
.empty-state {
text-align: center;
padding: 38px 20px;
color: var(--trfgo-muted);
}
.empty-state i {
font-size: 46px;
color: #cbd5e1;
margin-bottom: 10px;
}
.progress {
height: 9px;
border-radius: 999px;
background: #e2e8f0;
}
.progress-bar {
border-radius: 999px;
}
.metric-pill {
display: inline-flex;
align-items: center;
gap: 6px;
background: #f8fafc;
border: 1px solid #e5e7eb;
color: #475569;
border-radius: 999px;
padding: 5px 9px;
font-size: 12px;
font-weight: 700;
}
@media (max-width: 767px) {
.dashboard-hero {
padding: 22px;
}
.dashboard-title {
font-size: 25px;
}
.hero-actions {
flex-direction: column;
}
.btn-hero-light,
.btn-hero-outline {
justify-content: center;
}
}
</style>
</head>
<body>
<div class="wrapper">
<?php include('include/navbar.php'); ?>
<?php include('include/topbar.php'); ?>
<div class="page-wrapper">
<div class="page-content">
<div class="dashboard-hero mb-4">
<div class="dashboard-hero-content">
<div class="dashboard-eyebrow">
<i class="bx bx-lab"></i>
TRFgo Platform
</div>
<h1 class="dashboard-title">
Welcome, <?= e(trim($nameuser . ' ' . $surnameuser)); ?>
</h1>
<p class="dashboard-subtitle">
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.
</p>
<div class="hero-actions">
<a href="companies.php" class="btn-hero-light">
<i class="bx bx-buildings"></i>
Manage Companies
</a>
<a href="brands.php" class="btn-hero-outline">
<i class="bx bx-purchase-tag-alt"></i>
Manage Brands
</a>
<a href="departments.php" class="btn-hero-outline">
<i class="bx bx-sitemap"></i>
Manage Departments
</a>
</div>
</div>
</div>
<div class="dashboard-toolbar">
<div>
<div class="dashboard-toolbar-title">Your Dashboard</div>
<p class="dashboard-toolbar-subtitle">
Drag widgets using the handle. The layout is saved for your user account.
</p>
</div>
<div class="d-flex gap-2">
<button type="button" class="btn btn-sm btn-light" id="btnResetLayout">
<i class="bx bx-reset"></i>
Reset layout
</button>
<button type="button" class="btn btn-sm btn-primary" id="btnSaveLayout">
<i class="bx bx-save"></i>
Save layout
</button>
</div>
</div>
<div class="row dashboard-grid" id="dashboardGrid">
<?php foreach ($userLayout as $widgetKey): ?>
<?php if ($widgetKey === 'kpi_companies'): ?>
<div class="col-12 col-md-6 col-xl-3 dashboard-widget" data-widget="kpi_companies">
<div class="card widget-card">
<div class="card-body">
<div class="widget-header mb-2">
<div class="drag-handle"><i class="bx bx-move"></i></div>
</div>
<div class="d-flex align-items-center justify-content-between">
<div>
<div class="stat-label">Companies</div>
<div class="stat-value"><?= e($totalCompanies); ?></div>
<div class="stat-caption"><?= e($activeCompanies); ?> active companies</div>
</div>
<div class="stat-icon blue">
<i class="bx bx-buildings"></i>
</div>
</div>
</div>
</div>
</div>
<?php endif; ?>
<?php if ($widgetKey === 'kpi_brands'): ?>
<div class="col-12 col-md-6 col-xl-3 dashboard-widget" data-widget="kpi_brands">
<div class="card widget-card">
<div class="card-body">
<div class="widget-header mb-2">
<div class="drag-handle"><i class="bx bx-move"></i></div>
</div>
<div class="d-flex align-items-center justify-content-between">
<div>
<div class="stat-label">Brands</div>
<div class="stat-value"><?= e($totalBrands); ?></div>
<div class="stat-caption"><?= e($activeBrands); ?> active brands</div>
</div>
<div class="stat-icon green">
<i class="bx bx-purchase-tag-alt"></i>
</div>
</div>
</div>
</div>
</div>
<?php endif; ?>
<?php if ($widgetKey === 'kpi_departments'): ?>
<div class="col-12 col-md-6 col-xl-3 dashboard-widget" data-widget="kpi_departments">
<div class="card widget-card">
<div class="card-body">
<div class="widget-header mb-2">
<div class="drag-handle"><i class="bx bx-move"></i></div>
</div>
<div class="d-flex align-items-center justify-content-between">
<div>
<div class="stat-label">Departments</div>
<div class="stat-value"><?= e($totalDepartments); ?></div>
<div class="stat-caption"><?= e($activeDepartments); ?> active departments</div>
</div>
<div class="stat-icon orange">
<i class="bx bx-sitemap"></i>
</div>
</div>
</div>
</div>
</div>
<?php endif; ?>
<?php if ($widgetKey === 'kpi_users'): ?>
<div class="col-12 col-md-6 col-xl-3 dashboard-widget" data-widget="kpi_users">
<div class="card widget-card">
<div class="card-body">
<div class="widget-header mb-2">
<div class="drag-handle"><i class="bx bx-move"></i></div>
</div>
<div class="d-flex align-items-center justify-content-between">
<div>
<div class="stat-label">Company Users</div>
<div class="stat-value"><?= e($totalCompanyUsers); ?></div>
<div class="stat-caption"><?= e($activeCompanyUsers); ?> active assignments</div>
</div>
<div class="stat-icon purple">
<i class="bx bx-user-check"></i>
</div>
</div>
</div>
</div>
</div>
<?php endif; ?>
<?php if ($widgetKey === 'chart_structure'): ?>
<div class="col-12 col-xl-8 dashboard-widget" data-widget="chart_structure">
<div class="card widget-card">
<div class="card-header bg-transparent">
<div class="widget-header">
<div>
<h6 class="widget-title mb-0">Company Structure Overview</h6>
<p class="widget-subtitle">Brands, departments and user assignments by company</p>
</div>
<div class="drag-handle"><i class="bx bx-move"></i></div>
</div>
</div>
<div class="card-body">
<?php if (count($companyDistribution) > 0): ?>
<div id="companyStructureChart" style="min-height: 330px;"></div>
<?php else: ?>
<div class="empty-state">
<i class="bx bx-bar-chart-alt-2"></i>
<h6>No chart data available</h6>
<p class="mb-0">Create your first company, brand or department to populate this chart.</p>
</div>
<?php endif; ?>
</div>
</div>
</div>
<?php endif; ?>
<?php if ($widgetKey === 'setup_progress'): ?>
<div class="col-12 col-xl-4 dashboard-widget" data-widget="setup_progress">
<div class="card widget-card">
<div class="card-header bg-transparent">
<div class="widget-header">
<div>
<h6 class="widget-title mb-0">TRFgo Setup</h6>
<p class="widget-subtitle">Initial configuration progress</p>
</div>
<div class="drag-handle"><i class="bx bx-move"></i></div>
</div>
</div>
<div class="card-body">
<div class="d-flex align-items-center justify-content-between mb-2">
<strong><?= e($setupProgress); ?>% completed</strong>
<span class="badge-soft-muted"><?= e($completedSetupItems); ?>/<?= e(count($setupItems)); ?></span>
</div>
<div class="progress mb-3">
<div class="progress-bar" role="progressbar" style="width: <?= e($setupProgress); ?>%;" aria-valuenow="<?= e($setupProgress); ?>" aria-valuemin="0" aria-valuemax="100"></div>
</div>
<?php foreach ($setupItems as $item): ?>
<div class="setup-item">
<div class="setup-left">
<div class="setup-icon <?= $item['completed'] ? 'completed' : ''; ?>">
<i class="<?= e($item['icon']); ?>"></i>
</div>
<div class="setup-label"><?= e($item['label']); ?></div>
</div>
<?php if ($item['completed']): ?>
<span class="badge-soft-success">Ready</span>
<?php else: ?>
<span class="badge-soft-warning">Missing</span>
<?php endif; ?>
</div>
<?php endforeach; ?>
</div>
</div>
</div>
<?php endif; ?>
<?php if ($widgetKey === 'quick_actions'): ?>
<div class="col-12 col-xl-4 dashboard-widget" data-widget="quick_actions">
<div class="card widget-card">
<div class="card-header bg-transparent">
<div class="widget-header">
<div>
<h6 class="widget-title mb-0">Quick Actions</h6>
<p class="widget-subtitle">Start configuring TRFgo</p>
</div>
<div class="drag-handle"><i class="bx bx-move"></i></div>
</div>
</div>
<div class="card-body">
<div class="d-grid gap-2">
<a href="companies.php" class="quick-action">
<span class="quick-action-icon">
<i class="bx bx-plus-circle"></i>
</span>
<span>
<span class="quick-action-title d-block">Create company</span>
<span class="quick-action-text">Add a customer or laboratory organization</span>
</span>
</a>
<a href="brands.php" class="quick-action">
<span class="quick-action-icon">
<i class="bx bx-purchase-tag-alt"></i>
</span>
<span>
<span class="quick-action-title d-block">Create brand</span>
<span class="quick-action-text">Organize companies by brand or division</span>
</span>
</a>
<a href="departments.php" class="quick-action">
<span class="quick-action-icon">
<i class="bx bx-sitemap"></i>
</span>
<span>
<span class="quick-action-title d-block">Create department</span>
<span class="quick-action-text">Define departments such as Bags, Shoes, Apparel</span>
</span>
</a>
<a href="company-users.php" class="quick-action">
<span class="quick-action-icon">
<i class="bx bx-user-plus"></i>
</span>
<span>
<span class="quick-action-title d-block">Assign users</span>
<span class="quick-action-text">Connect Vanguard users to TRFgo companies</span>
</span>
</a>
</div>
</div>
</div>
</div>
<?php endif; ?>
<?php if ($widgetKey === 'recent_companies'): ?>
<div class="col-12 col-xl-8 dashboard-widget" data-widget="recent_companies">
<div class="card widget-card">
<div class="card-header bg-transparent">
<div class="widget-header">
<div>
<h6 class="widget-title mb-0">Recent Companies</h6>
<p class="widget-subtitle">Latest configured organizations in TRFgo</p>
</div>
<div class="d-flex align-items-center gap-2">
<a href="companies.php" class="btn btn-sm btn-primary">
<i class="bx bx-list-ul"></i>
View all
</a>
<div class="drag-handle"><i class="bx bx-move"></i></div>
</div>
</div>
</div>
<div class="card-body">
<?php if (count($recentCompanies) > 0): ?>
<div class="table-responsive">
<table class="table align-middle company-table mb-0">
<thead>
<tr>
<th>Company</th>
<th>Status</th>
<th class="text-center">Brands</th>
<th class="text-center">Departments</th>
<th class="text-center">Users</th>
<th>Created</th>
</tr>
</thead>
<tbody>
<?php foreach ($recentCompanies as $company): ?>
<tr>
<td>
<div class="company-name"><?= e($company['company_name']); ?></div>
<?php if (!empty($company['legal_name'])): ?>
<div class="company-legal"><?= e($company['legal_name']); ?></div>
<?php endif; ?>
</td>
<td>
<?php if ($company['status'] === 'active'): ?>
<span class="badge-soft-success">Active</span>
<?php elseif ($company['status'] === 'suspended'): ?>
<span class="badge-soft-warning">Suspended</span>
<?php else: ?>
<span class="badge-soft-muted">Inactive</span>
<?php endif; ?>
</td>
<td class="text-center">
<span class="metric-pill">
<i class="bx bx-purchase-tag-alt"></i>
<?= e($company['brand_count']); ?>
</span>
</td>
<td class="text-center">
<span class="metric-pill">
<i class="bx bx-sitemap"></i>
<?= e($company['department_count']); ?>
</span>
</td>
<td class="text-center">
<span class="metric-pill">
<i class="bx bx-user"></i>
<?= e($company['user_count']); ?>
</span>
</td>
<td>
<?= !empty($company['created_at']) ? e(date('d/m/Y', strtotime($company['created_at']))) : '-'; ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php else: ?>
<div class="empty-state">
<i class="bx bx-buildings"></i>
<h6>No companies configured yet</h6>
<p>Create your first company to start using TRFgo.</p>
<a href="companies.php" class="btn btn-primary">
<i class="bx bx-plus-circle"></i>
Add first company
</a>
</div>
<?php endif; ?>
</div>
</div>
</div>
<?php endif; ?>
<?php endforeach; ?>
</div>
</div>
</div>
<div class="overlay toggle-icon"></div>
<a href="javaScript:;" class="back-to-top">
<i class='bx bxs-up-arrow-alt'></i>
</a>
<?php include('include/footer.php'); ?>
</div>
<?php include('jsinclude.php'); ?>
<script src="assets/plugins/apexcharts-bundle/js/apexcharts.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/sortablejs@1.15.2/Sortable.min.js"></script>
<script>
document.addEventListener("DOMContentLoaded", function() {
const chartLabels = <?= json_encode($chartCompanyLabels, JSON_UNESCAPED_UNICODE); ?>;
const chartBrands = <?= json_encode($chartBrands, JSON_NUMERIC_CHECK); ?>;
const chartDepartments = <?= json_encode($chartDepartments, JSON_NUMERIC_CHECK); ?>;
const chartUsers = <?= json_encode($chartUsers, JSON_NUMERIC_CHECK); ?>;
let chartInstance = null;
function renderCompanyChart() {
const chartElement = document.querySelector("#companyStructureChart");
if (!chartElement) {
return;
}
if (typeof ApexCharts === "undefined") {
chartElement.innerHTML =
'<div class="empty-state"><i class="bx bx-error-circle"></i><h6>ApexCharts not loaded</h6><p class="mb-0">Check apexcharts.min.js path.</p></div>';
return;
}
if (chartInstance !== null) {
chartInstance.destroy();
}
const options = {
series: [{
name: "Brands",
data: chartBrands
},
{
name: "Departments",
data: chartDepartments
},
{
name: "Users",
data: chartUsers
}
],
chart: {
type: "bar",
height: 330,
toolbar: {
show: false
},
fontFamily: "inherit"
},
plotOptions: {
bar: {
horizontal: false,
columnWidth: "45%",
borderRadius: 6
}
},
dataLabels: {
enabled: false
},
stroke: {
show: true,
width: 2,
colors: ["transparent"]
},
xaxis: {
categories: chartLabels,
labels: {
rotate: -35,
trim: true
}
},
yaxis: {
min: 0,
forceNiceScale: true
},
fill: {
opacity: 1
},
legend: {
position: "top",
horizontalAlign: "right"
},
tooltip: {
y: {
formatter: function(value) {
return value;
}
}
},
grid: {
borderColor: "#eef2f7"
}
};
chartInstance = new ApexCharts(chartElement, options);
chartInstance.render();
}
renderCompanyChart();
function getCurrentLayout() {
const layout = [];
document.querySelectorAll("#dashboardGrid .dashboard-widget").forEach(function(widget) {
const widgetKey = widget.getAttribute("data-widget");
if (widgetKey) {
layout.push(widgetKey);
}
});
return layout;
}
function showMessage(type, message) {
if (typeof Swal !== "undefined") {
Swal.fire({
icon: type,
title: type === "success" ? "Done" : "Attention",
text: message,
confirmButtonColor: "#2563eb"
});
return;
}
alert(message);
}
function saveLayout(showSuccess = true) {
const layout = getCurrentLayout();
$.ajax({
url: window.location.pathname,
type: "POST",
dataType: "json",
data: {
action: "save_dashboard_layout",
layout: JSON.stringify(layout)
},
success: function(response) {
if (!response.success) {
showMessage("error", response.message || "Unable to save dashboard layout.");
return;
}
if (showSuccess) {
showMessage("success", response.message || "Dashboard layout saved.");
}
},
error: function() {
showMessage("error", "Server error while saving dashboard layout.");
}
});
}
if (typeof Sortable !== "undefined") {
const dashboardGrid = document.getElementById("dashboardGrid");
Sortable.create(dashboardGrid, {
animation: 180,
handle: ".drag-handle",
draggable: ".dashboard-widget",
ghostClass: "sortable-ghost",
chosenClass: "sortable-chosen",
onEnd: function() {
setTimeout(function() {
renderCompanyChart();
saveLayout(false);
}, 250);
}
});
} else {
console.warn("SortableJS not loaded. Dashboard drag and drop disabled.");
}
$("#btnSaveLayout").on("click", function() {
saveLayout(true);
});
$("#btnResetLayout").on("click", function() {
const doReset = function() {
$.ajax({
url: window.location.pathname,
type: "POST",
dataType: "json",
data: {
action: "reset_dashboard_layout"
},
success: function(response) {
if (!response.success) {
showMessage("error", response.message || "Unable to reset dashboard layout.");
return;
}
if (typeof Swal !== "undefined") {
Swal.fire({
icon: "success",
title: "Done",
text: response.message || "Dashboard layout reset.",
confirmButtonColor: "#2563eb"
}).then(function() {
window.location.reload();
});
return;
}
alert(response.message || "Dashboard layout reset.");
window.location.reload();
},
error: function() {
showMessage("error", "Server error while resetting dashboard layout.");
}
});
};
if (typeof Swal !== "undefined") {
Swal.fire({
icon: "question",
title: "Reset dashboard layout?",
text: "Your personal widget order will be restored to the default layout.",
showCancelButton: true,
confirmButtonColor: "#2563eb",
cancelButtonColor: "#64748b",
confirmButtonText: "Yes, reset"
}).then(function(result) {
if (result.isConfirmed) {
doReset();
}
});
return;
}
if (confirm("Reset dashboard layout?")) {
doReset();
}
});
});
</script>
</body>
</html>