Files
2026-06-15 16:10:44 +02:00

1651 lines
61 KiB
PHP

<?php include('include/headscript.php'); ?>
<?php
/*
* TRFgo - Company Users management page
* This page links Vanguard users to TRFgo companies, brands and departments.
*/
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;
}
/*
* AJAX actions
*/
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) {
$action = $_POST['action'];
try {
if ($action === 'get_brands_by_company') {
$idcompany = isset($_POST['idcompany']) ? (int) $_POST['idcompany'] : 0;
if ($idcompany <= 0) {
jsonResponse([
'success' => true,
'brands' => []
]);
}
$stmt = $db->prepare("
SELECT idbrand, brand_name, status
FROM brands
WHERE idcompany = :idcompany
ORDER BY brand_name ASC
");
$stmt->execute([':idcompany' => $idcompany]);
jsonResponse([
'success' => true,
'brands' => $stmt->fetchAll(PDO::FETCH_ASSOC)
]);
}
if ($action === 'get_departments_by_company_brand') {
$idcompany = isset($_POST['idcompany']) ? (int) $_POST['idcompany'] : 0;
$idbrand = !empty($_POST['idbrand']) ? (int) $_POST['idbrand'] : null;
if ($idcompany <= 0) {
jsonResponse([
'success' => true,
'departments' => []
]);
}
if ($idbrand !== null) {
$stmt = $db->prepare("
SELECT iddepartment, department_name, status
FROM departments
WHERE idcompany = :idcompany
AND (idbrand = :idbrand OR idbrand IS NULL)
ORDER BY department_name ASC
");
$stmt->execute([
':idcompany' => $idcompany,
':idbrand' => $idbrand,
]);
} else {
$stmt = $db->prepare("
SELECT iddepartment, department_name, status
FROM departments
WHERE idcompany = :idcompany
ORDER BY department_name ASC
");
$stmt->execute([':idcompany' => $idcompany]);
}
jsonResponse([
'success' => true,
'departments' => $stmt->fetchAll(PDO::FETCH_ASSOC)
]);
}
if ($action === 'save_company_user') {
$idcompanyuser = isset($_POST['idcompanyuser']) ? (int) $_POST['idcompanyuser'] : 0;
$iduser = isset($_POST['iduser']) ? (int) $_POST['iduser'] : 0;
$idcompany = isset($_POST['idcompany']) ? (int) $_POST['idcompany'] : 0;
$idbrand = !empty($_POST['idbrand']) ? (int) $_POST['idbrand'] : null;
$iddepartment = !empty($_POST['iddepartment']) ? (int) $_POST['iddepartment'] : null;
$userScope = $_POST['user_scope'] ?? 'company';
$companyRole = $_POST['company_role'] ?? 'viewer';
$status = $_POST['status'] ?? 'active';
$allowedScopes = ['company', 'brand', 'department'];
$allowedRoles = ['owner', 'admin', 'manager', 'operator', 'viewer', 'api_user', 'lab_user'];
$allowedStatuses = ['active', 'inactive'];
if ($iduser <= 0) {
jsonResponse([
'success' => false,
'message' => 'User is required.'
]);
}
if ($idcompany <= 0) {
jsonResponse([
'success' => false,
'message' => 'Company is required.'
]);
}
if (!in_array($userScope, $allowedScopes, true)) {
$userScope = 'company';
}
if (!in_array($companyRole, $allowedRoles, true)) {
$companyRole = 'viewer';
}
if (!in_array($status, $allowedStatuses, true)) {
$status = 'active';
}
/*
* Scope consistency.
*/
if ($userScope === 'company') {
$idbrand = null;
$iddepartment = null;
}
if ($userScope === 'brand') {
if ($idbrand === null) {
jsonResponse([
'success' => false,
'message' => 'Brand is required for brand scope.'
]);
}
$iddepartment = null;
}
if ($userScope === 'department') {
if ($iddepartment === null) {
jsonResponse([
'success' => false,
'message' => 'Department is required for department scope.'
]);
}
}
/*
* Check Vanguard user exists.
*/
$stmt = $db->prepare("
SELECT COUNT(*)
FROM auth_users
WHERE id = :iduser
");
$stmt->execute([':iduser' => $iduser]);
if ((int) $stmt->fetchColumn() === 0) {
jsonResponse([
'success' => false,
'message' => 'Selected Vanguard user does not exist.'
]);
}
/*
* Check company exists.
*/
$stmt = $db->prepare("
SELECT COUNT(*)
FROM companies
WHERE idcompany = :idcompany
");
$stmt->execute([':idcompany' => $idcompany]);
if ((int) $stmt->fetchColumn() === 0) {
jsonResponse([
'success' => false,
'message' => 'Selected company does not exist.'
]);
}
/*
* Check brand belongs to company.
*/
if ($idbrand !== null) {
$stmt = $db->prepare("
SELECT COUNT(*)
FROM brands
WHERE idbrand = :idbrand
AND idcompany = :idcompany
");
$stmt->execute([
':idbrand' => $idbrand,
':idcompany' => $idcompany,
]);
if ((int) $stmt->fetchColumn() === 0) {
jsonResponse([
'success' => false,
'message' => 'Selected brand does not belong to the selected company.'
]);
}
}
/*
* Check department belongs to company and, if brand is selected, is compatible.
*/
if ($iddepartment !== null) {
if ($idbrand !== null) {
$stmt = $db->prepare("
SELECT COUNT(*)
FROM departments
WHERE iddepartment = :iddepartment
AND idcompany = :idcompany
AND (idbrand = :idbrand OR idbrand IS NULL)
");
$stmt->execute([
':iddepartment' => $iddepartment,
':idcompany' => $idcompany,
':idbrand' => $idbrand,
]);
} else {
$stmt = $db->prepare("
SELECT COUNT(*)
FROM departments
WHERE iddepartment = :iddepartment
AND idcompany = :idcompany
");
$stmt->execute([
':iddepartment' => $iddepartment,
':idcompany' => $idcompany,
]);
}
if ((int) $stmt->fetchColumn() === 0) {
jsonResponse([
'success' => false,
'message' => 'Selected department is not compatible with the selected company/brand.'
]);
}
}
/*
* Check duplicate assignment.
*/
if ($idcompanyuser > 0) {
$stmt = $db->prepare("
SELECT COUNT(*)
FROM company_users
WHERE iduser = :iduser
AND idcompany = :idcompany
AND (
(idbrand IS NULL AND :idbrand_null = 1)
OR idbrand = :idbrand
)
AND (
(iddepartment IS NULL AND :iddepartment_null = 1)
OR iddepartment = :iddepartment
)
AND idcompanyuser <> :idcompanyuser
");
$stmt->execute([
':iduser' => $iduser,
':idcompany' => $idcompany,
':idbrand' => $idbrand,
':idbrand_null' => $idbrand === null ? 1 : 0,
':iddepartment' => $iddepartment,
':iddepartment_null' => $iddepartment === null ? 1 : 0,
':idcompanyuser' => $idcompanyuser,
]);
} else {
$stmt = $db->prepare("
SELECT COUNT(*)
FROM company_users
WHERE iduser = :iduser
AND idcompany = :idcompany
AND (
(idbrand IS NULL AND :idbrand_null = 1)
OR idbrand = :idbrand
)
AND (
(iddepartment IS NULL AND :iddepartment_null = 1)
OR iddepartment = :iddepartment
)
");
$stmt->execute([
':iduser' => $iduser,
':idcompany' => $idcompany,
':idbrand' => $idbrand,
':idbrand_null' => $idbrand === null ? 1 : 0,
':iddepartment' => $iddepartment,
':iddepartment_null' => $iddepartment === null ? 1 : 0,
]);
}
if ((int) $stmt->fetchColumn() > 0) {
jsonResponse([
'success' => false,
'message' => 'This user already has the same assignment.'
]);
}
if ($idcompanyuser > 0) {
$sql = "
UPDATE company_users
SET
iduser = :iduser,
idcompany = :idcompany,
idbrand = :idbrand,
iddepartment = :iddepartment,
user_scope = :user_scope,
company_role = :company_role,
status = :status,
updated_at = NOW()
WHERE idcompanyuser = :idcompanyuser
";
$stmt = $db->prepare($sql);
$stmt->execute([
':iduser' => $iduser,
':idcompany' => $idcompany,
':idbrand' => $idbrand,
':iddepartment' => $iddepartment,
':user_scope' => $userScope,
':company_role' => $companyRole,
':status' => $status,
':idcompanyuser' => $idcompanyuser,
]);
jsonResponse([
'success' => true,
'message' => 'User assignment updated successfully.'
]);
}
$sql = "
INSERT INTO company_users (
iduser,
idcompany,
idbrand,
iddepartment,
user_scope,
company_role,
status,
created_at,
updated_at
) VALUES (
:iduser,
:idcompany,
:idbrand,
:iddepartment,
:user_scope,
:company_role,
:status,
NOW(),
NOW()
)
";
$stmt = $db->prepare($sql);
$stmt->execute([
':iduser' => $iduser,
':idcompany' => $idcompany,
':idbrand' => $idbrand,
':iddepartment' => $iddepartment,
':user_scope' => $userScope,
':company_role' => $companyRole,
':status' => $status,
]);
jsonResponse([
'success' => true,
'message' => 'User assigned successfully.'
]);
}
if ($action === 'get_company_user') {
$idcompanyuser = isset($_POST['idcompanyuser']) ? (int) $_POST['idcompanyuser'] : 0;
if ($idcompanyuser <= 0) {
jsonResponse([
'success' => false,
'message' => 'Invalid assignment id.'
]);
}
$stmt = $db->prepare("
SELECT *
FROM company_users
WHERE idcompanyuser = :idcompanyuser
LIMIT 1
");
$stmt->execute([':idcompanyuser' => $idcompanyuser]);
$companyUser = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$companyUser) {
jsonResponse([
'success' => false,
'message' => 'Assignment not found.'
]);
}
jsonResponse([
'success' => true,
'company_user' => $companyUser
]);
}
if ($action === 'change_status') {
$idcompanyuser = isset($_POST['idcompanyuser']) ? (int) $_POST['idcompanyuser'] : 0;
$status = $_POST['status'] ?? 'inactive';
$allowedStatuses = ['active', 'inactive'];
if ($idcompanyuser <= 0 || !in_array($status, $allowedStatuses, true)) {
jsonResponse([
'success' => false,
'message' => 'Invalid request.'
]);
}
$stmt = $db->prepare("
UPDATE company_users
SET status = :status, updated_at = NOW()
WHERE idcompanyuser = :idcompanyuser
");
$stmt->execute([
':status' => $status,
':idcompanyuser' => $idcompanyuser,
]);
jsonResponse([
'success' => true,
'message' => 'Assignment status updated successfully.'
]);
}
if ($action === 'delete_company_user') {
$idcompanyuser = isset($_POST['idcompanyuser']) ? (int) $_POST['idcompanyuser'] : 0;
if ($idcompanyuser <= 0) {
jsonResponse([
'success' => false,
'message' => 'Invalid assignment id.'
]);
}
$stmt = $db->prepare("
DELETE FROM company_users
WHERE idcompanyuser = :idcompanyuser
");
$stmt->execute([':idcompanyuser' => $idcompanyuser]);
jsonResponse([
'success' => true,
'message' => 'User assignment deleted successfully.'
]);
}
jsonResponse([
'success' => false,
'message' => 'Unknown action.'
]);
} catch (Throwable $e) {
jsonResponse([
'success' => false,
'message' => $e->getMessage()
]);
}
}
/*
* Page data
*/
$users = [];
try {
$stmt = $db->query("
SELECT
u.id,
u.email,
u.first_name,
u.last_name,
u.username,
u.status,
r.display_name AS role_display_name,
r.name AS role_name
FROM auth_users u
LEFT JOIN auth_roles r ON r.id = u.role_id
ORDER BY u.first_name ASC, u.last_name ASC, u.email ASC
");
$users = $stmt->fetchAll(PDO::FETCH_ASSOC);
} catch (Throwable $e) {
$users = [];
}
$companies = [];
try {
$stmt = $db->query("
SELECT idcompany, company_name, status
FROM companies
ORDER BY company_name ASC
");
$companies = $stmt->fetchAll(PDO::FETCH_ASSOC);
} catch (Throwable $e) {
$companies = [];
}
$brands = [];
try {
$stmt = $db->query("
SELECT idbrand, idcompany, brand_name, status
FROM brands
ORDER BY brand_name ASC
");
$brands = $stmt->fetchAll(PDO::FETCH_ASSOC);
} catch (Throwable $e) {
$brands = [];
}
$departments = [];
try {
$stmt = $db->query("
SELECT iddepartment, idcompany, idbrand, department_name, status
FROM departments
ORDER BY department_name ASC
");
$departments = $stmt->fetchAll(PDO::FETCH_ASSOC);
} catch (Throwable $e) {
$departments = [];
}
$assignments = [];
try {
$stmt = $db->query("
SELECT
cu.idcompanyuser,
cu.iduser,
cu.idcompany,
cu.idbrand,
cu.iddepartment,
cu.user_scope,
cu.company_role,
cu.status,
cu.created_at,
u.email,
u.first_name,
u.last_name,
u.username,
u.status AS user_status,
r.display_name AS vanguard_role,
c.company_name,
c.status AS company_status,
b.brand_name,
b.status AS brand_status,
d.department_name,
d.status AS department_status
FROM company_users cu
INNER JOIN auth_users u ON u.id = cu.iduser
LEFT JOIN auth_roles r ON r.id = u.role_id
INNER JOIN companies c ON c.idcompany = cu.idcompany
LEFT JOIN brands b ON b.idbrand = cu.idbrand
LEFT JOIN departments d ON d.iddepartment = cu.iddepartment
ORDER BY c.company_name ASC, u.first_name ASC, u.last_name ASC, u.email ASC
");
$assignments = $stmt->fetchAll(PDO::FETCH_ASSOC);
} catch (Throwable $e) {
$assignments = [];
}
$pageTitle = 'Company Users';
?>
<!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-muted: #64748b;
--trfgo-border: #e5e7eb;
--trfgo-soft-blue: #eff6ff;
--trfgo-soft-green: #ecfdf5;
--trfgo-soft-orange: #fff7ed;
--trfgo-soft-purple: #f5f3ff;
}
.page-intro-card {
border: 0;
border-radius: 20px;
overflow: hidden;
background:
radial-gradient(circle at top right, rgba(59, 130, 246, 0.22), transparent 30%),
linear-gradient(135deg, #0f172a 0%, #1d4ed8 60%, #38bdf8 100%);
color: #fff;
box-shadow: 0 18px 45px rgba(15, 23, 42, 0.18);
}
.page-intro-card .card-body {
padding: 26px;
}
.page-intro-card h1,
.page-intro-card h2,
.page-intro-card h3,
.page-intro-card h4,
.page-intro-card h5,
.page-intro-card h6 {
color: #ffffff;
}
.intro-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.94);
font-size: 12px;
font-weight: 700;
letter-spacing: 0.04em;
text-transform: uppercase;
margin-bottom: 12px;
}
.intro-title {
font-size: 28px;
font-weight: 800;
letter-spacing: -0.03em;
margin-bottom: 8px;
color: #ffffff;
}
.intro-text {
max-width: 780px;
color: rgba(255, 255, 255, 0.82);
margin-bottom: 0;
line-height: 1.6;
}
.btn-intro {
border-radius: 12px;
padding: 10px 16px;
font-weight: 700;
display: inline-flex;
align-items: center;
gap: 8px;
border: 0;
background: #fff;
color: #1d4ed8;
box-shadow: 0 12px 28px rgba(15, 23, 42, 0.18);
}
.summary-card {
border: 0;
border-radius: 18px;
box-shadow: 0 10px 30px rgba(15, 23, 42, 0.06);
}
.summary-icon {
width: 44px;
height: 44px;
border-radius: 14px;
display: inline-flex;
align-items: center;
justify-content: center;
font-size: 24px;
}
.summary-icon.blue {
background: var(--trfgo-soft-blue);
color: #2563eb;
}
.summary-icon.green {
background: var(--trfgo-soft-green);
color: #059669;
}
.summary-icon.orange {
background: var(--trfgo-soft-orange);
color: #ea580c;
}
.summary-icon.purple {
background: var(--trfgo-soft-purple);
color: #7c3aed;
}
.summary-label {
color: var(--trfgo-muted);
font-size: 13px;
font-weight: 700;
margin-bottom: 3px;
}
.summary-value {
color: #0f172a;
font-size: 26px;
font-weight: 800;
line-height: 1.1;
}
.content-card {
border: 0;
border-radius: 18px;
box-shadow: 0 10px 30px rgba(15, 23, 42, 0.06);
}
.section-title {
color: #0f172a;
font-size: 18px;
font-weight: 800;
margin-bottom: 3px;
}
.section-subtitle {
color: var(--trfgo-muted);
font-size: 13px;
margin-bottom: 0;
}
.assignment-table th {
color: #64748b;
font-size: 12px;
text-transform: uppercase;
letter-spacing: 0.04em;
border-bottom: 1px solid var(--trfgo-border) !important;
white-space: nowrap;
}
.assignment-table td {
vertical-align: middle;
border-color: #eef2f7;
}
.user-name {
font-weight: 800;
color: #0f172a;
}
.user-sub {
color: var(--trfgo-muted);
font-size: 12px;
}
.scope-stack {
line-height: 1.35;
}
.scope-main {
font-weight: 800;
color: #0f172a;
}
.scope-sub {
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;
}
.badge-soft-purple {
background: #ede9fe;
color: #6d28d9;
border-radius: 999px;
font-weight: 700;
padding: 6px 10px;
white-space: nowrap;
}
.btn-action {
width: 34px;
height: 34px;
border-radius: 11px;
display: inline-flex;
align-items: center;
justify-content: center;
border: 1px solid #e5e7eb;
background: #fff;
color: #475569;
transition: all 0.16s ease;
}
.btn-action:hover {
color: #1d4ed8;
border-color: rgba(37, 99, 235, 0.35);
background: #eff6ff;
}
.btn-action.danger:hover {
color: #dc2626;
border-color: rgba(220, 38, 38, 0.30);
background: #fef2f2;
}
.modal-content {
border: 0;
border-radius: 20px;
box-shadow: 0 24px 80px rgba(15, 23, 42, 0.22);
}
.modal-header {
border-bottom: 1px solid #eef2f7;
}
.modal-title {
font-weight: 800;
color: #0f172a;
}
.form-label {
font-size: 13px;
font-weight: 700;
color: #334155;
}
.form-control,
.form-select {
border-radius: 12px;
border-color: #dbe3ef;
}
.form-control:focus,
.form-select:focus {
border-color: rgba(37, 99, 235, 0.45);
box-shadow: 0 0 0 0.20rem rgba(37, 99, 235, 0.12);
}
.required-dot {
color: #dc2626;
}
.scope-help {
background: #f8fafc;
border: 1px solid #e5e7eb;
border-radius: 14px;
padding: 12px 14px;
color: #475569;
font-size: 13px;
}
@media (max-width: 767px) {
.intro-title {
font-size: 24px;
}
.btn-intro {
width: 100%;
justify-content: center;
margin-top: 15px;
}
}
</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="card page-intro-card mb-4">
<div class="card-body">
<div class="d-flex align-items-center justify-content-between flex-wrap gap-3">
<div>
<div class="intro-eyebrow">
<i class="bx bx-user-check"></i>
TRFgo Access Control
</div>
<h1 class="intro-title">Company Users</h1>
<p class="intro-text">
Link Vanguard users to TRFgo companies, brands and departments.
Vanguard remains responsible for authentication; this page defines operational access and data visibility.
</p>
</div>
<div>
<button type="button" class="btn btn-intro" id="btnAddAssignment">
<i class="bx bx-user-plus"></i>
Assign User
</button>
</div>
</div>
</div>
</div>
<?php
$totalAssignments = count($assignments);
$activeAssignments = count(array_filter($assignments, fn($row) => $row['status'] === 'active'));
$companyScopeCount = count(array_filter($assignments, fn($row) => $row['user_scope'] === 'company'));
$departmentScopeCount = count(array_filter($assignments, fn($row) => $row['user_scope'] === 'department'));
?>
<div class="row row-cols-1 row-cols-md-4 mb-3">
<div class="col">
<div class="card summary-card">
<div class="card-body">
<div class="d-flex align-items-center justify-content-between">
<div>
<div class="summary-label">Assignments</div>
<div class="summary-value"><?= e($totalAssignments); ?></div>
</div>
<div class="summary-icon blue">
<i class="bx bx-user-check"></i>
</div>
</div>
</div>
</div>
</div>
<div class="col">
<div class="card summary-card">
<div class="card-body">
<div class="d-flex align-items-center justify-content-between">
<div>
<div class="summary-label">Active</div>
<div class="summary-value"><?= e($activeAssignments); ?></div>
</div>
<div class="summary-icon green">
<i class="bx bx-check-circle"></i>
</div>
</div>
</div>
</div>
</div>
<div class="col">
<div class="card summary-card">
<div class="card-body">
<div class="d-flex align-items-center justify-content-between">
<div>
<div class="summary-label">Company Scope</div>
<div class="summary-value"><?= e($companyScopeCount); ?></div>
</div>
<div class="summary-icon orange">
<i class="bx bx-buildings"></i>
</div>
</div>
</div>
</div>
</div>
<div class="col">
<div class="card summary-card">
<div class="card-body">
<div class="d-flex align-items-center justify-content-between">
<div>
<div class="summary-label">Department Scope</div>
<div class="summary-value"><?= e($departmentScopeCount); ?></div>
</div>
<div class="summary-icon purple">
<i class="bx bx-sitemap"></i>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="card content-card">
<div class="card-header bg-transparent">
<div class="d-flex align-items-center justify-content-between flex-wrap gap-2">
<div>
<h6 class="section-title mb-0">User Assignments</h6>
<p class="section-subtitle">Vanguard users linked to TRFgo operating scopes</p>
</div>
<button type="button" class="btn btn-primary btn-sm" id="btnAddAssignmentSmall">
<i class="bx bx-user-plus"></i>
Assign User
</button>
</div>
</div>
<div class="card-body">
<?php if (count($companies) === 0): ?>
<div class="alert alert-warning mb-3">
<strong>No companies available.</strong>
Create at least one company before assigning users.
</div>
<?php endif; ?>
<?php if (count($users) === 0): ?>
<div class="alert alert-warning mb-3">
<strong>No Vanguard users available.</strong>
Create users in Vanguard before assigning TRFgo access.
</div>
<?php endif; ?>
<div class="table-responsive">
<table id="assignmentsTable" class="table table-striped table-hover align-middle assignment-table" style="width:100%">
<thead>
<tr>
<th>User</th>
<th>Company</th>
<th>Scope</th>
<th>Role</th>
<th>Vanguard Role</th>
<th>Status</th>
<th>Created</th>
<th class="text-end">Actions</th>
</tr>
</thead>
<tbody>
<?php foreach ($assignments as $assignment): ?>
<?php
$fullName = trim(($assignment['first_name'] ?? '') . ' ' . ($assignment['last_name'] ?? ''));
if ($fullName === '') {
$fullName = $assignment['username'] ?: $assignment['email'];
}
$scopeLabel = ucfirst($assignment['user_scope']);
$roleLabel = str_replace('_', ' ', ucfirst($assignment['company_role']));
?>
<tr>
<td>
<div class="user-name"><?= e($fullName); ?></div>
<div class="user-sub"><?= e($assignment['email']); ?></div>
</td>
<td>
<div class="scope-main"><?= e($assignment['company_name']); ?></div>
<?php if ($assignment['company_status'] !== 'active'): ?>
<div class="scope-sub">Company status: <?= e($assignment['company_status']); ?></div>
<?php endif; ?>
</td>
<td>
<div class="scope-stack">
<?php if ($assignment['user_scope'] === 'company'): ?>
<span class="badge-soft-primary">Company</span>
<div class="scope-sub mt-1">Full company visibility</div>
<?php elseif ($assignment['user_scope'] === 'brand'): ?>
<span class="badge-soft-purple">Brand</span>
<div class="scope-sub mt-1"><?= e($assignment['brand_name'] ?: '-'); ?></div>
<?php else: ?>
<span class="badge-soft-warning">Department</span>
<div class="scope-sub mt-1">
<?= e($assignment['department_name'] ?: '-'); ?>
<?php if (!empty($assignment['brand_name'])): ?>
/ <?= e($assignment['brand_name']); ?>
<?php endif; ?>
</div>
<?php endif; ?>
</div>
</td>
<td>
<span class="badge-soft-muted"><?= e($roleLabel); ?></span>
</td>
<td>
<?= !empty($assignment['vanguard_role']) ? e($assignment['vanguard_role']) : '<span class="text-muted">-</span>'; ?>
</td>
<td>
<?php if ($assignment['status'] === 'active'): ?>
<span class="badge-soft-success">Active</span>
<?php else: ?>
<span class="badge-soft-muted">Inactive</span>
<?php endif; ?>
</td>
<td>
<?= !empty($assignment['created_at']) ? e(date('d/m/Y', strtotime($assignment['created_at']))) : '-'; ?>
</td>
<td class="text-end">
<button type="button"
class="btn-action btnEditAssignment"
data-id="<?= e($assignment['idcompanyuser']); ?>"
title="Edit">
<i class="bx bx-edit-alt"></i>
</button>
<?php if ($assignment['status'] === 'active'): ?>
<button type="button"
class="btn-action btnChangeStatus"
data-id="<?= e($assignment['idcompanyuser']); ?>"
data-status="inactive"
title="Set inactive">
<i class="bx bx-toggle-right"></i>
</button>
<?php else: ?>
<button type="button"
class="btn-action btnChangeStatus"
data-id="<?= e($assignment['idcompanyuser']); ?>"
data-status="active"
title="Set active">
<i class="bx bx-toggle-left"></i>
</button>
<?php endif; ?>
<button type="button"
class="btn-action danger btnDeleteAssignment"
data-id="<?= e($assignment['idcompanyuser']); ?>"
title="Delete">
<i class="bx bx-trash"></i>
</button>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
</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>
<div class="modal fade" id="assignmentModal" tabindex="-1" aria-labelledby="assignmentModalLabel" aria-hidden="true">
<div class="modal-dialog modal-xl modal-dialog-centered">
<form id="assignmentForm" class="modal-content">
<input type="hidden" name="action" value="save_company_user">
<input type="hidden" name="idcompanyuser" id="idcompanyuser" value="0">
<div class="modal-header">
<div>
<h5 class="modal-title" id="assignmentModalLabel">Assign User</h5>
<small class="text-muted">Link a Vanguard user to a TRFgo company, brand or department.</small>
</div>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="scope-help mb-3">
<strong>Important:</strong> Vanguard manages login, password and main role.
TRFgo assignment defines which company data the user can access.
</div>
<div class="row g-3">
<div class="col-12 col-md-6">
<label class="form-label">Vanguard User <span class="required-dot">*</span></label>
<select class="form-select" name="iduser" id="iduser" required>
<option value="">Select user</option>
<?php foreach ($users as $userRow): ?>
<?php
$displayName = trim(($userRow['first_name'] ?? '') . ' ' . ($userRow['last_name'] ?? ''));
if ($displayName === '') {
$displayName = $userRow['username'] ?: $userRow['email'];
}
?>
<option value="<?= e($userRow['id']); ?>">
<?= e($displayName); ?> - <?= e($userRow['email']); ?>
</option>
<?php endforeach; ?>
</select>
</div>
<div class="col-12 col-md-6">
<label class="form-label">Company <span class="required-dot">*</span></label>
<select class="form-select" name="idcompany" id="idcompany" required>
<option value="">Select company</option>
<?php foreach ($companies as $company): ?>
<option value="<?= e($company['idcompany']); ?>">
<?= e($company['company_name']); ?>
<?= $company['status'] !== 'active' ? ' - ' . e($company['status']) : ''; ?>
</option>
<?php endforeach; ?>
</select>
</div>
<div class="col-12 col-md-4">
<label class="form-label">Scope <span class="required-dot">*</span></label>
<select class="form-select" name="user_scope" id="user_scope" required>
<option value="company">Company</option>
<option value="brand">Brand</option>
<option value="department">Department</option>
</select>
</div>
<div class="col-12 col-md-4 scope-brand-field">
<label class="form-label">Brand</label>
<select class="form-select" name="idbrand" id="idbrand">
<option value="">No brand</option>
<?php foreach ($brands as $brand): ?>
<option value="<?= e($brand['idbrand']); ?>" data-company="<?= e($brand['idcompany']); ?>">
<?= e($brand['brand_name']); ?>
<?= $brand['status'] !== 'active' ? ' - ' . e($brand['status']) : ''; ?>
</option>
<?php endforeach; ?>
</select>
</div>
<div class="col-12 col-md-4 scope-department-field">
<label class="form-label">Department</label>
<select class="form-select" name="iddepartment" id="iddepartment">
<option value="">No department</option>
<?php foreach ($departments as $department): ?>
<option value="<?= e($department['iddepartment']); ?>"
data-company="<?= e($department['idcompany']); ?>"
data-brand="<?= e($department['idbrand']); ?>">
<?= e($department['department_name']); ?>
<?= $department['status'] !== 'active' ? ' - ' . e($department['status']) : ''; ?>
</option>
<?php endforeach; ?>
</select>
</div>
<div class="col-12 col-md-6">
<label class="form-label">TRFgo Role <span class="required-dot">*</span></label>
<select class="form-select" name="company_role" id="company_role" required>
<option value="owner">Owner</option>
<option value="admin">Admin</option>
<option value="manager">Manager</option>
<option value="operator">Operator</option>
<option value="viewer" selected>Viewer</option>
<option value="api_user">API User</option>
<option value="lab_user">Lab User</option>
</select>
</div>
<div class="col-12 col-md-6">
<label class="form-label">Status</label>
<select class="form-select" name="status" id="status">
<option value="active">Active</option>
<option value="inactive">Inactive</option>
</select>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-light" data-bs-dismiss="modal">Cancel</button>
<button type="submit" class="btn btn-primary" id="btnSaveAssignment">
<i class="bx bx-save"></i>
Save Assignment
</button>
</div>
</form>
</div>
</div>
<?php include('jsinclude.php'); ?>
<script>
$(document).ready(function() {
if ($.fn.DataTable) {
$('#assignmentsTable').DataTable({
pageLength: 25,
order: [
[1, 'asc'],
[0, 'asc']
],
responsive: true,
language: {
search: "",
searchPlaceholder: "Search user assignments..."
}
});
}
const assignmentModalElement = document.getElementById('assignmentModal');
const assignmentModal = new bootstrap.Modal(assignmentModalElement);
function resetAssignmentForm() {
$('#assignmentForm')[0].reset();
$('#idcompanyuser').val('0');
$('#assignmentModalLabel').text('Assign User');
$('#user_scope').val('company');
$('#company_role').val('viewer');
$('#status').val('active');
updateScopeFields();
filterBrandOptions('');
filterDepartmentOptions('', '');
}
function showAlert(type, message) {
if (typeof Swal !== 'undefined') {
Swal.fire({
icon: type,
title: type === 'success' ? 'Done' : 'Attention',
text: message,
confirmButtonColor: '#2563eb'
});
return;
}
alert(message);
}
function reloadAfterSuccess(message) {
if (typeof Swal !== 'undefined') {
Swal.fire({
icon: 'success',
title: 'Done',
text: message,
confirmButtonColor: '#2563eb'
}).then(function() {
window.location.reload();
});
return;
}
alert(message);
window.location.reload();
}
function filterBrandOptions(idcompany, selectedBrandId = '') {
$('#idbrand option').each(function() {
const optionCompany = $(this).data('company');
if ($(this).val() === '') {
$(this).show();
return;
}
if (!idcompany || String(optionCompany) === String(idcompany)) {
$(this).show();
} else {
$(this).hide();
}
});
if (selectedBrandId) {
$('#idbrand').val(selectedBrandId);
} else {
$('#idbrand').val('');
}
}
function filterDepartmentOptions(idcompany, idbrand, selectedDepartmentId = '') {
$('#iddepartment option').each(function() {
const optionCompany = $(this).data('company');
const optionBrand = $(this).data('brand');
if ($(this).val() === '') {
$(this).show();
return;
}
if (!idcompany) {
$(this).hide();
return;
}
if (String(optionCompany) !== String(idcompany)) {
$(this).hide();
return;
}
if (!idbrand || String(optionBrand) === String(idbrand) || optionBrand === '' || typeof optionBrand === 'undefined') {
$(this).show();
} else {
$(this).hide();
}
});
if (selectedDepartmentId) {
$('#iddepartment').val(selectedDepartmentId);
} else {
$('#iddepartment').val('');
}
}
function updateScopeFields() {
const scope = $('#user_scope').val();
if (scope === 'company') {
$('.scope-brand-field').hide();
$('.scope-department-field').hide();
$('#idbrand').val('');
$('#iddepartment').val('');
}
if (scope === 'brand') {
$('.scope-brand-field').show();
$('.scope-department-field').hide();
$('#iddepartment').val('');
}
if (scope === 'department') {
$('.scope-brand-field').show();
$('.scope-department-field').show();
}
}
$('#idcompany').on('change', function() {
const idcompany = $(this).val();
filterBrandOptions(idcompany);
filterDepartmentOptions(idcompany, '');
});
$('#idbrand').on('change', function() {
const idcompany = $('#idcompany').val();
const idbrand = $(this).val();
filterDepartmentOptions(idcompany, idbrand);
});
$('#user_scope').on('change', function() {
updateScopeFields();
});
$('#btnAddAssignment, #btnAddAssignmentSmall').on('click', function() {
resetAssignmentForm();
assignmentModal.show();
});
$('.btnEditAssignment').on('click', function() {
const idcompanyuser = $(this).data('id');
$.ajax({
url: 'company-users.php',
type: 'POST',
dataType: 'json',
data: {
action: 'get_company_user',
idcompanyuser: idcompanyuser
},
success: function(response) {
if (!response.success) {
showAlert('error', response.message || 'Unable to load assignment.');
return;
}
const assignment = response.company_user;
$('#assignmentModalLabel').text('Edit Assignment');
$('#idcompanyuser').val(assignment.idcompanyuser);
$('#iduser').val(assignment.iduser);
$('#idcompany').val(assignment.idcompany);
$('#user_scope').val(assignment.user_scope);
$('#company_role').val(assignment.company_role);
$('#status').val(assignment.status);
filterBrandOptions(assignment.idcompany, assignment.idbrand);
filterDepartmentOptions(assignment.idcompany, assignment.idbrand, assignment.iddepartment);
updateScopeFields();
assignmentModal.show();
},
error: function() {
showAlert('error', 'Server error while loading assignment.');
}
});
});
$('#assignmentForm').on('submit', function(e) {
e.preventDefault();
$('#btnSaveAssignment').prop('disabled', true).html('<i class="bx bx-loader-alt bx-spin"></i> Saving...');
$.ajax({
url: 'company-users.php',
type: 'POST',
dataType: 'json',
data: $(this).serialize(),
success: function(response) {
$('#btnSaveAssignment').prop('disabled', false).html('<i class="bx bx-save"></i> Save Assignment');
if (!response.success) {
showAlert('error', response.message || 'Unable to save assignment.');
return;
}
assignmentModal.hide();
reloadAfterSuccess(response.message || 'Assignment saved successfully.');
},
error: function() {
$('#btnSaveAssignment').prop('disabled', false).html('<i class="bx bx-save"></i> Save Assignment');
showAlert('error', 'Server error while saving assignment.');
}
});
});
$('.btnChangeStatus').on('click', function() {
const idcompanyuser = $(this).data('id');
const status = $(this).data('status');
const message = status === 'active' ?
'Do you want to activate this assignment?' :
'Do you want to set this assignment as inactive?';
if (typeof Swal !== 'undefined') {
Swal.fire({
icon: 'question',
title: 'Confirm status change',
text: message,
showCancelButton: true,
confirmButtonColor: '#2563eb',
cancelButtonColor: '#64748b',
confirmButtonText: 'Yes, continue'
}).then(function(result) {
if (result.isConfirmed) {
changeAssignmentStatus(idcompanyuser, status);
}
});
return;
}
if (confirm(message)) {
changeAssignmentStatus(idcompanyuser, status);
}
});
function changeAssignmentStatus(idcompanyuser, status) {
$.ajax({
url: 'company-users.php',
type: 'POST',
dataType: 'json',
data: {
action: 'change_status',
idcompanyuser: idcompanyuser,
status: status
},
success: function(response) {
if (!response.success) {
showAlert('error', response.message || 'Unable to update status.');
return;
}
reloadAfterSuccess(response.message || 'Status updated successfully.');
},
error: function() {
showAlert('error', 'Server error while updating status.');
}
});
}
$('.btnDeleteAssignment').on('click', function() {
const idcompanyuser = $(this).data('id');
if (typeof Swal !== 'undefined') {
Swal.fire({
icon: 'warning',
title: 'Delete assignment?',
text: 'The user will no longer have this TRFgo company access.',
showCancelButton: true,
confirmButtonColor: '#dc2626',
cancelButtonColor: '#64748b',
confirmButtonText: 'Yes, delete'
}).then(function(result) {
if (result.isConfirmed) {
deleteAssignment(idcompanyuser);
}
});
return;
}
if (confirm('Delete assignment?')) {
deleteAssignment(idcompanyuser);
}
});
function deleteAssignment(idcompanyuser) {
$.ajax({
url: 'company-users.php',
type: 'POST',
dataType: 'json',
data: {
action: 'delete_company_user',
idcompanyuser: idcompanyuser
},
success: function(response) {
if (!response.success) {
showAlert('error', response.message || 'Unable to delete assignment.');
return;
}
reloadAfterSuccess(response.message || 'Assignment deleted successfully.');
},
error: function() {
showAlert('error', 'Server error while deleting assignment.');
}
});
}
updateScopeFields();
});
</script>
</body>
</html>