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

2002 lines
78 KiB
PHP

<?php include('include/headscript.php'); ?>
<?php
/*
* TRFgo Customer Dashboard
* Operational dashboard for company/customer users.
* Widgets are draggable and layout is persisted per Vanguard user.
*/
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 tableExists(PDO $db, string $tableName): bool
{
try {
$stmt = $db->prepare("
SELECT COUNT(*)
FROM information_schema.tables
WHERE table_schema = DATABASE()
AND table_name = :table_name
");
$stmt->execute([':table_name' => $tableName]);
return (int) $stmt->fetchColumn() > 0;
} catch (Throwable $e) {
return false;
}
}
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_customer';
/*
* Widget registry.
*/
$availableWidgets = [
'customer_company_profile',
'kpi_business_partners',
'kpi_samples',
'kpi_sample_parts',
'kpi_documents',
'kpi_trf_requests',
'kpi_pending_requests',
'kpi_reports_received',
'master_data_readiness',
'chart_trf_status',
'recent_samples',
'recent_trf_requests',
'document_repository',
'quick_actions',
];
$defaultLayout = [
'customer_company_profile',
'kpi_business_partners',
'kpi_samples',
'kpi_sample_parts',
'kpi_documents',
'master_data_readiness',
'recent_samples',
'quick_actions',
'kpi_trf_requests',
'kpi_pending_requests',
'kpi_reports_received',
'chart_trf_status',
'recent_trf_requests',
'document_repository',
];
/*
* Find customer company scope for current user.
*
* SaaS:
* - user can be linked to one or more companies through company_users.
*
* On-premise:
* - if no assignment exists, fallback to TRFGO_DEFAULT_COMPANY_ID or first company.
*/
$userCompanies = getRows($db, "
SELECT DISTINCT
c.idcompany,
c.company_name,
c.legal_name,
c.status,
c.email,
c.phone,
c.city,
c.external_code,
cu.company_role,
cu.user_scope
FROM company_users cu
INNER JOIN companies c ON c.idcompany = cu.idcompany
WHERE cu.iduser = :iduser
AND cu.status = 'active'
ORDER BY c.company_name ASC
", [
':iduser' => $iduserlogin,
]);
$selectedCompanyId = 0;
if (!empty($_GET['idcompany'])) {
$selectedCompanyId = (int) $_GET['idcompany'];
}
if ($selectedCompanyId <= 0 && count($userCompanies) > 0) {
$selectedCompanyId = (int) $userCompanies[0]['idcompany'];
}
/*
* On-premise fallback.
*/
if ($selectedCompanyId <= 0) {
$defaultCompanyId = isset($_ENV['TRFGO_DEFAULT_COMPANY_ID']) ? (int) $_ENV['TRFGO_DEFAULT_COMPANY_ID'] : 0;
if ($defaultCompanyId > 0) {
$selectedCompanyId = $defaultCompanyId;
} else {
$selectedCompanyId = (int) getScalar($db, "
SELECT idcompany
FROM companies
ORDER BY idcompany ASC
LIMIT 1
");
}
}
$selectedCompany = null;
if ($selectedCompanyId > 0) {
$rows = getRows($db, "
SELECT *
FROM companies
WHERE idcompany = :idcompany
LIMIT 1
", [
':idcompany' => $selectedCompanyId,
]);
$selectedCompany = $rows[0] ?? null;
}
$companyParams = [
':idcompany' => $selectedCompanyId,
];
/*
* Existing setup data.
*/
$totalBrands = $selectedCompanyId > 0
? (int) getScalar($db, "SELECT COUNT(*) FROM brands WHERE idcompany = :idcompany", $companyParams)
: 0;
$totalDepartments = $selectedCompanyId > 0
? (int) getScalar($db, "SELECT COUNT(*) FROM departments WHERE idcompany = :idcompany", $companyParams)
: 0;
$totalCompanyUsers = $selectedCompanyId > 0
? (int) getScalar($db, "SELECT COUNT(*) FROM company_users WHERE idcompany = :idcompany AND status = 'active'", $companyParams)
: 0;
/*
* Future operational tables.
* These names are provisional and will be created later with Phinx.
*/
$hasSamplesTable = tableExists($db, 'samples');
$hasTrfRequestsTable = tableExists($db, 'trf_requests');
$hasLabReportsTable = tableExists($db, 'lab_reports');
$hasDocumentsTable = tableExists($db, 'documents') || tableExists($db, 'trf_documents');
$hasBusinessPartnersTable = tableExists($db, 'business_partners');
$hasSamplePartsTable = tableExists($db, 'sample_parts');
$hasSamplePhotosTable = tableExists($db, 'sample_photos');
/*
* KPI counters.
*/
$totalSamples = 0;
$totalTrfRequests = 0;
$pendingTrfRequests = 0;
$reportsReceived = 0;
$totalDocuments = 0;
$totalBusinessPartners = 0;
$activeBusinessPartners = 0;
$totalSampleParts = 0;
$totalSamplePhotos = 0;
$recentSamples = [];
if ($selectedCompanyId > 0 && $hasSamplesTable) {
$totalSamples = (int) getScalar($db, "
SELECT COUNT(*)
FROM samples
WHERE idcompany = :idcompany
", $companyParams);
}
if ($selectedCompanyId > 0 && $hasBusinessPartnersTable) {
$totalBusinessPartners = (int) getScalar($db, "
SELECT COUNT(*)
FROM business_partners
WHERE idcompany = :idcompany
", $companyParams);
$activeBusinessPartners = (int) getScalar($db, "
SELECT COUNT(*)
FROM business_partners
WHERE idcompany = :idcompany
AND status = 'active'
", $companyParams);
}
if ($selectedCompanyId > 0 && $hasSamplePartsTable && $hasSamplesTable) {
$totalSampleParts = (int) getScalar($db, "
SELECT COUNT(sp.idpart)
FROM sample_parts sp
INNER JOIN samples s ON s.idsample = sp.idsample
WHERE s.idcompany = :idcompany
", $companyParams);
}
if ($selectedCompanyId > 0 && $hasSamplePhotosTable && $hasSamplesTable) {
$totalSamplePhotos = (int) getScalar($db, "
SELECT COUNT(sph.idsamplephoto)
FROM sample_photos sph
INNER JOIN samples s ON s.idsample = sph.idsample
WHERE s.idcompany = :idcompany
", $companyParams);
}
if ($selectedCompanyId > 0 && $hasSamplesTable) {
$recentSamples = getRows($db, "
SELECT
s.idsample,
s.sample_code,
s.external_sample_id,
s.article_no,
s.po_no,
s.season,
s.sample_description,
s.color,
s.production_stage,
s.status,
s.created_at,
b.brand_name,
d.department_name,
bp1.partner_name AS producer_name,
bp2.partner_name AS supplier_name,
COUNT(DISTINCT sp.idpart) AS parts_count,
COUNT(DISTINCT sph.idsamplephoto) AS photos_count,
COUNT(DISTINCT sd.idsampledocument) AS documents_count
FROM samples s
LEFT JOIN brands b ON b.idbrand = s.idbrand
LEFT JOIN departments d ON d.iddepartment = s.iddepartment
LEFT JOIN business_partners bp1 ON bp1.idpartner = s.idproducer
LEFT JOIN business_partners bp2 ON bp2.idpartner = s.idsupplier
LEFT JOIN sample_parts sp ON sp.idsample = s.idsample
LEFT JOIN sample_photos sph ON sph.idsample = s.idsample
LEFT JOIN sample_documents sd ON sd.idsample = s.idsample
WHERE s.idcompany = :idcompany
GROUP BY
s.idsample,
s.sample_code,
s.external_sample_id,
s.article_no,
s.po_no,
s.season,
s.sample_description,
s.color,
s.production_stage,
s.status,
s.created_at,
b.brand_name,
d.department_name,
bp1.partner_name,
bp2.partner_name
ORDER BY s.created_at DESC, s.idsample DESC
LIMIT 8
", $companyParams);
}
if ($selectedCompanyId > 0 && $hasTrfRequestsTable) {
$totalTrfRequests = (int) getScalar($db, "
SELECT COUNT(*)
FROM trf_requests
WHERE idcompany = :idcompany
", $companyParams);
$pendingTrfRequests = (int) getScalar($db, "
SELECT COUNT(*)
FROM trf_requests
WHERE idcompany = :idcompany
AND status IN ('draft', 'submitted', 'available_for_lab', 'pulled_by_lab', 'in_lims', 'testing')
", $companyParams);
}
if ($selectedCompanyId > 0 && $hasLabReportsTable) {
$reportsReceived = (int) getScalar($db, "
SELECT COUNT(lr.idlabreport)
FROM lab_reports lr
INNER JOIN trf_requests trf ON trf.idtrf = lr.idtrf
WHERE trf.idcompany = :idcompany
", $companyParams);
}
if ($selectedCompanyId > 0 && tableExists($db, 'documents')) {
$totalDocuments = (int) getScalar($db, "
SELECT COUNT(*)
FROM documents
WHERE idcompany = :idcompany
", $companyParams);
}
if ($selectedCompanyId > 0 && !$totalDocuments && tableExists($db, 'trf_documents')) {
$totalDocuments = (int) getScalar($db, "
SELECT COUNT(td.iddocument)
FROM trf_documents td
INNER JOIN trf_requests trf ON trf.idtrf = td.idtrf
WHERE trf.idcompany = :idcompany
", $companyParams);
}
/*
* TRF status chart data.
*/
$trfStatusRows = [];
if ($selectedCompanyId > 0 && $hasTrfRequestsTable) {
$trfStatusRows = getRows($db, "
SELECT status, COUNT(*) AS total
FROM trf_requests
WHERE idcompany = :idcompany
GROUP BY status
ORDER BY status ASC
", $companyParams);
}
$trfStatusLabels = [];
$trfStatusValues = [];
foreach ($trfStatusRows as $row) {
$trfStatusLabels[] = ucfirst(str_replace('_', ' ', $row['status']));
$trfStatusValues[] = (int) $row['total'];
}
/*
* Recent TRF requests.
*/
$recentTrfRequests = [];
if ($selectedCompanyId > 0 && $hasTrfRequestsTable) {
$recentTrfRequests = getRows($db, "
SELECT
idtrf,
trf_code,
external_trf_id,
trf_type,
service_required,
status,
created_at
FROM trf_requests
WHERE idcompany = :idcompany
ORDER BY created_at DESC, idtrf DESC
LIMIT 8
", $companyParams);
}
/*
* Pending actions.
*/
$pendingActions = [
[
'title' => 'Create business partners',
'text' => $hasBusinessPartnersTable ? 'Add producers, suppliers, vendors and factories.' : 'Business partner module is not active yet.',
'icon' => 'bx bx-network-chart',
'completed' => $hasBusinessPartnersTable && $totalBusinessPartners > 0,
'link' => $hasBusinessPartnersTable ? 'business-partners.php' : '#',
],
[
'title' => 'Create or import samples',
'text' => $hasSamplesTable ? 'Start building product identity cards.' : 'Sample module is not active yet.',
'icon' => 'bx bx-package',
'completed' => $hasSamplesTable && $totalSamples > 0,
'link' => $hasSamplesTable ? 'samples.php' : '#',
],
[
'title' => 'Add BOM / parts',
'text' => $hasSamplePartsTable ? 'Complete sample identity with materials and components.' : 'BOM module is not active yet.',
'icon' => 'bx bx-git-branch',
'completed' => $hasSamplePartsTable && $totalSampleParts > 0,
'link' => $hasSamplesTable ? 'samples.php' : '#',
],
[
'title' => 'Attach documents',
'text' => $hasDocumentsTable ? 'Attach technical sheets, certificates and declarations.' : 'Document repository is not active yet.',
'icon' => 'bx bx-folder',
'completed' => $hasDocumentsTable && $totalDocuments > 0,
'link' => $hasDocumentsTable ? 'documents.php' : '#',
],
[
'title' => 'Prepare TRF requests',
'text' => $hasTrfRequestsTable ? 'Create test requests from one or more samples.' : 'TRF request module is not active yet.',
'icon' => 'bx bx-file',
'completed' => $hasTrfRequestsTable && $totalTrfRequests > 0,
'link' => $hasTrfRequestsTable ? 'trf-requests.php' : '#',
],
];
$completedPendingActions = count(array_filter($pendingActions, function ($item) {
return $item['completed'];
}));
$operationalProgress = count($pendingActions) > 0
? round(($completedPendingActions / count($pendingActions)) * 100)
: 0;
/*
* 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()
]);
}
}
/*
* Load user 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 = 'Customer Dashboard';
$companyName = $selectedCompany['company_name'] ?? 'Your Company';
$companyLegalName = $selectedCompany['legal_name'] ?? '';
?>
<!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-soft-red: #fef2f2;
--trfgo-border: #e5e7eb;
--trfgo-muted: #64748b;
}
.customer-hero {
position: relative;
overflow: hidden;
border-radius: 20px;
background:
radial-gradient(circle at top right, rgba(45, 212, 191, 0.32), transparent 34%),
linear-gradient(135deg, #0f172a 0%, #1d4ed8 54%, #0f766e 100%);
color: #fff;
padding: 28px;
min-height: 220px;
box-shadow: 0 18px 45px rgba(15, 23, 42, 0.22);
}
.customer-hero h1,
.customer-hero h2,
.customer-hero h3,
.customer-hero h4,
.customer-hero h5,
.customer-hero h6 {
color: #ffffff;
}
.customer-hero::before {
content: "";
position: absolute;
width: 280px;
height: 280px;
border-radius: 50%;
background: rgba(255, 255, 255, 0.09);
right: -90px;
bottom: -120px;
}
.customer-hero-content {
position: relative;
z-index: 2;
}
.customer-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;
}
.customer-title {
font-size: 32px;
font-weight: 800;
letter-spacing: -0.03em;
margin-bottom: 8px;
color: #ffffff;
}
.customer-subtitle {
max-width: 820px;
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;
}
.company-switcher {
max-width: 360px;
}
.company-switcher .form-select {
border-radius: 12px;
border: 0;
box-shadow: 0 12px 28px rgba(15, 23, 42, 0.16);
font-weight: 700;
}
.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;
}
.coming-soon {
display: inline-flex;
align-items: center;
gap: 6px;
background: #f1f5f9;
color: #475569;
border-radius: 999px;
padding: 5px 9px;
font-size: 11px;
font-weight: 800;
text-transform: uppercase;
letter-spacing: 0.03em;
}
.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.disabled-action {
pointer-events: none;
opacity: 0.55;
}
.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;
}
.action-item {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
padding: 13px 0;
border-bottom: 1px solid #eef2f7;
}
.action-item:last-child {
border-bottom: 0;
}
.action-left {
display: flex;
align-items: center;
gap: 12px;
}
.action-icon {
width: 38px;
height: 38px;
border-radius: 12px;
display: inline-flex;
align-items: center;
justify-content: center;
background: #f1f5f9;
color: #475569;
font-size: 20px;
}
.action-icon.completed {
background: #ecfdf5;
color: #059669;
}
.action-title {
font-weight: 800;
color: #0f172a;
}
.action-text {
color: var(--trfgo-muted);
font-size: 12px;
}
.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;
}
.badge-soft-primary {
background: #dbeafe;
color: #1d4ed8;
border-radius: 999px;
font-weight: 700;
padding: 6px 10px;
white-space: nowrap;
}
.info-row {
display: flex;
justify-content: space-between;
gap: 14px;
padding: 12px 0;
border-bottom: 1px solid #eef2f7;
}
.info-row:last-child {
border-bottom: 0;
}
.info-label {
color: var(--trfgo-muted);
font-size: 13px;
font-weight: 700;
}
.info-value {
color: #0f172a;
font-size: 13px;
font-weight: 800;
text-align: right;
}
.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;
}
.trf-table th {
color: #64748b;
font-size: 12px;
text-transform: uppercase;
letter-spacing: 0.04em;
border-bottom: 1px solid #e5e7eb;
}
.trf-table td {
vertical-align: middle;
border-color: #eef2f7;
}
.trf-code {
font-weight: 800;
color: #0f172a;
}
.trf-sub {
font-size: 12px;
color: var(--trfgo-muted);
}
@media (max-width: 767px) {
.customer-hero {
padding: 22px;
}
.customer-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="customer-hero mb-4">
<div class="customer-hero-content">
<div class="d-flex align-items-start justify-content-between flex-wrap gap-3">
<div>
<div class="customer-eyebrow">
<i class="bx bx-home-circle"></i>
Customer Workspace
</div>
<h1 class="customer-title">
<?= e($companyName); ?>
</h1>
<p class="customer-subtitle">
Manage your samples, digital test request forms, laboratory reports and technical documents from one operational dashboard.
This workspace is designed for customer users and on-premise installations.
</p>
<div class="hero-actions">
<a href="<?= $hasSamplesTable ? 'samples.php' : '#'; ?>" class="btn-hero-light <?= !$hasSamplesTable ? 'disabled-action' : ''; ?>">
<i class="bx bx-package"></i>
Add Sample
</a>
<a href="<?= $hasTrfRequestsTable ? 'trf-requests.php' : '#'; ?>" class="btn-hero-outline <?= !$hasTrfRequestsTable ? 'disabled-action' : ''; ?>">
<i class="bx bx-file"></i>
New TRF Request
</a>
<a href="<?= $hasDocumentsTable ? 'documents.php' : '#'; ?>" class="btn-hero-outline <?= !$hasDocumentsTable ? 'disabled-action' : ''; ?>">
<i class="bx bx-folder"></i>
Documents
</a>
</div>
</div>
<?php if (count($userCompanies) > 1): ?>
<div class="company-switcher">
<select class="form-select" id="companySwitcher">
<?php foreach ($userCompanies as $companyOption): ?>
<option value="<?= e($companyOption['idcompany']); ?>" <?= (int) $companyOption['idcompany'] === $selectedCompanyId ? 'selected' : ''; ?>>
<?= e($companyOption['company_name']); ?>
</option>
<?php endforeach; ?>
</select>
</div>
<?php endif; ?>
</div>
</div>
</div>
<?php if (!$selectedCompany): ?>
<div class="alert alert-warning">
<strong>No company available.</strong>
This user is not linked to any TRFgo company yet.
</div>
<?php endif; ?>
<div class="dashboard-toolbar">
<div>
<div class="dashboard-toolbar-title">Operational 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 === 'customer_company_profile'): ?>
<div class="col-12 col-xl-4 dashboard-widget" data-widget="customer_company_profile">
<div class="card widget-card">
<div class="card-header bg-transparent">
<div class="widget-header">
<div>
<h6 class="widget-title mb-0">Company Profile</h6>
<p class="widget-subtitle">Current customer workspace</p>
</div>
<div class="drag-handle"><i class="bx bx-move"></i></div>
</div>
</div>
<div class="card-body">
<div class="info-row">
<div class="info-label">Company</div>
<div class="info-value"><?= e($companyName); ?></div>
</div>
<div class="info-row">
<div class="info-label">Legal Name</div>
<div class="info-value"><?= $companyLegalName ? e($companyLegalName) : '-'; ?></div>
</div>
<div class="info-row">
<div class="info-label">Brands</div>
<div class="info-value"><?= e($totalBrands); ?></div>
</div>
<div class="info-row">
<div class="info-label">Departments</div>
<div class="info-value"><?= e($totalDepartments); ?></div>
</div>
<div class="info-row">
<div class="info-label">Active Users</div>
<div class="info-value"><?= e($totalCompanyUsers); ?></div>
</div>
</div>
</div>
</div>
<?php endif; ?>
<?php if ($widgetKey === 'kpi_business_partners'): ?>
<div class="col-12 col-md-6 col-xl-3 dashboard-widget" data-widget="kpi_business_partners">
<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>
<?php if (!$hasBusinessPartnersTable): ?>
<span class="coming-soon">Coming soon</span>
<?php endif; ?>
</div>
<div class="d-flex align-items-center justify-content-between">
<div>
<div class="stat-label">Business Partners</div>
<div class="stat-value"><?= e($totalBusinessPartners); ?></div>
<div class="stat-caption"><?= e($activeBusinessPartners); ?> active partners</div>
</div>
<div class="stat-icon green">
<i class="bx bx-network-chart"></i>
</div>
</div>
</div>
</div>
</div>
<?php endif; ?>
<?php if ($widgetKey === 'kpi_samples'): ?>
<div class="col-12 col-md-6 col-xl-3 dashboard-widget" data-widget="kpi_samples">
<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>
<?php if (!$hasSamplesTable): ?>
<span class="coming-soon">Coming soon</span>
<?php endif; ?>
</div>
<div class="d-flex align-items-center justify-content-between">
<div>
<div class="stat-label">Samples</div>
<div class="stat-value"><?= e($totalSamples); ?></div>
<div class="stat-caption">Products or samples in archive</div>
</div>
<div class="stat-icon blue">
<i class="bx bx-package"></i>
</div>
</div>
</div>
</div>
</div>
<?php endif; ?>
<?php if ($widgetKey === 'kpi_sample_parts'): ?>
<div class="col-12 col-md-6 col-xl-3 dashboard-widget" data-widget="kpi_sample_parts">
<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>
<?php if (!$hasSamplePartsTable): ?>
<span class="coming-soon">Coming soon</span>
<?php endif; ?>
</div>
<div class="d-flex align-items-center justify-content-between">
<div>
<div class="stat-label">BOM / Parts</div>
<div class="stat-value"><?= e($totalSampleParts); ?></div>
<div class="stat-caption"><?= e($totalSamplePhotos); ?> sample photos uploaded</div>
</div>
<div class="stat-icon orange">
<i class="bx bx-git-branch"></i>
</div>
</div>
</div>
</div>
</div>
<?php endif; ?>
<?php if ($widgetKey === 'kpi_trf_requests'): ?>
<div class="col-12 col-md-6 col-xl-3 dashboard-widget" data-widget="kpi_trf_requests">
<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>
<?php if (!$hasTrfRequestsTable): ?>
<span class="coming-soon">Coming soon</span>
<?php endif; ?>
</div>
<div class="d-flex align-items-center justify-content-between">
<div>
<div class="stat-label">TRF Requests</div>
<div class="stat-value"><?= e($totalTrfRequests); ?></div>
<div class="stat-caption">Digital test request forms</div>
</div>
<div class="stat-icon green">
<i class="bx bx-file"></i>
</div>
</div>
</div>
</div>
</div>
<?php endif; ?>
<?php if ($widgetKey === 'kpi_documents'): ?>
<div class="col-12 col-md-6 col-xl-3 dashboard-widget" data-widget="kpi_documents">
<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>
<?php if (!$hasDocumentsTable): ?>
<span class="coming-soon">Coming soon</span>
<?php endif; ?>
</div>
<div class="d-flex align-items-center justify-content-between">
<div>
<div class="stat-label">Documents</div>
<div class="stat-value"><?= e($totalDocuments); ?></div>
<div class="stat-caption">Technical files and certificates</div>
</div>
<div class="stat-icon purple">
<i class="bx bx-folder"></i>
</div>
</div>
</div>
</div>
</div>
<?php endif; ?>
<?php if ($widgetKey === 'kpi_pending_requests'): ?>
<div class="col-12 col-md-6 col-xl-3 dashboard-widget" data-widget="kpi_pending_requests">
<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>
<?php if (!$hasTrfRequestsTable): ?>
<span class="coming-soon">Coming soon</span>
<?php endif; ?>
</div>
<div class="d-flex align-items-center justify-content-between">
<div>
<div class="stat-label">Pending</div>
<div class="stat-value"><?= e($pendingTrfRequests); ?></div>
<div class="stat-caption">Requests not completed yet</div>
</div>
<div class="stat-icon orange">
<i class="bx bx-time-five"></i>
</div>
</div>
</div>
</div>
</div>
<?php endif; ?>
<?php if ($widgetKey === 'kpi_reports_received'): ?>
<div class="col-12 col-md-6 col-xl-3 dashboard-widget" data-widget="kpi_reports_received">
<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>
<?php if (!$hasLabReportsTable): ?>
<span class="coming-soon">Coming soon</span>
<?php endif; ?>
</div>
<div class="d-flex align-items-center justify-content-between">
<div>
<div class="stat-label">Reports</div>
<div class="stat-value"><?= e($reportsReceived); ?></div>
<div class="stat-caption">Laboratory reports received</div>
</div>
<div class="stat-icon purple">
<i class="bx bx-test-tube"></i>
</div>
</div>
</div>
</div>
</div>
<?php endif; ?>
<?php if ($widgetKey === 'chart_trf_status'): ?>
<div class="col-12 col-xl-8 dashboard-widget" data-widget="chart_trf_status">
<div class="card widget-card">
<div class="card-header bg-transparent">
<div class="widget-header">
<div>
<h6 class="widget-title mb-0">TRF Status Overview</h6>
<p class="widget-subtitle">Distribution of digital test request forms by status</p>
</div>
<div class="drag-handle"><i class="bx bx-move"></i></div>
</div>
</div>
<div class="card-body">
<?php if ($hasTrfRequestsTable && count($trfStatusRows) > 0): ?>
<div id="trfStatusChart" style="min-height: 330px;"></div>
<?php else: ?>
<div class="empty-state">
<i class="bx bx-pie-chart-alt-2"></i>
<h6>TRF chart not available yet</h6>
<p class="mb-0">The chart will be populated when TRF requests are created.</p>
</div>
<?php endif; ?>
</div>
</div>
</div>
<?php endif; ?>
<?php if ($widgetKey === 'master_data_readiness'): ?>
<div class="col-12 col-xl-4 dashboard-widget" data-widget="master_data_readiness"">
<div class=" card widget-card">
<div class="card-header bg-transparent">
<div class="widget-header">
<div>
<h6 class="widget-title mb-0">Master Data Readiness</h6>
<p class="widget-subtitle">Product identity card setup 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($operationalProgress); ?>% ready</strong>
<span class="badge-soft-muted"><?= e($completedPendingActions); ?>/<?= e(count($pendingActions)); ?></span>
</div>
<div class="progress mb-3">
<div class="progress-bar" role="progressbar" style="width: <?= e($operationalProgress); ?>%;" aria-valuenow="<?= e($operationalProgress); ?>" aria-valuemin="0" aria-valuemax="100"></div>
</div>
<?php foreach ($pendingActions as $item): ?>
<div class="action-item">
<div class="action-left">
<div class="action-icon <?= $item['completed'] ? 'completed' : ''; ?>">
<i class="<?= e($item['icon']); ?>"></i>
</div>
<div>
<div class="action-title"><?= e($item['title']); ?></div>
<div class="action-text"><?= e($item['text']); ?></div>
</div>
</div>
<?php if ($item['completed']): ?>
<span class="badge-soft-success">Ready</span>
<?php else: ?>
<span class="badge-soft-warning">Todo</span>
<?php endif; ?>
</div>
<?php endforeach; ?>
</div>
</div>
</div>
<?php endif; ?>
<?php if ($widgetKey === 'recent_samples'): ?>
<div class="col-12 col-xl-8 dashboard-widget" data-widget="recent_samples">
<div class="card widget-card">
<div class="card-header bg-transparent">
<div class="widget-header">
<div>
<h6 class="widget-title mb-0">Recent Samples</h6>
<p class="widget-subtitle">Latest product identity cards created by the customer</p>
</div>
<div class="d-flex align-items-center gap-2">
<a href="<?= $hasSamplesTable ? 'samples.php' : '#'; ?>" class="btn btn-sm btn-primary <?= !$hasSamplesTable ? 'disabled-action' : ''; ?>">
<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 ($hasSamplesTable && count($recentSamples) > 0): ?>
<div class="table-responsive">
<table class="table align-middle trf-table mb-0">
<thead>
<tr>
<th>Sample</th>
<th>Brand / Dept.</th>
<th>Producer / Supplier</th>
<th class="text-center">BOM</th>
<th class="text-center">Files</th>
<th>Status</th>
</tr>
</thead>
<tbody>
<?php foreach ($recentSamples as $sample): ?>
<tr>
<td>
<div class="trf-code"><?= e($sample['sample_code']); ?></div>
<div class="trf-sub">
<?= e($sample['sample_description']); ?>
<?php if (!empty($sample['article_no'])): ?>
| Article: <?= e($sample['article_no']); ?>
<?php endif; ?>
</div>
</td>
<td>
<div><?= e($sample['brand_name'] ?: '-'); ?></div>
<div class="trf-sub"><?= e($sample['department_name'] ?: '-'); ?></div>
</td>
<td>
<div><?= e($sample['producer_name'] ?: '-'); ?></div>
<div class="trf-sub"><?= e($sample['supplier_name'] ?: '-'); ?></div>
</td>
<td class="text-center">
<span class="badge-soft-muted"><?= e($sample['parts_count']); ?> parts</span>
</td>
<td class="text-center">
<span class="badge-soft-muted">
<?= e(((int) $sample['photos_count']) + ((int) $sample['documents_count'])); ?> files
</span>
</td>
<td>
<span class="badge-soft-primary">
<?= e(ucfirst(str_replace('_', ' ', $sample['status']))); ?>
</span>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php else: ?>
<div class="empty-state">
<i class="bx bx-package"></i>
<h6>No samples yet</h6>
<p>The latest product identity cards will appear here.</p>
<a href="<?= $hasSamplesTable ? 'samples.php' : '#'; ?>" class="btn btn-primary <?= !$hasSamplesTable ? 'disabled-action' : ''; ?>">
<i class="bx bx-plus-circle"></i>
Add sample
</a>
</div>
<?php endif; ?>
</div>
</div>
</div>
<?php endif; ?>
<?php if ($widgetKey === 'recent_trf_requests'): ?>
<div class="col-12 col-xl-8 dashboard-widget" data-widget="recent_trf_requests">
<div class="card widget-card">
<div class="card-header bg-transparent">
<div class="widget-header">
<div>
<h6 class="widget-title mb-0">Recent TRF Requests</h6>
<p class="widget-subtitle">Latest customer test request forms</p>
</div>
<div class="d-flex align-items-center gap-2">
<a href="<?= $hasTrfRequestsTable ? 'trf-requests.php' : '#'; ?>" class="btn btn-sm btn-primary <?= !$hasTrfRequestsTable ? 'disabled-action' : ''; ?>">
<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 ($hasTrfRequestsTable && count($recentTrfRequests) > 0): ?>
<div class="table-responsive">
<table class="table align-middle trf-table mb-0">
<thead>
<tr>
<th>TRF Code</th>
<th>Type</th>
<th>Service</th>
<th>Status</th>
<th>Created</th>
</tr>
</thead>
<tbody>
<?php foreach ($recentTrfRequests as $trf): ?>
<tr>
<td>
<div class="trf-code"><?= e($trf['trf_code']); ?></div>
<?php if (!empty($trf['external_trf_id'])): ?>
<div class="trf-sub"><?= e($trf['external_trf_id']); ?></div>
<?php endif; ?>
</td>
<td><?= e($trf['trf_type'] ?: '-'); ?></td>
<td><?= e($trf['service_required'] ?: '-'); ?></td>
<td>
<span class="badge-soft-primary">
<?= e(ucfirst(str_replace('_', ' ', $trf['status']))); ?>
</span>
</td>
<td>
<?= !empty($trf['created_at']) ? e(date('d/m/Y', strtotime($trf['created_at']))) : '-'; ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php else: ?>
<div class="empty-state">
<i class="bx bx-file"></i>
<h6>No TRF requests yet</h6>
<p>The latest digital test request forms will appear here.</p>
<a href="<?= $hasTrfRequestsTable ? 'trf-requests.php' : '#'; ?>" class="btn btn-primary <?= !$hasTrfRequestsTable ? 'disabled-action' : ''; ?>">
<i class="bx bx-plus-circle"></i>
New TRF request
</a>
</div>
<?php endif; ?>
</div>
</div>
</div>
<?php endif; ?>
<?php if ($widgetKey === 'document_repository'): ?>
<div class="col-12 col-xl-4 dashboard-widget" data-widget="document_repository">
<div class="card widget-card">
<div class="card-header bg-transparent">
<div class="widget-header">
<div>
<h6 class="widget-title mb-0">Document Repository</h6>
<p class="widget-subtitle">Technical files and report archive</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-3">
<div>
<div class="stat-label">Documents</div>
<div class="stat-value"><?= e($totalDocuments); ?></div>
<div class="stat-caption">Linked technical and laboratory files</div>
</div>
<div class="stat-icon blue">
<i class="bx bx-folder"></i>
</div>
</div>
<?php if (!$hasDocumentsTable): ?>
<div class="alert alert-light border mb-0">
<strong>Coming soon.</strong><br>
The document repository will be activated with the sample/TRF modules.
</div>
<?php else: ?>
<a href="documents.php" class="btn btn-primary w-100">
<i class="bx bx-folder-open"></i>
Open Repository
</a>
<?php endif; ?>
</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">Common customer operations</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="<?= $hasBusinessPartnersTable ? 'business-partners.php' : '#'; ?>" class="quick-action <?= !$hasBusinessPartnersTable ? 'disabled-action' : ''; ?>">
<span class="quick-action-icon">
<i class="bx bx-network-chart"></i>
</span>
<span>
<span class="quick-action-title d-block">Add business partner</span>
<span class="quick-action-text">Create producer, supplier, vendor or factory</span>
</span>
</a>
<a href="<?= $hasSamplesTable ? 'samples.php' : '#'; ?>" class="quick-action <?= !$hasSamplesTable ? 'disabled-action' : ''; ?>">
<span class="quick-action-icon">
<i class="bx bx-package"></i>
</span>
<span>
<span class="quick-action-title d-block">Create sample identity card</span>
<span class="quick-action-text">Create product data, producer, supplier and technical details</span>
</span>
</a>
<a href="<?= $hasTrfRequestsTable ? 'trf-requests.php' : '#'; ?>" class="quick-action <?= !$hasTrfRequestsTable ? 'disabled-action' : ''; ?>">
<span class="quick-action-icon">
<i class="bx bx-file"></i>
</span>
<span>
<span class="quick-action-title d-block">Create TRF request</span>
<span class="quick-action-text">Prepare a new digital test request form</span>
</span>
</a>
<a href="<?= $hasLabReportsTable ? 'lab-results.php' : '#'; ?>" class="quick-action <?= !$hasLabReportsTable ? 'disabled-action' : ''; ?>">
<span class="quick-action-icon">
<i class="bx bx-test-tube"></i>
</span>
<span>
<span class="quick-action-title d-block">View lab results</span>
<span class="quick-action-text">Check received reports and structured results</span>
</span>
</a>
<a href="<?= $hasDocumentsTable ? 'documents.php' : '#'; ?>" class="quick-action <?= !$hasDocumentsTable ? 'disabled-action' : ''; ?>">
<span class="quick-action-icon">
<i class="bx bx-folder"></i>
</span>
<span>
<span class="quick-action-title d-block">Open documents</span>
<span class="quick-action-text">Browse the technical document repository</span>
</span>
</a>
</div>
</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 trfStatusLabels = <?= json_encode($trfStatusLabels, JSON_UNESCAPED_UNICODE); ?>;
const trfStatusValues = <?= json_encode($trfStatusValues, JSON_NUMERIC_CHECK); ?>;
let trfChartInstance = null;
function renderTrfStatusChart() {
const chartElement = document.querySelector("#trfStatusChart");
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 (trfChartInstance !== null) {
trfChartInstance.destroy();
}
const options = {
series: trfStatusValues,
chart: {
type: "donut",
height: 330,
toolbar: {
show: false
},
fontFamily: "inherit"
},
labels: trfStatusLabels,
legend: {
position: "bottom"
},
dataLabels: {
enabled: true
},
plotOptions: {
pie: {
donut: {
size: "68%",
labels: {
show: true,
total: {
show: true,
label: "TRF",
formatter: function(w) {
return w.globals.seriesTotals.reduce((a, b) => a + b, 0);
}
}
}
}
}
},
stroke: {
width: 2
}
};
trfChartInstance = new ApexCharts(chartElement, options);
trfChartInstance.render();
}
renderTrfStatusChart();
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() {
renderTrfStatusChart();
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();
}
});
$("#companySwitcher").on("change", function() {
const idcompany = $(this).val();
window.location.href = window.location.pathname + "?idcompany=" + encodeURIComponent(idcompany);
});
});
</script>
</body>
</html>