|
@@ -0,0 +1,351 @@
|
|
|
|
|
+<?php
|
|
|
|
|
+/**
|
|
|
|
|
+ * dashboard/admin/consultant-clients.php
|
|
|
|
|
+ *
|
|
|
|
|
+ * Admin tool: assign client_records to consultant accounts.
|
|
|
|
|
+ *
|
|
|
|
|
+ * POST actions (CSRF protected):
|
|
|
|
|
+ * action=assign consultant_id + client_id → insert into consultant_clients
|
|
|
|
|
+ * action=remove assignment_id → delete from consultant_clients
|
|
|
|
|
+ * action=set_type user_id + user_type → update users.user_type
|
|
|
|
|
+ */
|
|
|
|
|
+
|
|
|
|
|
+require_once __DIR__ . '/../../config/database.php';
|
|
|
|
|
+require_once __DIR__ . '/../../lib/auth.php';
|
|
|
|
|
+require_once __DIR__ . '/../../lib/csrf.php';
|
|
|
|
|
+
|
|
|
|
|
+if (session_status() === PHP_SESSION_NONE) {
|
|
|
|
|
+ session_start();
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+requireAdmin();
|
|
|
|
|
+
|
|
|
|
|
+$pdo = getDBConnection();
|
|
|
|
|
+$flash = '';
|
|
|
|
|
+$flashType = 'success';
|
|
|
|
|
+
|
|
|
|
|
+// ── POST handler ──────────────────────────────────────────────────────────────
|
|
|
|
|
+if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|
|
|
|
+ if (!verifyCsrfToken($_POST['csrf_token'] ?? '')) {
|
|
|
|
|
+ $flash = 'Invalid request token. Please try again.';
|
|
|
|
|
+ $flashType = 'danger';
|
|
|
|
|
+ } else {
|
|
|
|
|
+ $action = $_POST['action'] ?? '';
|
|
|
|
|
+
|
|
|
|
|
+ if ($action === 'assign') {
|
|
|
|
|
+ $consultantId = (int) ($_POST['consultant_id'] ?? 0);
|
|
|
|
|
+ $clientId = (int) ($_POST['client_id'] ?? 0);
|
|
|
|
|
+ if ($consultantId && $clientId) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ $pdo->prepare("
|
|
|
|
|
+ INSERT IGNORE INTO consultant_clients (consultant_id, client_id, assigned_by)
|
|
|
|
|
+ VALUES (?, ?, ?)
|
|
|
|
|
+ ")->execute([$consultantId, $clientId, getCurrentUserId()]);
|
|
|
|
|
+ $flash = 'Client assigned successfully.';
|
|
|
|
|
+ } catch (PDOException $e) {
|
|
|
|
|
+ $flash = 'Assignment failed: ' . htmlspecialchars($e->getMessage(), ENT_QUOTES, 'UTF-8');
|
|
|
|
|
+ $flashType = 'danger';
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
|
|
+ $flash = 'Please select both a consultant and a client.';
|
|
|
|
|
+ $flashType = 'warning';
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ } elseif ($action === 'remove') {
|
|
|
|
|
+ $assignmentId = (int) ($_POST['assignment_id'] ?? 0);
|
|
|
|
|
+ if ($assignmentId) {
|
|
|
|
|
+ $pdo->prepare("DELETE FROM consultant_clients WHERE id = ?")->execute([$assignmentId]);
|
|
|
|
|
+ $flash = 'Assignment removed.';
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ } elseif ($action === 'set_type') {
|
|
|
|
|
+ $targetUserId = (int) ($_POST['user_id'] ?? 0);
|
|
|
|
|
+ $userType = $_POST['user_type'] ?? '';
|
|
|
|
|
+ if ($targetUserId && in_array($userType, ['client', 'consultant', 'admin'], true)) {
|
|
|
|
|
+ $pdo->prepare("UPDATE users SET user_type = ? WHERE id = ?")
|
|
|
|
|
+ ->execute([$userType, $targetUserId]);
|
|
|
|
|
+ $flash = 'User type updated.';
|
|
|
|
|
+ } else {
|
|
|
|
|
+ $flash = 'Invalid user type selection.';
|
|
|
|
|
+ $flashType = 'warning';
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// ── Load data ─────────────────────────────────────────────────────────────────
|
|
|
|
|
+
|
|
|
|
|
+// All users with their type
|
|
|
|
|
+$allUsers = $pdo->query("
|
|
|
|
|
+ SELECT id, fullname, email, user_type
|
|
|
|
|
+ FROM users
|
|
|
|
|
+ WHERE active = 1
|
|
|
|
|
+ ORDER BY user_type, fullname
|
|
|
|
|
+")->fetchAll();
|
|
|
|
|
+
|
|
|
|
|
+// Consultants with their assigned clients
|
|
|
|
|
+$consultants = $pdo->query("
|
|
|
|
|
+ SELECT u.id, u.fullname, u.email
|
|
|
|
|
+ FROM users u
|
|
|
|
|
+ WHERE u.user_type IN ('consultant','admin') AND u.active = 1
|
|
|
|
|
+ ORDER BY u.fullname
|
|
|
|
|
+")->fetchAll();
|
|
|
|
|
+
|
|
|
|
|
+// All client records (for the assign dropdown)
|
|
|
|
|
+$allClients = $pdo->query("
|
|
|
|
|
+ SELECT id, client, company
|
|
|
|
|
+ FROM client_records
|
|
|
|
|
+ ORDER BY client ASC
|
|
|
|
|
+")->fetchAll();
|
|
|
|
|
+
|
|
|
|
|
+// Assignments: consultant_id → [{assignment_id, client_id, client_name, company, assigned_at}]
|
|
|
|
|
+$assignmentRows = $pdo->query("
|
|
|
|
|
+ SELECT cc.id AS assignment_id, cc.consultant_id, cc.assigned_at,
|
|
|
|
|
+ cr.id AS client_id, cr.client, cr.company
|
|
|
|
|
+ FROM consultant_clients cc
|
|
|
|
|
+ JOIN client_records cr ON cr.id = cc.client_id
|
|
|
|
|
+ ORDER BY cc.consultant_id, cr.client
|
|
|
|
|
+")->fetchAll();
|
|
|
|
|
+
|
|
|
|
|
+$assignmentsByConsultant = [];
|
|
|
|
|
+foreach ($assignmentRows as $row) {
|
|
|
|
|
+ $assignmentsByConsultant[$row['consultant_id']][] = $row;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// ── Page render ───────────────────────────────────────────────────────────────
|
|
|
|
|
+
|
|
|
|
|
+$pageTitle = 'Manage Consultants';
|
|
|
|
|
+$siteName = 'Crop Monitor';
|
|
|
|
|
+
|
|
|
|
|
+include __DIR__ . '/../../layouts/header.php';
|
|
|
|
|
+include __DIR__ . '/../../layouts/navbar.php';
|
|
|
|
|
+?>
|
|
|
|
|
+
|
|
|
|
|
+<div id="layoutSidenav">
|
|
|
|
|
+ <div id="layoutSidenav_nav">
|
|
|
|
|
+ <?php include __DIR__ . '/../../layouts/consultant-sidebar.php'; ?>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div id="layoutSidenav_content">
|
|
|
|
|
+ <main>
|
|
|
|
|
+ <div class="container-fluid px-4">
|
|
|
|
|
+
|
|
|
|
|
+ <h1 class="mt-4"><?= htmlspecialchars($pageTitle, ENT_QUOTES, 'UTF-8') ?></h1>
|
|
|
|
|
+ <ol class="breadcrumb mb-4">
|
|
|
|
|
+ <li class="breadcrumb-item"><a href="/dashboard/consultant/index.php">Consultant</a></li>
|
|
|
|
|
+ <li class="breadcrumb-item active">Manage Consultants</li>
|
|
|
|
|
+ </ol>
|
|
|
|
|
+
|
|
|
|
|
+ <?php if ($flash): ?>
|
|
|
|
|
+ <div class="alert alert-<?= $flashType ?> alert-dismissible fade show" role="alert">
|
|
|
|
|
+ <?= htmlspecialchars($flash, ENT_QUOTES, 'UTF-8') ?>
|
|
|
|
|
+ <button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <?php endif; ?>
|
|
|
|
|
+
|
|
|
|
|
+ <div class="row g-4">
|
|
|
|
|
+
|
|
|
|
|
+ <!-- ══ LEFT: User roles ═══════════════════════════════════ -->
|
|
|
|
|
+ <div class="col-xl-4">
|
|
|
|
|
+ <div class="card shadow-sm border-0 h-100">
|
|
|
|
|
+ <div class="card-header fw-semibold">
|
|
|
|
|
+ <i class="fas fa-users-cog me-1"></i> User Roles
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="card-body p-0">
|
|
|
|
|
+ <table class="table table-sm table-hover mb-0 align-middle">
|
|
|
|
|
+ <thead class="table-light">
|
|
|
|
|
+ <tr>
|
|
|
|
|
+ <th>Name</th>
|
|
|
|
|
+ <th>Email</th>
|
|
|
|
|
+ <th>Role</th>
|
|
|
|
|
+ <th></th>
|
|
|
|
|
+ </tr>
|
|
|
|
|
+ </thead>
|
|
|
|
|
+ <tbody>
|
|
|
|
|
+ <?php foreach ($allUsers as $u): ?>
|
|
|
|
|
+ <tr>
|
|
|
|
|
+ <td class="fw-semibold small">
|
|
|
|
|
+ <?= htmlspecialchars($u['fullname'], ENT_QUOTES, 'UTF-8') ?>
|
|
|
|
|
+ </td>
|
|
|
|
|
+ <td class="text-muted small">
|
|
|
|
|
+ <?= htmlspecialchars($u['email'], ENT_QUOTES, 'UTF-8') ?>
|
|
|
|
|
+ </td>
|
|
|
|
|
+ <td>
|
|
|
|
|
+ <?php
|
|
|
|
|
+ $badge = match($u['user_type']) {
|
|
|
|
|
+ 'admin' => 'danger',
|
|
|
|
|
+ 'consultant' => 'info',
|
|
|
|
|
+ default => 'secondary',
|
|
|
|
|
+ };
|
|
|
|
|
+ ?>
|
|
|
|
|
+ <span class="badge bg-<?= $badge ?>">
|
|
|
|
|
+ <?= htmlspecialchars(ucfirst($u['user_type']), ENT_QUOTES, 'UTF-8') ?>
|
|
|
|
|
+ </span>
|
|
|
|
|
+ </td>
|
|
|
|
|
+ <td>
|
|
|
|
|
+ <button class="btn btn-xs btn-sm btn-outline-secondary"
|
|
|
|
|
+ data-bs-toggle="modal"
|
|
|
|
|
+ data-bs-target="#modalSetType"
|
|
|
|
|
+ data-user-id="<?= (int)$u['id'] ?>"
|
|
|
|
|
+ data-user-name="<?= htmlspecialchars($u['fullname'], ENT_QUOTES, 'UTF-8') ?>"
|
|
|
|
|
+ data-user-type="<?= htmlspecialchars($u['user_type'], ENT_QUOTES, 'UTF-8') ?>">
|
|
|
|
|
+ Edit
|
|
|
|
|
+ </button>
|
|
|
|
|
+ </td>
|
|
|
|
|
+ </tr>
|
|
|
|
|
+ <?php endforeach; ?>
|
|
|
|
|
+ </tbody>
|
|
|
|
|
+ </table>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- ══ RIGHT: Consultant assignments ══════════════════════ -->
|
|
|
|
|
+ <div class="col-xl-8">
|
|
|
|
|
+
|
|
|
|
|
+ <!-- Assign form -->
|
|
|
|
|
+ <div class="card shadow-sm border-0 mb-4">
|
|
|
|
|
+ <div class="card-header fw-semibold">
|
|
|
|
|
+ <i class="fas fa-user-plus me-1"></i> Assign Client to Consultant
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="card-body">
|
|
|
|
|
+ <form method="POST" class="row g-2 align-items-end">
|
|
|
|
|
+ <input type="hidden" name="csrf_token" value="<?= generateCsrfToken() ?>">
|
|
|
|
|
+ <input type="hidden" name="action" value="assign">
|
|
|
|
|
+
|
|
|
|
|
+ <div class="col-md-5">
|
|
|
|
|
+ <label class="form-label fw-semibold small">Consultant</label>
|
|
|
|
|
+ <select name="consultant_id" class="form-select form-select-sm" required>
|
|
|
|
|
+ <option value="">Select consultant…</option>
|
|
|
|
|
+ <?php foreach ($consultants as $c): ?>
|
|
|
|
|
+ <option value="<?= (int)$c['id'] ?>">
|
|
|
|
|
+ <?= htmlspecialchars($c['fullname'], ENT_QUOTES, 'UTF-8') ?>
|
|
|
|
|
+ </option>
|
|
|
|
|
+ <?php endforeach; ?>
|
|
|
|
|
+ </select>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div class="col-md-5">
|
|
|
|
|
+ <label class="form-label fw-semibold small">Client</label>
|
|
|
|
|
+ <select name="client_id" class="form-select form-select-sm" required>
|
|
|
|
|
+ <option value="">Select client…</option>
|
|
|
|
|
+ <?php foreach ($allClients as $cl): ?>
|
|
|
|
|
+ <option value="<?= (int)$cl['id'] ?>">
|
|
|
|
|
+ <?= htmlspecialchars($cl['client'] . ($cl['company'] ? ' — ' . $cl['company'] : ''), ENT_QUOTES, 'UTF-8') ?>
|
|
|
|
|
+ </option>
|
|
|
|
|
+ <?php endforeach; ?>
|
|
|
|
|
+ </select>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div class="col-md-2">
|
|
|
|
|
+ <button class="btn btn-success btn-sm w-100" type="submit">
|
|
|
|
|
+ <i class="fas fa-plus me-1"></i>Assign
|
|
|
|
|
+ </button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </form>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- Current assignments per consultant -->
|
|
|
|
|
+ <?php if (empty($consultants)): ?>
|
|
|
|
|
+ <div class="alert alert-info">
|
|
|
|
|
+ No consultant accounts yet. Set a user's role to "Consultant" using the panel on the left.
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <?php else: ?>
|
|
|
|
|
+ <?php foreach ($consultants as $con):
|
|
|
|
|
+ $assigned = $assignmentsByConsultant[$con['id']] ?? [];
|
|
|
|
|
+ ?>
|
|
|
|
|
+ <div class="card shadow-sm border-0 mb-3">
|
|
|
|
|
+ <div class="card-header d-flex justify-content-between align-items-center">
|
|
|
|
|
+ <span class="fw-semibold">
|
|
|
|
|
+ <i class="fas fa-user-tie me-1 text-info"></i>
|
|
|
|
|
+ <?= htmlspecialchars($con['fullname'], ENT_QUOTES, 'UTF-8') ?>
|
|
|
|
|
+ <span class="text-muted fw-normal small ms-1">
|
|
|
|
|
+ <?= htmlspecialchars($con['email'], ENT_QUOTES, 'UTF-8') ?>
|
|
|
|
|
+ </span>
|
|
|
|
|
+ </span>
|
|
|
|
|
+ <span class="badge bg-secondary"><?= count($assigned) ?> client<?= count($assigned) !== 1 ? 's' : '' ?></span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <?php if (empty($assigned)): ?>
|
|
|
|
|
+ <div class="card-body text-muted small">No clients assigned yet.</div>
|
|
|
|
|
+ <?php else: ?>
|
|
|
|
|
+ <ul class="list-group list-group-flush">
|
|
|
|
|
+ <?php foreach ($assigned as $a): ?>
|
|
|
|
|
+ <li class="list-group-item d-flex justify-content-between align-items-center py-2">
|
|
|
|
|
+ <div>
|
|
|
|
|
+ <span class="fw-semibold">
|
|
|
|
|
+ <?= htmlspecialchars($a['client'], ENT_QUOTES, 'UTF-8') ?>
|
|
|
|
|
+ </span>
|
|
|
|
|
+ <?php if ($a['company']): ?>
|
|
|
|
|
+ <span class="text-muted small ms-1">
|
|
|
|
|
+ — <?= htmlspecialchars($a['company'], ENT_QUOTES, 'UTF-8') ?>
|
|
|
|
|
+ </span>
|
|
|
|
|
+ <?php endif; ?>
|
|
|
|
|
+ <div class="text-muted" style="font-size:.72rem">
|
|
|
|
|
+ Assigned <?= date('j M Y', strtotime($a['assigned_at'])) ?>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <form method="POST" class="d-inline"
|
|
|
|
|
+ onsubmit="return confirm('Remove this client assignment?')">
|
|
|
|
|
+ <input type="hidden" name="csrf_token" value="<?= generateCsrfToken() ?>">
|
|
|
|
|
+ <input type="hidden" name="action" value="remove">
|
|
|
|
|
+ <input type="hidden" name="assignment_id" value="<?= (int)$a['assignment_id'] ?>">
|
|
|
|
|
+ <button class="btn btn-sm btn-outline-danger" type="submit">
|
|
|
|
|
+ <i class="fas fa-times"></i>
|
|
|
|
|
+ </button>
|
|
|
|
|
+ </form>
|
|
|
|
|
+ </li>
|
|
|
|
|
+ <?php endforeach; ?>
|
|
|
|
|
+ </ul>
|
|
|
|
|
+ <?php endif; ?>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <?php endforeach; ?>
|
|
|
|
|
+ <?php endif; ?>
|
|
|
|
|
+
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div><!-- /.row -->
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </main>
|
|
|
|
|
+ </div>
|
|
|
|
|
+</div>
|
|
|
|
|
+
|
|
|
|
|
+<!-- ── Modal: set user type ───────────────────────────────────────────────── -->
|
|
|
|
|
+<div class="modal fade" id="modalSetType" tabindex="-1">
|
|
|
|
|
+ <div class="modal-dialog modal-sm">
|
|
|
|
|
+ <div class="modal-content">
|
|
|
|
|
+ <form method="POST">
|
|
|
|
|
+ <input type="hidden" name="csrf_token" value="<?= generateCsrfToken() ?>">
|
|
|
|
|
+ <input type="hidden" name="action" value="set_type">
|
|
|
|
|
+ <input type="hidden" name="user_id" id="modalUserId">
|
|
|
|
|
+ <div class="modal-header">
|
|
|
|
|
+ <h5 class="modal-title">Change Role</h5>
|
|
|
|
|
+ <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="modal-body">
|
|
|
|
|
+ <p class="mb-2 text-muted small" id="modalUserName"></p>
|
|
|
|
|
+ <label class="form-label fw-semibold">User Type</label>
|
|
|
|
|
+ <select name="user_type" id="modalUserType" class="form-select">
|
|
|
|
|
+ <option value="client">Client</option>
|
|
|
|
|
+ <option value="consultant">Consultant</option>
|
|
|
|
|
+ <option value="admin">Admin</option>
|
|
|
|
|
+ </select>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="modal-footer">
|
|
|
|
|
+ <button type="button" class="btn btn-secondary btn-sm" data-bs-dismiss="modal">Cancel</button>
|
|
|
|
|
+ <button type="submit" class="btn btn-primary btn-sm">Save</button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </form>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+</div>
|
|
|
|
|
+
|
|
|
|
|
+<script>
|
|
|
|
|
+document.getElementById('modalSetType').addEventListener('show.bs.modal', function (e) {
|
|
|
|
|
+ const btn = e.relatedTarget;
|
|
|
|
|
+ document.getElementById('modalUserId').value = btn.dataset.userId;
|
|
|
|
|
+ document.getElementById('modalUserName').textContent = btn.dataset.userName;
|
|
|
|
|
+ document.getElementById('modalUserType').value = btn.dataset.userType;
|
|
|
|
|
+});
|
|
|
|
|
+</script>
|
|
|
|
|
+
|
|
|
|
|
+<?php include __DIR__ . '/../../layouts/footer.php'; ?>
|