December 16th, 2025 V.4

This commit is contained in:
Flatlogic Bot 2025-12-17 01:39:36 +00:00
parent 99a2cd4d5f
commit a9b3eacb8c
24 changed files with 669 additions and 1205 deletions

430
app.php
View File

@ -1,99 +1,18 @@
<?php <?php
require_once 'auth.php'; session_start();
if (!isset($_SESSION['user_id'])) {
// Check if user is logged in
if (!is_logged_in()) {
header('Location: login.php'); header('Location: login.php');
exit; exit;
} }
require_once 'db/config.php';
$pdo = db();
require_once __DIR__ . '/db/config.php'; $total_candidates = $pdo->query("SELECT COUNT(*) FROM candidates")->fetchColumn();
$total_tasks = $pdo->query("SELECT COUNT(*) FROM tasks")->fetchColumn();
// Data Fetching for Dashboard $completed_tasks = $pdo->query("SELECT COUNT(*) FROM tasks WHERE status = 'Completed'")->fetchColumn();
try { $completion_rate = ($total_tasks > 0) ? ($completed_tasks / $total_tasks) * 100 : 0;
$pdo = db(); // For now, open cases will be a static number
$open_cases = 12;
// Key Metrics
$totalActiveCandidates = $pdo->query("SELECT COUNT(*) FROM candidates WHERE status NOT IN ('Hired', 'Rejected')")->fetchColumn();
$newCandidatesThisWeek = $pdo->query("SELECT COUNT(*) FROM candidates WHERE created_at >= NOW() - INTERVAL 7 DAY")->fetchColumn();
// Assuming tasks with "interview" in the name are interviews
$interviewsThisWeek = $pdo->query("SELECT COUNT(*) FROM tasks WHERE title LIKE '%interview%' AND due_date >= CURDATE() AND due_date < CURDATE() + INTERVAL 7 DAY")->fetchColumn();
$pendingTasks = $pdo->query("SELECT COUNT(*) FROM tasks WHERE status NOT IN ('Completed')")->fetchColumn();
$overdueTasksCount = $pdo->query("SELECT COUNT(*) FROM tasks WHERE status NOT IN ('Completed') AND due_date < CURDATE()")->fetchColumn();
$newHiresThisMonth = $pdo->query("SELECT COUNT(*) FROM candidates WHERE status = 'Hired' AND created_at >= NOW() - INTERVAL 1 MONTH")->fetchColumn();
// Recent Activity Feed from workflow_logs
$recentActivities = $pdo->query("
SELECT wl.*, w.name as workflow_name, c.name as candidate_name
FROM workflow_logs wl
LEFT JOIN workflows w ON wl.workflow_id = w.id
LEFT JOIN candidates c ON wl.candidate_id = c.id
ORDER BY wl.executed_at DESC
LIMIT 10
")->fetchAll();
// At-Risk Items
$overdueTasks = $pdo->query("
SELECT t.title, t.due_date, c.name as candidate_name
FROM tasks t
LEFT JOIN candidates c ON t.assigned_to = c.id
WHERE t.status != 'Completed' AND t.due_date < CURDATE()
ORDER BY t.due_date ASC
")->fetchAll();
$inactiveCandidates = $pdo->query("
SELECT name, email, created_at
FROM candidates
WHERE status = 'Applied' AND created_at <= NOW() - INTERVAL 7 DAY
")->fetchAll();
$incompleteOnboarding = $pdo->query("
SELECT t.title, c.name as candidate_name, t.due_date
FROM tasks t
JOIN candidates c ON t.assigned_to = c.id
WHERE c.status = 'Hired' AND t.status != 'Completed'
")->fetchAll();
$all_candidates = $pdo->query("SELECT * FROM candidates ORDER BY name ASC")->fetchAll();
} catch (PDOException $e) {
error_log("Dashboard Data Fetch Error: " . $e->getMessage());
// Initialize variables to prevent errors in the view
$totalActiveCandidates = $newCandidatesThisWeek = $interviewsThisWeek = $pendingTasks = $overdueTasksCount = $newHiresThisMonth = 0;
$recentActivities = $overdueTasks = $inactiveCandidates = $incompleteOnboarding = $all_candidates = [];
}
function time_ago($datetime, $full = false) {
$now = new DateTime;
$ago = new DateTime($datetime);
$diff = $now->diff($ago);
$diff->w = floor($diff->d / 7);
$diff->d -= $diff->w * 7;
$string = array(
'y' => 'year',
'm' => 'month',
'w' => 'week',
'd' => 'day',
'h' => 'hour',
'i' => 'minute',
's' => 'second',
);
foreach ($string as $k => &$v) {
if ($diff->$k) {
$v = $diff->$k . ' ' . $v . ($diff->$k > 1 ? 's' : '');
} else {
unset($string[$k]);
}
}
if (!$full) $string = array_slice($string, 0, 1);
return $string ? implode(', ', $string) . ' ago' : 'just now';
}
?> ?>
<!DOCTYPE html> <!DOCTYPE html>
@ -101,322 +20,55 @@ function time_ago($datetime, $full = false) {
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Dashboard - FinMox</title>
<title>Executive Dashboard - FinMox Flow</title> <script src="https://cdn.tailwindcss.com"></script>
<meta name="description" content="Executive dashboard for FinMox Flow, providing key metrics and insights for HR and operations.">
<meta property="og:title" content="FinMox Flow Dashboard">
<meta property="og:description" content="Key metrics for HR and operations.">
<meta property="og:image" content="<?php echo htmlspecialchars($_SERVER['PROJECT_IMAGE_URL'] ?? '', ENT_QUOTES, 'UTF-8'); ?>">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:image" content="<?php echo htmlspecialchars($_SERVER['PROJECT_IMAGE_URL'] ?? '', ENT_QUOTES, 'UTF-8'); ?>">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
<link rel="stylesheet" href="assets/css/custom.css?v=<?php echo time(); ?>">
<style> <style>
.main-content { @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap');
background-color: #f4f7fc; * {
font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
} }
.card { body {
border: none; background-color: #fafafa;
border-radius: 0.75rem; -webkit-font-smoothing: antialiased;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05);
transition: all 0.3s ease;
}
.card:hover {
transform: translateY(-5px);
box-shadow: 0 8px 15px rgba(0, 0, 0, 0.07);
}
.metric-card .card-body {
display: flex;
align-items: center;
}
.metric-card .icon {
font-size: 2.5rem;
margin-right: 1rem;
color: #0d6efd;
opacity: 0.7;
}
.metric-card .value {
font-size: 2.25rem;
font-weight: 700;
}
.metric-card .label {
font-size: 1rem;
color: #6c757d;
}
.metric-card .indicator {
font-size: 0.875rem;
font-weight: 500;
}
.quick-actions .btn {
padding: 0.75rem 1.5rem;
font-size: 1rem;
font-weight: 500;
}
.activity-feed .activity-item {
display: flex;
align-items: center;
padding: 0.75rem 0;
border-bottom: 1px solid #e9ecef;
}
.activity-feed .activity-item:last-child {
border-bottom: none;
}
.activity-feed .icon {
font-size: 1.5rem;
margin-right: 1rem;
color: #6c757d;
}
.at-risk-section .table {
margin-bottom: 0;
} }
</style> </style>
</head> </head>
<body> <body class="bg-gray-100">
<div class="flex h-screen bg-gray-200">
<header class="header d-flex justify-content-between align-items-center"> <!-- Sidebar -->
<div class="logo"> <div class="w-64 bg-white shadow-md">
<div class="p-6">
<a href="app.php"> <a href="app.php">
<img src="assets/pasted-20251120-051320-b2b0cdfa.png" alt="FinMox Logo" style="height: 32px;"> <img src="assets/pasted-20251120-051320-b2b0cdfa.png" alt="FinMox Logo" style="height: 32px;">
</a> </a>
</div> </div>
<nav class="d-flex align-items-center"> <nav class="mt-6">
<a href="app.php" class="btn btn-primary me-2">Home</a> <a href="dashboard.php" class="block py-2.5 px-4 rounded transition duration-200 hover:bg-gray-200 text-gray-700">Dashboard</a>
<a href="chat.php" class="btn btn-outline-primary me-2">Chat</a> <a href="chat.php" class="block py-2.5 px-4 rounded transition duration-200 hover:bg-gray-200 text-gray-700">AI Copilot</a>
<a href="dashboard.php" class="btn btn-outline-primary me-2">Dashboard</a> <a href="workflows.php" class="block py-2.5 px-4 rounded transition duration-200 hover:bg-gray-200 text-gray-700">Workflows</a>
<a href="workflows.php" class="btn btn-outline-primary me-2">Workflows</a> <a href="roles.php" class="block py-2.5 px-4 rounded transition duration-200 hover:bg-gray-200 text-gray-700">Roles</a>
<a href="settings.php" class="btn btn-outline-primary me-3">Settings</a> <a href="settings.php" class="block py-2.5 px-4 rounded transition duration-200 hover:bg-gray-200 text-gray-700">Settings</a>
<div class="dropdown"> <a href="logout.php" class="block py-2.5 px-4 rounded transition duration-200 hover:bg-gray-200 text-gray-700">Logout</a>
<button class="btn btn-outline-secondary dropdown-toggle" type="button" id="userDropdown" data-bs-toggle="dropdown" aria-expanded="false">
<?php echo htmlspecialchars($_SESSION['username']); ?>
</button>
<ul class="dropdown-menu dropdown-menu-end" aria-labelledby="userDropdown">
<?php if (hasPermission('manage_roles')): ?>
<li><a class="dropdown-item" href="roles.php">Manage Roles</a></li>
<li><hr class="dropdown-divider"></li>
<?php endif; ?>
<li><a class="dropdown-item" href="logout.php">Logout</a></li>
</ul>
</div>
</nav> </nav>
</header> </div>
<main class="main-content"> <!-- Main content -->
<div class="container-fluid py-4"> <div class="flex-1 flex flex-col overflow-hidden">
<main class="flex-1 overflow-x-hidden overflow-y-auto bg-[#fafafa] pb-20 md:pb-0">
<div class="max-w-7xl mx-auto px-4 md:px-6 py-4 md:py-8">
<!-- TOP SECTION - Key Metrics --> <!-- Dashboard Header -->
<div class="row mb-4"> <div class="md:flex md:items-center md:justify-between">
<div class="col-xl-3 col-md-6 mb-4"> <div class="flex-1 min-w-0">
<div class="card metric-card h-100"> <h2 class="text-2xl font-bold leading-7 text-gray-900 sm:text-3xl sm:truncate">
<div class="card-body"> Welcome to FinMox
<div class="icon"><i class="bi bi-people-fill"></i></div> </h2>
<div>
<div class="value"><?php echo $totalActiveCandidates; ?></div>
<div class="label">Total Active Candidates</div>
<div class="indicator text-success">+<?php echo $newCandidatesThisWeek; ?> this week</div>
</div>
</div>
</div>
</div>
<div class="col-xl-3 col-md-6 mb-4">
<div class="card metric-card h-100">
<div class="card-body">
<div class="icon"><i class="bi bi-calendar-check"></i></div>
<div>
<div class="value"><?php echo $interviewsThisWeek; ?></div>
<div class="label">Interviews This Week</div>
</div>
</div>
</div>
</div>
<div class="col-xl-3 col-md-6 mb-4">
<div class="card metric-card h-100">
<div class="card-body">
<div class="icon"><i class="bi bi-list-task"></i></div>
<div>
<div class="value"><?php echo $pendingTasks; ?></div>
<div class="label">Pending Tasks</div>
<?php if ($overdueTasksCount > 0): ?>
<div class="indicator text-danger"><?php echo $overdueTasksCount; ?> overdue</div>
<?php endif; ?>
</div>
</div>
</div>
</div>
<div class="col-xl-3 col-md-6 mb-4">
<div class="card metric-card h-100">
<div class="card-body">
<div class="icon"><i class="bi bi-person-plus-fill"></i></div>
<div>
<div class="value"><?php echo $newHiresThisMonth; ?></div>
<div class="label">New Hires This Month</div>
</div>
</div>
</div>
</div> </div>
</div> </div>
<!-- MIDDLE SECTION - Quick Actions -->
<div class="card mb-4">
<div class="card-body text-center quick-actions">
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addCandidateModal"><i class="bi bi-plus-circle me-2"></i>Add New Candidate</button>
<button class="btn btn-secondary" data-bs-toggle="modal" data-bs-target="#addTaskModal"><i class="bi bi-calendar-plus me-2"></i>Schedule Interview</button>
<a href="#" class="btn btn-secondary"><i class="bi bi-file-earmark-text me-2"></i>View Weekly Report</a>
<a href="workflows.php" class="btn btn-secondary"><i class="bi bi-diagram-3 me-2"></i>Manage Workflows</a>
</div>
</div>
<div class="row">
<!-- RECENT ACTIVITY FEED -->
<div class="col-lg-6 mb-4">
<div class="card h-100">
<div class="card-header bg-white py-3">
<h5 class="mb-0">Recent Activity</h5>
</div>
<div class="card-body activity-feed">
<?php if (empty($recentActivities)): ?>
<p class="text-center text-muted">No recent activity.</p>
<?php else: ?>
<?php foreach ($recentActivities as $activity): ?>
<div class="activity-item">
<div class="icon"><i class="bi bi-bell"></i></div>
<div class="flex-grow-1">
<div>
<?php
$action_desc = htmlspecialchars(ucfirst(str_replace('_', ' ', $activity['action'])));
if ($activity['workflow_name']) {
echo htmlspecialchars(ucfirst(str_replace('_', ' ', $activity['workflow_name'])));
} else {
echo $action_desc;
}
if ($activity['candidate_name']) {
echo " for " . htmlspecialchars($activity['candidate_name']);
}
?>
</div>
<small class="text-muted"><?php echo time_ago($activity['executed_at']); ?></small>
</div>
<a href="workflows.php?highlight=<?php echo $activity['workflow_id']; ?>" class="btn btn-sm btn-outline-secondary">Details</a>
</div>
<?php endforeach; ?>
<?php endif; ?>
</div>
</div>
</div>
<!-- AT-RISK ITEMS -->
<div class="col-lg-6 mb-4">
<div class="card h-100">
<div class="card-header bg-white py-3">
<h5 class="mb-0">At-Risk Items</h5>
</div>
<div class="card-body at-risk-section">
<?php if (!empty($overdueTasks)): ?>
<h6><i class="bi bi-exclamation-triangle-fill text-danger me-2"></i>Overdue Tasks</h6>
<div class="table-responsive mb-3">
<table class="table table-sm">
<tbody>
<?php foreach ($overdueTasks as $task): ?>
<tr>
<td><?php echo htmlspecialchars($task['title']); ?></td>
<td><small><?php echo htmlspecialchars($task['candidate_name']); ?></small></td>
<td class="text-danger"><small><?php echo date("M d", strtotime($task['due_date'])); ?></small></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php endif; ?>
<?php if (!empty($inactiveCandidates)): ?>
<h6 class="mt-3"><i class="bi bi-person-fill-exclamation text-warning me-2"></i>Inactive Candidates (>7 days)</h6>
<div class="table-responsive mb-3">
<table class="table table-sm">
<tbody>
<?php foreach ($inactiveCandidates as $candidate): ?>
<tr>
<td><?php echo htmlspecialchars($candidate['name']); ?></td>
<td><small><?php echo htmlspecialchars($candidate['email']); ?></small></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php endif; ?>
<?php if (!empty($incompleteOnboarding)): ?>
<h6 class="mt-3"><i class="bi bi-clipboard-x-fill text-info me-2"></i>Incomplete Onboarding</h6>
<div class="table-responsive">
<table class="table table-sm">
<tbody>
<?php foreach ($incompleteOnboarding as $task): ?>
<tr>
<td><?php echo htmlspecialchars($task['title']); ?></td>
<td><small><?php echo htmlspecialchars($task['candidate_name']); ?></small></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php endif; ?>
<?php if (empty($overdueTasks) && empty($inactiveCandidates) && empty($incompleteOnboarding)): ?>
<p class="text-center text-muted mt-3">No at-risk items found. Great job!</p>
<?php endif; ?>
</div>
</div>
</div>
</div>
</div> </div>
</main> </main>
<!-- Add Candidate Modal -->
<div class="modal fade" id="addCandidateModal" tabindex="-1" aria-labelledby="addCandidateModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="addCandidateModalLabel">Add New Candidate</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<form method="POST" action="app.php">
<input type="hidden" name="add_candidate" value="1">
<div class="mb-3"><label for="name" class="form-label">Name</label><input type="text" class="form-control" id="name" name="name" required></div>
<div class="mb-3"><label for="email" class="form-label">Email</label><input type="email" class="form-control" id="email" name="email" required></div>
<div class="mb-3"><label for="phone" class="form-label">Phone</label><input type="text" class="form-control" id="phone" name="phone"></div>
<div class="mb-3"><label for="status" class="form-label">Status</label><select class="form-select" id="status" name="status"><option value="Applied" selected>Applied</option><option value="Interviewing">Interviewing</option><option value="Offered">Offered</option><option value="Hired">Hired</option><option value="Rejected">Rejected</option></select></div>
<div class="mb-3"><label for="notes" class="form-label">Notes</label><textarea class="form-control" id="notes" name="notes" rows="3"></textarea></div>
<div class="modal-footer"><button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button><button type="submit" class="btn btn-primary">Save Candidate</button></div>
</form>
</div> </div>
</div> </div>
</div>
</div>
<!-- Add Task Modal (for "Schedule Interview") -->
<div class="modal fade" id="addTaskModal" tabindex="-1" aria-labelledby="addTaskModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="addTaskModalLabel">Schedule Interview</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<form method="POST" action="app.php">
<input type="hidden" name="add_task" value="1">
<div class="mb-3"><label for="task_name" class="form-label">Interview Title</label><input type="text" class="form-control" id="task_name" name="title" required value="Interview with "></div>
<div class="mb-3"><label for="candidate_id" class="form-label">Candidate</label><select class="form-select" id="candidate_id" name="assigned_to" required><option value="" disabled selected>Select a candidate</option><?php foreach ($all_candidates as $candidate): ?><option value="<?php echo $candidate['id']; ?>"><?php echo htmlspecialchars($candidate['name']); ?></option><?php endforeach; ?></select></div>
<div class="mb-3"><label for="due_date" class="form-label">Interview Date</label><input type="date" class="form-control" id="due_date" name="due_date" required></div>
<div class="mb-3"><label for="task_description" class="form-label">Notes</label><textarea class="form-control" id="task_description" name="description" rows="3"></textarea></div>
<div class="modal-footer"><button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button><button type="submit" class="btn btn-primary">Schedule</button></div>
</form>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
</body> </body>
</html> </html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 318 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 130 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 356 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

View File

@ -3,6 +3,7 @@ session_start();
require_once 'db/config.php'; require_once 'db/config.php';
function register_user($username, $password, $email) { function register_user($username, $password, $email) {
try {
$pdo = db(); $pdo = db();
$password_hash = password_hash($password, PASSWORD_DEFAULT); $password_hash = password_hash($password, PASSWORD_DEFAULT);
@ -12,11 +13,10 @@ function register_user($username, $password, $email) {
$role = $stmt->fetch(); $role = $stmt->fetch();
$default_role_id = $role ? $role['id'] : null; $default_role_id = $role ? $role['id'] : null;
try {
$stmt = $pdo->prepare("INSERT INTO users (username, password, role_id, email) VALUES (?, ?, ?, ?)"); $stmt = $pdo->prepare("INSERT INTO users (username, password, role_id, email) VALUES (?, ?, ?, ?)");
return $stmt->execute([$username, $password_hash, $default_role_id, $email]); return $stmt->execute([$username, $password_hash, $default_role_id, $email]);
} catch (PDOException $e) { } catch (PDOException $e) {
// Handle duplicate username // Handle duplicate username or other db errors
error_log($e->getMessage()); error_log($e->getMessage());
return false; return false;
} }
@ -32,6 +32,7 @@ function login_user($username, $password) {
$_SESSION['user_id'] = $user['id']; $_SESSION['user_id'] = $user['id'];
$_SESSION['username'] = $user['username']; $_SESSION['username'] = $user['username'];
$_SESSION['role_id'] = $user['role_id']; $_SESSION['role_id'] = $user['role_id'];
$_SESSION['email'] = $user['email'];
return true; return true;
} }
return false; return false;

214
chat.php
View File

@ -1,28 +1,36 @@
<?php <?php
require_once 'auth.php'; session_start();
if (!is_logged_in()) { if (!isset($_SESSION['user_id'])) {
header('Location: login.php'); header('Location: login.php');
exit(); exit;
} }
require_once 'db/config.php';
?> ?>
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AI Chat - FinMox</title> <title>AI Copilot - FinMox</title>
<script src="https://cdn.tailwindcss.com"></script> <script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css"> <script src="https://unpkg.com/lucide@latest"></script>
<style> <style>
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap'); @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap');
* { * {
font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif; font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
} }
body { body {
background-color: #fafafa;
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
} }
.chat-container { .chat-container {
height: calc(100vh - 80px); display: flex;
flex-direction: column;
height: calc(100vh - 120px); /* Adjust based on header/footer height */
}
.chat-messages {
flex-grow: 1;
overflow-y: auto;
} }
.message-bubble { .message-bubble {
max-width: 75%; max-width: 75%;
@ -32,131 +40,131 @@ if (!is_logged_in()) {
color: white; color: white;
} }
.ai-message { .ai-message {
background-color: #E5E7EB; background-color: #F3F4F6;
color: #1F2937; color: #1F2937;
} }
#chat-input:focus { #chat-input-wrapper {
outline: none; position: relative;
box-shadow: none; }
border-color: #3B82F6; #chat-input {
padding-right: 40px; /* Space for the button */
}
#send-btn {
position: absolute;
right: 8px;
top: 50%;
transform: translateY(-50%);
background: none;
border: none;
cursor: pointer;
color: #9CA3AF;
}
#send-btn:hover {
color: #374151;
} }
</style> </style>
</head> </head>
<body class="bg-gray-100"> <body class="bg-gray-100">
<div class="flex h-screen"> <div class="flex h-screen bg-gray-200">
<!-- Sidebar --> <!-- Sidebar -->
<div class="w-1/4 bg-white border-r border-gray-200 flex flex-col"> <div class="w-64 bg-white shadow-md">
<div class="p-4 border-b border-gray-200"> <div class="p-6">
<a href="chat.php" class="w-full bg-black text-white px-4 py-2 rounded-lg text-sm font-medium flex items-center justify-center gap-2">
<i class="fas fa-plus"></i>
New Chat
</a>
</div>
<div class="flex-grow overflow-y-auto">
<!-- Conversation List -->
<div class="p-4">
<h2 class="text-xs text-gray-500 font-semibold uppercase mb-2">Past Conversations</h2>
<ul id="conversation-list">
<!-- Placeholder for conversation items -->
<li class="p-2 rounded-lg hover:bg-gray-100 cursor-pointer">
<p class="font-semibold text-sm truncate">Onboarding Workflow Ideas</p>
<p class="text-xs text-gray-500 truncate">Sure, here are some ideas for your onboarding...</p>
<p class="text-xs text-gray-400 mt-1">2 hours ago</p>
</li>
<li class="p-2 rounded-lg hover:bg-gray-100 cursor-pointer mt-2">
<p class="font-semibold text-sm truncate">Compliance Questions</p>
<p class="text-xs text-gray-500 truncate">Let's break down the compliance requirements...</p>
<p class="text-xs text-gray-400 mt-1">Yesterday</p>
</li>
</ul>
</div>
</div>
</div>
<!-- Main Chat Area -->
<div class="w-3/4 flex flex-col">
<header class="bg-white border-b border-gray-200 p-4 flex items-center justify-between">
<div class="flex items-center gap-2">
<a href="app.php"> <a href="app.php">
<img src="assets/pasted-20251120-051320-b2b0cdfa.png" alt="FinMox Logo" style="height: 32px;"> <img src="assets/pasted-20251120-051320-b2b0cdfa.png" alt="FinMox Logo" style="height: 32px;">
</a> </a>
</div> </div>
<nav class="flex items-center gap-4"> <nav class="mt-6">
<a href="app.php" class="text-sm text-gray-600 hover:text-gray-900">Home</a> <a href="dashboard.php" class="block py-2.5 px-4 rounded transition duration-200 hover:bg-gray-200 text-gray-700">Dashboard</a>
<a href="dashboard.php" class="text-sm text-gray-600 hover:text-gray-900">Dashboard</a> <a href="chat.php" class="block py-2.5 px-4 rounded transition duration-200 bg-gray-200 text-gray-900 font-semibold">AI Copilot</a>
<a href="workflows.php" class="text-sm text-gray-600 hover:text-gray-900">Workflows</a> <a href="workflows.php" class="block py-2.5 px-4 rounded transition duration-200 hover:bg-gray-200 text-gray-700">Workflows</a>
<a href="settings.php" class="text-sm text-gray-600 hover:text-gray-900">Settings</a> <a href="roles.php" class="block py-2.5 px-4 rounded transition duration-200 hover:bg-gray-200 text-gray-700">Roles</a>
<a href="logout.php" class="text-sm text-gray-600 hover:text-gray-900">Logout</a> <a href="settings.php" class="block py-2.5 px-4 rounded transition duration-200 hover:bg-gray-200 text-gray-700">Settings</a>
<a href="logout.php" class="block py-2.5 px-4 rounded transition duration-200 hover:bg-gray-200 text-gray-700">Logout</a>
</nav> </nav>
</header> </div>
<main class="flex-grow p-6 overflow-y-auto" id="chat-messages"> <!-- Main content -->
<!-- Messages will be appended here --> <main class="flex-1 flex flex-col overflow-hidden bg-[#fafafa]">
</main> <div class="max-w-4xl w-full mx-auto px-4 sm:px-6 md:px-8 py-8 flex flex-col flex-grow">
<h1 class="text-3xl font-bold text-gray-900 mb-6">AI Copilot</h1>
<div class="bg-white border-t border-gray-200 p-4"> <div class="chat-container bg-white border border-gray-200 rounded-xl shadow-sm flex-grow">
<div class="chat-messages p-6" id="chat-messages">
<!-- Initial Welcome Screen -->
<div id="welcome-screen" class="text-center h-full flex flex-col justify-center items-center">
<div class="mb-8">
<h2 class="text-2xl font-semibold text-gray-800">Hi, I'm FinMox AI.</h2>
<p class="text-gray-500">How can I help you today?</p>
</div>
<div class="w-full max-w-2xl">
<p class="text-sm text-gray-600 mb-3">Here are a few suggestions:</p>
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
<button class="suggestion-card text-left p-4 bg-gray-50 hover:bg-gray-100 rounded-lg border border-gray-200 transition">
<i data-lucide="file-text" class="w-5 h-5 mb-2 text-gray-600"></i>
<p class="text-sm font-medium text-gray-800">Summarize key candidate skills</p>
</button>
<button class="suggestion-card text-left p-4 bg-gray-50 hover:bg-gray-100 rounded-lg border border-gray-200 transition">
<i data-lucide="mail" class="w-5 h-5 mb-2 text-gray-600"></i>
<p class="text-sm font-medium text-gray-800">Draft a response to a candidate</p>
</button>
<button class="suggestion-card text-left p-4 bg-gray-50 hover:bg-gray-100 rounded-lg border border-gray-200 transition">
<i data-lucide="bar-chart-2" class="w-5 h-5 mb-2 text-gray-600"></i>
<p class="text-sm font-medium text-gray-800">Analyze data from a resume</p>
</button>
</div>
</div>
</div>
<!-- Chat messages will be appended here -->
</div>
<div class="p-4 border-t border-gray-200">
<div class="typing-indicator text-sm text-gray-500 mb-2 hidden" id="typing-indicator"> <div class="typing-indicator text-sm text-gray-500 mb-2 hidden" id="typing-indicator">
AI is thinking... AI is thinking...
</div> </div>
<div class="relative"> <div id="chat-input-wrapper" class="relative">
<textarea id="chat-input" class="w-full border border-gray-300 rounded-lg p-4 pr-28" placeholder="Ask the AI Assistant..." rows="1"></textarea> <input type="text" id="chat-input" class="w-full border-gray-300 rounded-lg p-3 pr-12 text-sm" placeholder="Send a message...">
<div class="absolute bottom-3 right-3 flex items-center"> <button id="send-btn">
<button class="mr-2 text-gray-400 hover:text-gray-600"> <i data-lucide="send" class="w-5 h-5"></i>
<i class="fas fa-paperclip"></i>
</button> </button>
<button id="send-btn" class="bg-black text-white px-4 py-2 rounded-lg text-sm font-medium">Send</button>
</div> </div>
</div> </div>
<div class="text-xs text-gray-400 mt-1 text-right" id="char-counter">0 / 4000</div>
</div> </div>
</div> </div>
</main>
</div> </div>
<script> <script>
lucide.createIcons();
const chatMessages = document.getElementById('chat-messages'); const chatMessages = document.getElementById('chat-messages');
const userInput = document.getElementById('chat-input'); const userInput = document.getElementById('chat-input');
const sendBtn = document.getElementById('send-btn'); const sendBtn = document.getElementById('send-btn');
const typingIndicator = document.getElementById('typing-indicator'); const typingIndicator = document.getElementById('typing-indicator');
const charCounter = document.getElementById('char-counter'); const welcomeScreen = document.getElementById('welcome-screen');
let welcomeMessageCleared = false;
function addMessage(sender, content) { function addMessage(sender, content) {
if (!welcomeMessageCleared) {
chatMessages.innerHTML = '';
welcomeMessageCleared = true;
}
const messageWrapper = document.createElement('div'); const messageWrapper = document.createElement('div');
messageWrapper.classList.add('flex', 'mb-4'); messageWrapper.classList.add('flex', 'mb-4');
const avatar = document.createElement('div'); const bubble = document.createElement('div');
avatar.classList.add('w-8', 'h-8', 'rounded-full', 'flex', 'items-center', 'justify-center', 'text-white', 'font-bold'); bubble.classList.add('p-3', 'rounded-lg', 'message-bubble');
bubble.textContent = content;
const messageBubble = document.createElement('div');
messageBubble.classList.add('p-3', 'rounded-lg', 'message-bubble');
const timestamp = document.createElement('div');
timestamp.classList.add('text-xs', 'text-gray-400', 'mt-1');
timestamp.textContent = new Date().toLocaleTimeString();
if (sender === 'user') { if (sender === 'user') {
messageWrapper.classList.add('justify-end'); messageWrapper.classList.add('justify-end');
avatar.classList.add('bg-blue-500', 'ml-3'); bubble.classList.add('user-message');
avatar.textContent = 'U';
messageBubble.classList.add('user-message');
messageBubble.textContent = content;
messageWrapper.appendChild(messageBubble);
messageWrapper.appendChild(avatar);
} else { } else {
messageWrapper.classList.add('justify-start'); messageWrapper.classList.add('justify-start');
avatar.classList.add('bg-gray-700', 'mr-3'); bubble.classList.add('ai-message');
avatar.textContent = 'AI';
messageBubble.classList.add('ai-message');
messageBubble.innerHTML = content; // Use innerHTML to render potential markdown
messageWrapper.appendChild(avatar);
messageWrapper.appendChild(messageBubble);
} }
const messageContainer = document.createElement('div'); messageWrapper.appendChild(bubble);
messageContainer.classList.add('flex', 'flex-col', (sender === 'user' ? 'items-end' : 'items-start'));
messageContainer.appendChild(messageBubble);
messageContainer.appendChild(timestamp);
messageWrapper.appendChild(messageContainer);
chatMessages.appendChild(messageWrapper); chatMessages.appendChild(messageWrapper);
chatMessages.scrollTop = chatMessages.scrollHeight; chatMessages.scrollTop = chatMessages.scrollHeight;
} }
@ -167,16 +175,13 @@ if (!is_logged_in()) {
addMessage('user', message); addMessage('user', message);
userInput.value = ''; userInput.value = '';
charCounter.textContent = '0 / 4000';
sendBtn.disabled = true; sendBtn.disabled = true;
typingIndicator.classList.remove('hidden'); typingIndicator.classList.remove('hidden');
try { try {
const response = await fetch('api/chat.php', { const response = await fetch('api/chat.php', {
method: 'POST', method: 'POST',
headers: { headers: { 'Content-Type': 'application/json' },
'Content-Type': 'application/json'
},
body: JSON.stringify({ message }) body: JSON.stringify({ message })
}); });
@ -197,24 +202,27 @@ if (!is_logged_in()) {
} }
} }
function handleSuggestionClick(event) {
const button = event.target.closest('.suggestion-card');
if (button) {
const text = button.querySelector('p').textContent;
userInput.value = text;
userInput.focus();
}
}
sendBtn.addEventListener('click', sendMessage); sendBtn.addEventListener('click', sendMessage);
userInput.addEventListener('keypress', (e) => { userInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter' && !e.shiftKey) { if (e.key === 'Enter') {
e.preventDefault(); e.preventDefault();
sendMessage(); sendMessage();
} }
}); });
userInput.addEventListener('input', () => { document.querySelectorAll('.suggestion-card').forEach(card => {
const count = userInput.value.length; card.addEventListener('click', handleSuggestionClick);
charCounter.textContent = `${count} / 4000`;
}); });
// Auto-resize textarea
userInput.addEventListener('input', () => {
userInput.style.height = 'auto';
userInput.style.height = (userInput.scrollHeight) + 'px';
});
</script> </script>
</body> </body>
</html> </html>

View File

@ -1,350 +1,225 @@
<?php <?php
require_once 'auth.php'; session_start();
if (!isset($_SESSION['user_id'])) {
// Check if user is logged in
if (!is_logged_in()) {
header('Location: login.php'); header('Location: login.php');
exit; exit;
} }
require_once 'db/config.php'; require_once 'db/config.php';
// Note: The following PHP variables are placeholders and not currently used in the new design.
// They can be integrated later when making the dashboard dynamic.
/*
$pdo = db(); $pdo = db();
// Pagination for Candidates
$candidate_page = isset($_GET['candidate_page']) ? (int)$_GET['candidate_page'] : 1;
$limit = 5;
$candidate_offset = ($candidate_page - 1) * $limit;
$stmt = $pdo->prepare("SELECT * FROM candidates LIMIT :limit OFFSET :offset");
$stmt->bindParam(':limit', $limit, PDO::PARAM_INT);
$stmt->bindParam(':offset', $candidate_offset, PDO::PARAM_INT);
$stmt->execute();
$candidates = $stmt->fetchAll(PDO::FETCH_ASSOC);
$stmt = $pdo->query("SELECT COUNT(*) FROM candidates");
$total_candidates_records = $stmt->fetchColumn();
$total_candidate_pages = ceil($total_candidates_records / $limit);
// Pagination for Tasks
$task_page = isset($_GET['task_page']) ? (int)$_GET['task_page'] : 1;
$task_offset = ($task_page - 1) * $limit;
$stmt = $pdo->prepare("SELECT * FROM tasks LIMIT :limit OFFSET :offset");
$stmt->bindParam(':limit', $limit, PDO::PARAM_INT);
$stmt->bindParam(':offset', $task_offset, PDO::PARAM_INT);
$stmt->execute();
$tasks = $stmt->fetchAll(PDO::FETCH_ASSOC);
$stmt = $pdo->query("SELECT COUNT(*) FROM tasks");
$total_tasks_records = $stmt->fetchColumn();
$total_task_pages = ceil($total_tasks_records / $limit);
// Handle form submissions for adding candidates and tasks
$message = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (isset($_POST['add_candidate'])) {
$name = $_POST['name'];
$email = $_POST['email'];
$status = $_POST['status'];
$stmt = $pdo->prepare("INSERT INTO candidates (name, email, status) VALUES (:name, :email, :status)");
$stmt->execute(['name' => $name, 'email' => $email, 'status' => $status]);
$message = 'Candidate added successfully!';
}
if (isset($_POST['add_task'])) {
$title = $_POST['title'];
$status = $_POST['status'];
$assigned_to = $_POST['assigned_to'];
$stmt = $pdo->prepare("INSERT INTO tasks (title, status, assigned_to) VALUES (:title, :status, :assigned_to)");
$stmt->execute(['title' => $title, 'status' => $status, 'assigned_to' => $assigned_to]);
$message = 'Task added successfully!';
}
// Redirect to the same page to avoid form resubmission
header("Location: dashboard.php?tab=" . (isset($_POST['add_candidate']) ? 'candidates' : 'tasks'));
exit;
}
// Fetch data for overview
$total_candidates = $pdo->query("SELECT COUNT(*) FROM candidates")->fetchColumn(); $total_candidates = $pdo->query("SELECT COUNT(*) FROM candidates")->fetchColumn();
$total_tasks = $pdo->query("SELECT COUNT(*) FROM tasks")->fetchColumn(); $total_tasks = $pdo->query("SELECT COUNT(*) FROM tasks")->fetchColumn();
$completed_tasks = $pdo->query("SELECT COUNT(*) FROM tasks WHERE status = 'Completed'")->fetchColumn(); $completed_tasks = $pdo->query("SELECT COUNT(*) FROM tasks WHERE status = 'Completed'")->fetchColumn();
$completion_rate = ($total_tasks > 0) ? ($completed_tasks / $total_tasks) * 100 : 0;
$open_cases = 12;
*/
?> ?>
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Dashboard</title> <title>Dashboard - FinMox</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet"> <script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="assets/css/custom.css?v=<?php echo time(); ?>"> <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script src="https://unpkg.com/lucide@latest"></script>
<style>
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap');
* {
font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
}
body {
background-color: #fafafa;
-webkit-font-smoothing: antialiased;
}
.stat-card .percentage.positive { color: #22c55e; }
.stat-card .percentage.negative { color: #ef4444; }
</style>
</head> </head>
<body> <body class="bg-gray-100">
<header class="header d-flex justify-content-between align-items-center"> <div class="flex h-screen bg-gray-200">
<div class="logo"> <!-- Sidebar -->
<div class="w-64 bg-white shadow-md">
<div class="p-6">
<a href="app.php"> <a href="app.php">
<img src="assets/pasted-20251120-051320-b2b0cdfa.png" alt="FinMox Logo" style="height: 32px;"> <img src="assets/pasted-20251120-051320-b2b0cdfa.png" alt="FinMox Logo" style="height: 32px;">
</a> </a>
</div> </div>
<nav class="d-flex align-items-center"> <nav class="mt-6">
<a href="app.php" class="btn btn-outline-primary me-2">Home</a> <a href="dashboard.php" class="block py-2.5 px-4 rounded transition duration-200 bg-gray-200 text-gray-900 font-semibold">Dashboard</a>
<a href="workflows.php" class="btn btn-outline-primary me-2">Workflows</a> <a href="chat.php" class="block py-2.5 px-4 rounded transition duration-200 hover:bg-gray-200 text-gray-700">Chat</a>
<a href="settings.php" class="btn btn-outline-primary me-3">Settings</a> <a href="workflows.php" class="block py-2.5 px-4 rounded transition duration-200 hover:bg-gray-200 text-gray-700">Workflows</a>
<div class="dropdown"> <a href="roles.php" class="block py-2.5 px-4 rounded transition duration-200 hover:bg-gray-200 text-gray-700">Roles</a>
<button class="btn btn-outline-secondary dropdown-toggle" type="button" id="userDropdown" data-bs-toggle="dropdown" aria-expanded="false"> <a href="settings.php" class="block py-2.5 px-4 rounded transition duration-200 hover:bg-gray-200 text-gray-700">Settings</a>
<?php echo htmlspecialchars($_SESSION['username']); ?> <a href="logout.php" class="block py-2.5 px-4 rounded transition duration-200 hover:bg-gray-200 text-gray-700">Logout</a>
</button>
<ul class="dropdown-menu dropdown-menu-end" aria-labelledby="userDropdown">
<li><a class="dropdown-item" href="roles.php">Manage Roles</a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" href="logout.php">Logout</a></li>
</ul>
</div>
</nav> </nav>
</header> </div>
<main class="container-fluid mt-4"> <!-- Main content -->
<h2 class="mb-4">Dashboard</h2> <main class="flex-1 overflow-x-hidden overflow-y-auto bg-[#fafafa]">
<div class="max-w-7xl mx-auto px-4 sm:px-6 md:px-8 py-8">
<!-- Header -->
<div class="mb-8">
<h1 class="text-3xl font-bold text-gray-900">HR Dashboard</h1>
<p class="text-gray-600 mt-1">Overview of recruiting, onboarding, and HR operations</p>
</div>
<?php if ($message): ?> <!-- Key Metrics -->
<div class="alert alert-success"><?php echo $message; ?></div> <div class="mb-8">
<?php endif; ?> <h2 class="text-xl font-semibold text-gray-900 mb-4">Key Metrics</h2>
<div class="grid grid-cols-2 md:grid-cols-4 gap-6">
<div class="stat-card bg-white p-6 rounded-xl shadow-sm border border-gray-200">
<p class="text-gray-500 text-sm">Active Candidates</p>
<div class="flex items-baseline space-x-2 mt-1">
<p class="text-3xl font-bold text-gray-900">47</p>
<p class="percentage positive text-sm font-semibold">+12%</p>
</div>
</div>
<div class="stat-card bg-white p-6 rounded-xl shadow-sm border border-gray-200">
<p class="text-gray-500 text-sm">Open Cases</p>
<div class="flex items-baseline space-x-2 mt-1">
<p class="text-3xl font-bold text-gray-900">23</p>
<p class="percentage negative text-sm font-semibold">-8%</p>
</div>
</div>
<div class="stat-card bg-white p-6 rounded-xl shadow-sm border border-gray-200">
<p class="text-gray-500 text-sm">Onboarding Tasks</p>
<div class="flex items-baseline space-x-2 mt-1">
<p class="text-3xl font-bold text-gray-900">156</p>
<p class="percentage positive text-sm font-semibold">+5%</p>
</div>
</div>
<div class="stat-card bg-white p-6 rounded-xl shadow-sm border border-gray-200">
<p class="text-gray-500 text-sm">Completion Rate</p>
<div class="flex items-baseline space-x-2 mt-1">
<p class="text-3xl font-bold text-gray-900">94%</p>
<p class="percentage positive text-sm font-semibold">+2%</p>
</div>
</div>
</div>
</div>
<!-- Nav tabs --> <!-- Main Grid -->
<ul class="nav nav-tabs" id="dashboardTab" role="tablist"> <div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
<li class="nav-item" role="presentation"> <!-- Hiring Funnel (Left Column) -->
<button class="nav-link active" id="overview-tab" data-bs-toggle="tab" data-bs-target="#overview" type="button" role="tab" aria-controls="overview" aria-selected="true">Overview</button> <div class="lg:col-span-2">
<div class="bg-white p-6 rounded-xl shadow-sm border border-gray-200">
<h3 class="text-lg font-semibold text-gray-900 mb-4">Hiring Funnel</h3>
<div class="h-80">
<canvas id="hiringFunnelChart"></canvas>
</div>
</div>
</div>
<!-- Recent Activity (Right Column) -->
<div>
<div class="bg-white p-6 rounded-xl shadow-sm border border-gray-200">
<h3 class="text-lg font-semibold text-gray-900 mb-4">Recent Activity</h3>
<ul class="space-y-4">
<li class="flex items-center">
<div class="p-2 bg-blue-100 rounded-full"><i data-lucide="user-plus" class="w-5 h-5 text-blue-600"></i></div>
<div class="ml-3"><p class="text-sm text-gray-700">New candidate profile created</p><p class="text-xs text-gray-500">5 minutes ago</p></div>
</li> </li>
<?php if (hasPermission('view_candidates')) { ?> <li class="flex items-center">
<li class="nav-item" role="presentation"> <div class="p-2 bg-yellow-100 rounded-full"><i data-lucide="file-text" class="w-5 h-5 text-yellow-600"></i></div>
<button class="nav-link" id="candidates-tab" data-bs-toggle="tab" data-bs-target="#candidates" type="button" role="tab" aria-controls="candidates" aria-selected="false">Candidates</button> <div class="ml-3"><p class="text-sm text-gray-700">Case updated</p><p class="text-xs text-gray-500">23 minutes ago</p></div>
</li> </li>
<?php } ?> <li class="flex items-center">
<?php if (hasPermission('view_tasks')) { ?> <div class="p-2 bg-green-100 rounded-full"><i data-lucide="check-circle" class="w-5 h-5 text-green-600"></i></div>
<li class="nav-item" role="presentation"> <div class="ml-3"><p class="text-sm text-gray-700">Onboarding task completed</p><p class="text-xs text-gray-500">1 hour ago</p></div>
<button class="nav-link" id="tasks-tab" data-bs-toggle="tab" data-bs-target="#tasks" type="button" role="tab" aria-controls="tasks" aria-selected="false">Tasks</button> </li>
<li class="flex items-center">
<div class="p-2 bg-red-100 rounded-full"><i data-lucide="clock" class="w-5 h-5 text-red-600"></i></div>
<div class="ml-3"><p class="text-sm text-gray-700">Deadline approaching</p><p class="text-xs text-gray-500">2 hours ago</p></div>
</li> </li>
<?php } ?>
</ul> </ul>
<!-- Tab content -->
<div class="tab-content" id="dashboardTabContent">
<!-- Overview Tab -->
<div class="tab-pane fade show active" id="overview" role="tabpanel" aria-labelledby="overview-tab">
<div class="row mb-4 mt-4">
<div class="col-md-4">
<div class="card text-center shadow-sm">
<div class="card-body">
<h5 class="card-title">Total Candidates</h5>
<p class="card-text fs-4"><?php echo $total_candidates; ?></p>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card text-center shadow-sm">
<div class="card-body">
<h5 class="card-title">Total Tasks</h5>
<p class="card-text fs-4"><?php echo $total_tasks; ?></p>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card text-center shadow-sm">
<div class="card-body">
<h5 class="card-title">Completed Tasks</h5>
<p class="card-text fs-4"><?php echo $completed_tasks; ?></p>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
<!-- Candidates Tab --> <!-- Open HR Cases -->
<div class="tab-pane fade" id="candidates" role="tabpanel" aria-labelledby="candidates-tab"> <div class="mt-8">
<div class="card shadow-sm mt-4"> <div class="bg-white p-6 rounded-xl shadow-sm border border-gray-200">
<div class="card-body"> <h3 class="text-lg font-semibold text-gray-900 mb-4">Open HR Cases</h3>
<div class="d-flex justify-content-between align-items-center mb-3"> <table class="w-full text-left">
<h5 class="card-title">Candidates</h5>
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addCandidateModal">Add Candidate</button>
</div>
<div class="table-responsive">
<table class="table table-striped">
<thead> <thead>
<tr> <tr>
<th>Name</th> <th class="p-2 text-sm font-semibold text-gray-600">Case</th>
<th>Email</th> <th class="p-2 text-sm font-semibold text-gray-600">Priority</th>
<th>Status</th> <th class="p-2 text-sm font-semibold text-gray-600">Assignee</th>
<th>Actions</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<?php foreach ($candidates as $candidate) { ?> <tr class="border-t border-gray-200">
<tr> <td class="p-3 text-sm text-gray-800">Performance Review - Marcus Chen</td>
<td><?php echo htmlspecialchars($candidate['name']); ?></td> <td class="p-3"><span class="px-2 py-1 text-xs font-semibold text-red-800 bg-red-100 rounded-full">High</span></td>
<td><?php echo htmlspecialchars($candidate['email']); ?></td> <td class="p-3 text-sm text-gray-800">John Doe</td>
<td><span class="badge bg-secondary"><?php echo htmlspecialchars($candidate['status']); ?></span></td> </tr>
<td> <tr class="border-t border-gray-200">
<a href="edit_candidate.php?id=<?php echo $candidate['id']; ?>" class="btn btn-sm btn-outline-primary">Edit</a> <td class="p-3 text-sm text-gray-800">Background Check - Sarah Johnson</td>
<a href="delete_candidate.php?id=<?php echo $candidate['id']; ?>" class="btn btn-sm btn-outline-danger" onclick="return confirm('Are you sure?')">Delete</a> <td class="p-3"><span class="px-2 py-1 text-xs font-semibold text-yellow-800 bg-yellow-100 rounded-full">Medium</span></td>
</td> <td class="p-3 text-sm text-gray-800">Jane Smith</td>
</tr>
<tr class="border-t border-gray-200">
<td class="p-3 text-sm text-gray-800">Onboarding Checklist - Emily Davis</td>
<td class="p-3"><span class="px-2 py-1 text-xs font-semibold text-green-800 bg-green-100 rounded-full">Low</span></td>
<td class="p-3 text-sm text-gray-800">Alice Johnson</td>
</tr> </tr>
<?php } ?>
</tbody> </tbody>
</table> </table>
</div> </div>
<nav>
<ul class="pagination">
<?php for ($i = 1; $i <= $total_candidate_pages; $i++) { ?>
<li class="page-item <?php if ($i == $candidate_page) echo 'active'; ?>"><a class="page-link" href="?candidate_page=<?php echo $i; ?>&tab=candidates"><?php echo $i; ?></a></li>
<?php } ?>
</ul>
</nav>
</div>
</div>
</div> </div>
<!-- Tasks Tab -->
<div class="tab-pane fade" id="tasks" role="tabpanel" aria-labelledby="tasks-tab">
<div class="card shadow-sm mt-4">
<div class="card-body">
<div class="d-flex justify-content-between align-items-center mb-3">
<h5 class="card-title">Tasks</h5>
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addTaskModal">Add Task</button>
</div>
<div class="table-responsive">
<table class="table table-striped">
<thead>
<tr>
<th>Title</th>
<th>Status</th>
<th>Assigned To</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<?php foreach ($tasks as $task) { ?>
<tr>
<td><?php echo htmlspecialchars($task['title']); ?></td>
<td><span class="badge bg-info"><?php echo htmlspecialchars($task['status']); ?></span></td>
<td><?php echo htmlspecialchars($task['assigned_to'] ?? 'N/A'); ?></td>
<td>
<a href="edit_task.php?id=<?php echo $task['id']; ?>" class="btn btn-sm btn-outline-primary">Edit</a>
<a href="delete_task.php?id=<?php echo $task['id']; ?>" class="btn btn-sm btn-outline-danger" onclick="return confirm('Are you sure?')">Delete</a>
</td>
</tr>
<?php } ?>
</tbody>
</table>
</div>
<nav>
<ul class="pagination">
<?php for ($i = 1; $i <= $total_task_pages; $i++) { ?>
<li class="page-item <?php if ($i == $task_page) echo 'active'; ?>"><a class="page-link" href="?task_page=<?php echo $i; ?>&tab=tasks"><?php echo $i; ?></a></li>
<?php } ?>
</ul>
</nav>
</div>
</div>
</div>
</div> </div>
</main> </main>
<!-- Add Candidate Modal -->
<div class="modal fade" id="addCandidateModal" tabindex="-1" aria-labelledby="addCandidateModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="addCandidateModalLabel">Add New Candidate</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div> </div>
<div class="modal-body">
<form method="POST" action="dashboard.php">
<input type="hidden" name="add_candidate" value="1">
<div class="mb-3">
<label for="name" class="form-label">Name</label>
<input type="text" class="form-control" id="name" name="name" required>
</div>
<div class="mb-3">
<label for="email" class="form-label">Email</label>
<input type="email" class="form-control" id="email" name="email" required>
</div>
<div class="mb-3">
<label for="status" class="form-label">Status</label>
<select class="form-select" id="status" name="status">
<option>Applied</option>
<option>Interviewing</option>
<option>Offered</option>
<option>Hired</option>
</select>
</div>
<button type="submit" class="btn btn-primary">Save Candidate</button>
</form>
</div>
</div>
</div>
</div>
<!-- Add Task Modal -->
<div class="modal fade" id="addTaskModal" tabindex="-1" aria-labelledby="addTaskModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="addTaskModalLabel">Add New Task</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<form method="POST" action="dashboard.php">
<input type="hidden" name="add_task" value="1">
<div class="mb-3">
<label for="title" class="form-label">Title</label>
<input type="text" class="form-control" id="title" name="title" required>
</div>
<div class="mb-3">
<label for="status" class="form-label">Status</label>
<select class="form-select" id="status" name="status">
<option>Pending</option>
<option>In Progress</option>
<option>Completed</option>
</select>
</div>
<div class="mb-3">
<label for="assigned_to" class="form-label">Assigned To</label>
<input type="text" class="form-control" id="assigned_to" name="assigned_to">
</div>
<button type="submit" class="btn btn-primary">Save Task</button>
</form>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
<script> <script>
document.addEventListener('DOMContentLoaded', function () { lucide.createIcons();
// Tab persistence
const urlParams = new URLSearchParams(window.location.search);
const tab = urlParams.get('tab');
if (tab) {
const tabEl = document.querySelector('#' + tab + '-tab');
if(tabEl) {
const tab = new bootstrap.Tab(tabEl);
tab.show();
}
}
// Reset URL after modal close const ctx = document.getElementById('hiringFunnelChart').getContext('2d');
const modals = document.querySelectorAll('.modal'); const hiringFunnelChart = new Chart(ctx, {
modals.forEach(modal => { type: 'bar',
modal.addEventListener('hidden.bs.modal', function () { data: {
// To refresh the content, we reload the page with the correct tab active labels: ['Applications', 'Screening', 'Interviews', 'Offers', 'Hires'],
const activeTab = document.querySelector('.nav-tabs .nav-link.active').id.replace('-tab', ''); datasets: [{
window.location.href = 'dashboard.php?tab=' + activeTab; label: 'Candidates',
}); data: [150, 100, 60, 30, 15],
}); backgroundColor: '#3b82f6',
borderRadius: 4,
barThickness: 30,
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
display: false
}
},
scales: {
y: {
beginAtZero: true,
grid: {
color: '#e5e7eb',
borderDash: [2, 4],
},
ticks: {
color: '#6b7280'
}
},
x: {
grid: {
display: false,
},
ticks: {
color: '#6b7280'
}
}
}
}
}); });
</script> </script>
</body> </body>

View File

@ -0,0 +1 @@
ALTER TABLE `users` ADD COLUMN `email` VARCHAR(255) NOT NULL UNIQUE AFTER `password`;

View File

@ -7,6 +7,11 @@ if (is_logged_in()) {
} }
$error = ''; $error = '';
$success = '';
if (isset($_GET['registered']) && $_GET['registered'] === 'true') {
$success = 'Registration successful! You can now log in.';
}
if ($_SERVER['REQUEST_METHOD'] === 'POST') { if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$username = $_POST['username'] ?? ''; $username = $_POST['username'] ?? '';
@ -15,12 +20,17 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (empty($username) || empty($password)) { if (empty($username) || empty($password)) {
$error = 'Please fill in all fields.'; $error = 'Please fill in all fields.';
} else { } else {
try {
if (login_user($username, $password)) { if (login_user($username, $password)) {
header('Location: app.php'); header('Location: app.php');
exit; exit;
} else { } else {
$error = 'Invalid username or password.'; $error = 'Invalid username or password.';
} }
} catch (PDOException $e) {
error_log("Database connection error during login: " . $e->getMessage());
$error = "A server error occurred. Please try again later.";
}
} }
} }
?> ?>
@ -42,6 +52,9 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
<h3>Login</h3> <h3>Login</h3>
</div> </div>
<div class="card-body"> <div class="card-body">
<?php if ($success): ?>
<div class="alert alert-success"><?php echo $success; ?></div>
<?php endif; ?>
<?php if ($error): ?> <?php if ($error): ?>
<div class="alert alert-danger"><?php echo $error; ?></div> <div class="alert alert-danger"><?php echo $error; ?></div>
<?php endif; ?> <?php endif; ?>

View File

@ -5,16 +5,23 @@ $error = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST') { if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$username = $_POST['username'] ?? ''; $username = $_POST['username'] ?? '';
$email = $_POST['email'] ?? '';
$password = $_POST['password'] ?? ''; $password = $_POST['password'] ?? '';
if (empty($username) || empty($password)) { if (empty($username) || empty($password) || empty($email)) {
$error = 'Please fill in all fields.'; $error = 'Please fill in all fields.';
} else { } else {
if (register_user($username, $password)) { try {
header('Location: login.php'); if (register_user($username, $password, $email)) {
// Redirect to login page with a success message
header('Location: login.php?registered=true');
exit; exit;
} else { } else {
$error = 'Username already exists.'; $error = 'Username or email already exists.';
}
} catch (PDOException $e) {
error_log("Database error during registration: " . $e->getMessage());
$error = "A server error occurred. Please try again later.";
} }
} }
} }
@ -45,6 +52,10 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
<label for="username" class="form-label">Username</label> <label for="username" class="form-label">Username</label>
<input type="text" class="form-control" id="username" name="username" required> <input type="text" class="form-control" id="username" name="username" required>
</div> </div>
<div class="mb-3">
<label for="email" class="form-label">Email</label>
<input type="email" class="form-control" id="email" name="email" required>
</div>
<div class="mb-3"> <div class="mb-3">
<label for="password" class="form-label">Password</label> <label for="password" class="form-label">Password</label>
<input type="password" class="form-control" id="password" name="password" required> <input type="password" class="form-control" id="password" name="password" required>

101
roles.php
View File

@ -43,57 +43,77 @@ $roles = $stmt->fetchAll();
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Role Management</title> <title>Role Management</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet"> <script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="assets/css/custom.css"> <style>
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap');
* {
font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
}
body {
background-color: #fafafa;
-webkit-font-smoothing: antialiased;
}
</style>
</head> </head>
<body> <body class="bg-gray-100">
<header class="header d-flex justify-content-between align-items-center"> <div class="flex h-screen bg-gray-200">
<div class="logo"> <!-- Sidebar -->
<div class="w-64 bg-white shadow-md">
<div class="p-6">
<a href="app.php"> <a href="app.php">
<img src="assets/pasted-20251120-051320-b2b0cdfa.png" alt="FinMox Logo" style="height: 32px;"> <img src="assets/pasted-20251120-051320-b2b0cdfa.png" alt="FinMox Logo" style="height: 32px;">
</a> </a>
</div> </div>
<nav class="d-flex align-items-center"> <nav class="mt-6">
<a href="app.php" class="btn btn-outline-primary me-2">Home</a> <a href="dashboard.php" class="block py-2.5 px-4 rounded transition duration-200 hover:bg-gray-200 text-gray-700">Dashboard</a>
<a href="dashboard.php" class="btn btn-outline-primary me-2">Dashboard</a> <a href="chat.php" class="block py-2.5 px-4 rounded transition duration-200 hover:bg-gray-200 text-gray-700">Chat</a>
<a href="workflows.php" class="btn btn-outline-primary me-2">Workflows</a> <a href="workflows.php" class="block py-2.5 px-4 rounded transition duration-200 hover:bg-gray-200 text-gray-700">Workflows</a>
<a href="settings.php" class="btn btn-outline-primary me-3">Settings</a> <a href="roles.php" class="block py-2.5 px-4 rounded transition duration-200 bg-gray-200 text-gray-900 font-semibold">Roles</a>
<div class="dropdown"> <a href="settings.php" class="block py-2.5 px-4 rounded transition duration-200 hover:bg-gray-200 text-gray-700">Settings</a>
<button class="btn btn-outline-secondary dropdown-toggle" type="button" id="userDropdown" data-bs-toggle="dropdown" aria-expanded="false"> <a href="logout.php" class="block py-2.5 px-4 rounded transition duration-200 hover:bg-gray-200 text-gray-700">Logout</a>
<?php echo htmlspecialchars($_SESSION['username']); ?>
</button>
<ul class="dropdown-menu dropdown-menu-end" aria-labelledby="userDropdown">
<?php if (hasPermission('manage_roles')): ?>
<li><a class="dropdown-item" href="roles.php">Manage Roles</a></li>
<li><hr class="dropdown-divider"></li>
<?php endif; ?>
<li><a class="dropdown-item" href="logout.php">Logout</a></li>
</ul>
</div>
</nav> </nav>
</header>
<main class="container-fluid">
<div class="d-flex justify-content-between align-items-center mb-4">
<h2>Role Management</h2>
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addRoleModal">Add Role</button>
</div> </div>
<div class="card"> <!-- Main content -->
<div class="card-body"> <div class="flex-1 flex flex-col overflow-hidden">
<table class="table"> <main class="flex-1 overflow-x-hidden overflow-y-auto bg-[#fafafa] pb-20 md:pb-0">
<thead> <div class="max-w-7xl mx-auto px-4 md:px-6 py-4 md:py-8">
<div class="md:flex md:items-center md:justify-between">
<div class="flex-1 min-w-0">
<h2 class="text-2xl font-bold leading-7 text-gray-900 sm:text-3xl sm:truncate">
Role Management
</h2>
</div>
<div class="mt-4 flex md:mt-0 md:ml-4">
<button type="button" class="inline-flex items-center px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500" data-bs-toggle="modal" data-bs-target="#addRoleModal">
Add Role
</button>
</div>
</div>
<div class="mt-6 md:mt-8 flex flex-col">
<div class="-my-2 overflow-x-auto sm:-mx-6 lg:-mx-8">
<div class="py-2 align-middle inline-block min-w-full sm:px-6 lg:px-8">
<div class="shadow overflow-hidden border-b border-gray-200 sm:rounded-lg">
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50">
<tr> <tr>
<th>Role Name</th> <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
<th>Actions</th> Role Name
</th>
<th scope="col" class="relative px-6 py-3">
<span class="sr-only">Edit</span>
</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody class="bg-white divide-y divide-gray-200">
<?php foreach ($roles as $role): ?> <?php foreach ($roles as $role): ?>
<tr> <tr>
<td><?php echo htmlspecialchars($role['name']); ?></td> <td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">
<td> <?php echo htmlspecialchars($role['name']); ?>
<a href="edit_role.php?role_id=<?php echo $role['id']; ?>" class="btn btn-sm btn-outline-primary">Edit</a> </td>
<td class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
<a href="edit_role.php?role_id=<?php echo $role['id']; ?>" class="text-indigo-600 hover:text-indigo-900">Edit</a>
</td> </td>
</tr> </tr>
<?php endforeach; ?> <?php endforeach; ?>
@ -101,7 +121,12 @@ $roles = $stmt->fetchAll();
</table> </table>
</div> </div>
</div> </div>
</div>
</div>
</div>
</main> </main>
</div>
</div>
<!-- Add Role Modal --> <!-- Add Role Modal -->
<div class="modal fade" id="addRoleModal" tabindex="-1" aria-labelledby="addRoleModalLabel" aria-hidden="true"> <div class="modal fade" id="addRoleModal" tabindex="-1" aria-labelledby="addRoleModalLabel" aria-hidden="true">

View File

@ -4,6 +4,19 @@ if (!isset($_SESSION['user_id'])) {
header('Location: login.php'); header('Location: login.php');
exit; exit;
} }
// Mock data for roles and users, replace with database data
$roles = [
['id' => 1, 'name' => 'Admin', 'description' => 'Full access to all features and settings.'],
['id' => 2, 'name' => 'HR Manager', 'description' => 'Can manage candidates, workflows, and view reports.'],
['id' => 3, 'name' => 'Team Member', 'description' => 'Limited access to assigned tasks and candidates.']
];
$users = [
['id' => 1, 'name' => 'Alexandre Dubois', 'email' => 'alex@finmox.com', 'role' => 'Admin', 'status' => 'Active', 'avatar' => 'https://i.pravatar.cc/40?u=1'],
['id' => 2, 'name' => 'Béatrice Martin', 'email' => 'beatrice@finmox.com', 'role' => 'HR Manager', 'status' => 'Active', 'avatar' => 'https://i.pravatar.cc/40?u=2'],
['id' => 3, 'name' => 'Charles Leclerc', 'email' => 'charles@finmox.com', 'role' => 'Team Member', 'status' => 'Invited', 'avatar' => 'https://i.pravatar.cc/40?u=3'],
];
?> ?>
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
@ -18,17 +31,9 @@ if (!isset($_SESSION['user_id'])) {
font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif; font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
} }
body { body {
background-color: #fafafa;
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
} }
.tab-active {
border-bottom-color: #000;
color: #000;
font-weight: 600;
}
.tab-inactive {
border-bottom-color: transparent;
color: #6b7280;
}
</style> </style>
</head> </head>
<body class="bg-gray-100"> <body class="bg-gray-100">
@ -52,194 +57,135 @@ if (!isset($_SESSION['user_id'])) {
<!-- Main content --> <!-- Main content -->
<div class="flex-1 flex flex-col overflow-hidden"> <div class="flex-1 flex flex-col overflow-hidden">
<header class="flex justify-between items-center p-6 bg-white border-b"> <main class="flex-1 overflow-x-hidden overflow-y-auto bg-[#fafafa] pb-20 md:pb-0">
<h1 class="text-2xl font-bold text-gray-900">Settings</h1> <div class="max-w-7xl mx-auto px-4 md:px-6 py-4 md:py-8">
<div>
<!-- Any header content can go here --> <!-- SettingsHeader -->
<div class="md:flex md:items-center md:justify-between">
<div class="flex-1 min-w-0">
<h2 class="text-2xl font-bold leading-7 text-gray-900 sm:text-3xl sm:truncate">
Settings
</h2>
<p class="mt-1 text-sm text-gray-500">
Manage your workspace and team settings.
</p>
</div>
<div class="mt-4 flex md:mt-0 md:ml-4">
<button type="button" class="inline-flex items-center px-4 py-2 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
Invite team
</button>
</div> </div>
</header>
<main class="flex-1 overflow-x-hidden overflow-y-auto bg-gray-100 p-6">
<div>
<div class="border-b border-gray-200">
<nav class="-mb-px flex space-x-8" aria-label="Tabs">
<button onclick="changeTab('profile')" id="tab-profile" class="whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm tab-active">Company Profile</button>
<button onclick="changeTab('integrations')" id="tab-integrations" class="whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm tab-inactive">Integrations</button>
<button onclick="changeTab('team')" id="tab-team" class="whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm tab-inactive">Team Members</button>
<button onclick="changeTab('billing')" id="tab-billing" class="whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm tab-inactive">Billing</button>
</nav>
</div> </div>
<!-- Tab Content --> <div class="mt-6 md:mt-8 space-y-4 md:space-y-6">
<div id="content-profile" class="mt-6">
<div class="bg-white shadow rounded-lg p-6"> <!-- PermissionsTable -->
<h2 class="text-xl font-semibold mb-4">Company Profile</h2> <div class="bg-white shadow-sm rounded-lg">
<form> <div class="px-4 py-5 sm:px-6">
<div class="mb-4"> <h3 class="text-lg leading-6 font-medium text-gray-900">Roles & Permissions</h3>
<p class="mt-1 text-sm text-gray-500">Define what each role can see and do.</p>
</div>
<div class="border-t border-gray-200">
<ul class="divide-y divide-gray-200">
<?php foreach ($roles as $role): ?>
<li class="p-4 sm:p-6 hover:bg-gray-50">
<div class="flex items-center justify-between">
<div>
<p class="font-medium text-gray-900"><?php echo htmlspecialchars($role['name']); ?></p>
<p class="text-sm text-gray-500"><?php echo htmlspecialchars($role['description']); ?></p>
</div>
<a href="edit_role.php?id=<?php echo $role['id']; ?>" class="text-sm font-medium text-indigo-600 hover:text-indigo-800">Edit</a>
</div>
</li>
<?php endforeach; ?>
</ul>
</div>
</div>
<!-- WorkspaceSettings -->
<div class="bg-white shadow-sm rounded-lg">
<div class="px-4 py-5 sm:px-6">
<h3 class="text-lg leading-6 font-medium text-gray-900">Workspace</h3>
<p class="mt-1 text-sm text-gray-500">Manage your company profile and branding.</p>
</div>
<div class="border-t border-gray-200 px-4 py-5 sm:p-6">
<form class="space-y-6">
<div>
<label for="company-name" class="block text-sm font-medium text-gray-700">Company Name</label> <label for="company-name" class="block text-sm font-medium text-gray-700">Company Name</label>
<input type="text" id="company-name" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-black focus:ring-black sm:text-sm" placeholder="FinMox"> <input type="text" id="company-name" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm" value="FinMox">
</div> </div>
<div class="mb-4"> <div>
<label for="logo-upload" class="block text-sm font-medium text-gray-700">Company Logo</label> <label class="block text-sm font-medium text-gray-700">Company Logo</label>
<div class="mt-1 flex items-center"> <div class="mt-2 flex items-center space-x-4">
<span class="inline-block h-12 w-12 rounded-full overflow-hidden bg-gray-100"> <img src="assets/pasted-20251120-051320-b2b0cdfa.png" alt="Logo" class="h-10 w-auto">
<img src="assets/pasted-20251120-051320-b2b0cdfa.png" alt="Current Logo"> <input type="file" id="logo-upload" class="text-sm text-gray-500 file:mr-4 file:py-2 file:px-4 file:rounded-md file:border-0 file:text-sm file:font-semibold file:bg-indigo-50 file:text-indigo-600 hover:file:bg-indigo-100">
</span>
<input type="file" id="logo-upload" class="ml-5 bg-white py-2 px-3 border border-gray-300 rounded-md shadow-sm text-sm leading-4 font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-black">
</div> </div>
</div> </div>
<div class="mb-4">
<label for="timezone" class="block text-sm font-medium text-gray-700">Timezone</label>
<select id="timezone" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-black focus:ring-black sm:text-sm">
<option>Pacific Standard Time (PST)</option>
<option selected>Eastern Standard Time (EST)</option>
</select>
</div>
<div class="mb-4">
<label for="date-format" class="block text-sm font-medium text-gray-700">Date Format</label>
<select id="date-format" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-black focus:ring-black sm:text-sm">
<option>MM/DD/YYYY</option>
<option>DD/MM/YYYY</option>
<option selected>YYYY-MM-DD</option>
</select>
</div>
<div class="flex justify-end"> <div class="flex justify-end">
<button type="submit" class="bg-black text-white px-4 py-2 rounded-lg text-sm font-medium">Save Changes</button> <button type="submit" class="inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
Save Changes
</button>
</div> </div>
</form> </form>
</div> </div>
</div> </div>
<div id="content-integrations" class="hidden mt-6"> <!-- UserManagement -->
<div class="bg-white shadow rounded-lg p-6"> <div class="bg-white shadow-sm rounded-lg">
<h2 class="text-xl font-semibold mb-4">Integrations</h2> <div class="px-4 py-5 sm:px-6">
<div class="space-y-4"> <h3 class="text-lg leading-6 font-medium text-gray-900">User Management</h3>
<!-- Gmail --> <p class="mt-1 text-sm text-gray-500">Invite, remove, and manage your team members.</p>
<div class="flex items-center justify-between p-4 border rounded-lg">
<div class="flex items-center gap-4">
<img src="https://upload.wikimedia.org/wikipedia/commons/thumb/7/7e/Gmail_icon_%282020%29.svg/1200px-Gmail_icon_%282020%29.svg.png" alt="Gmail" class="h-8 w-8">
<span class="font-medium">Gmail</span>
</div>
<div class="flex items-center gap-4">
<span class="text-sm text-green-600 font-semibold">Connected</span>
<button class="bg-gray-200 text-gray-700 px-4 py-2 rounded-lg text-sm font-medium">Configure</button>
</div>
</div>
<!-- Slack -->
<div class="flex items-center justify-between p-4 border rounded-lg">
<div class="flex items-center gap-4">
<img src="https://upload.wikimedia.org/wikipedia/commons/d/d5/Slack_icon_2019.svg" alt="Slack" class="h-8 w-8">
<span class="font-medium">Slack</span>
</div>
<div class="flex items-center gap-4">
<span class="text-sm text-gray-500">Not Connected</span>
<button class="bg-black text-white px-4 py-2 rounded-lg text-sm font-medium">Connect</button>
</div>
</div>
<!-- Google Calendar -->
<div class="flex items-center justify-between p-4 border rounded-lg">
<div class="flex items-center gap-4">
<img src="https://upload.wikimedia.org/wikipedia/commons/a/a5/Google_Calendar_icon_%282020%29.svg" alt="Google Calendar" class="h-8 w-8">
<span class="font-medium">Google Calendar</span>
</div>
<div class="flex items-center gap-4">
<span class="text-sm text-green-600 font-semibold">Connected</span>
<button class="bg-gray-200 text-gray-700 px-4 py-2 rounded-lg text-sm font-medium">Configure</button>
</div>
</div>
<!-- Generic ATS -->
<div class="flex items-center justify-between p-4 border rounded-lg">
<div class="flex items-center gap-4">
<svg class="h-8 w-8 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path></svg>
<span class="font-medium">Your ATS</span>
</div>
<div class="flex items-center gap-4">
<span class="text-sm text-gray-500">Not Connected</span>
<button class="bg-black text-white px-4 py-2 rounded-lg text-sm font-medium">Connect</button>
</div>
</div>
</div>
</div>
</div>
<div id="content-team" class="hidden mt-6">
<div class="bg-white shadow rounded-lg">
<div class="flex justify-between items-center p-6">
<h2 class="text-xl font-semibold">Team Members</h2>
<button class="bg-black text-white px-4 py-2 rounded-lg text-sm font-medium">Invite Team Member</button>
</div> </div>
<div class="border-t border-gray-200">
<table class="min-w-full divide-y divide-gray-200"> <table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50"> <thead class="bg-gray-50">
<tr> <tr>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Name</th> <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Name</th>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Email</th>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Role</th>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Status</th> <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Status</th>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Role</th>
<th scope="col" class="relative px-6 py-3"><span class="sr-only">Edit</span></th> <th scope="col" class="relative px-6 py-3"><span class="sr-only">Edit</span></th>
</tr> </tr>
</thead> </thead>
<tbody class="bg-white divide-y divide-gray-200"> <tbody class="bg-white divide-y divide-gray-200">
<?php foreach ($users as $user): ?>
<tr> <tr>
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">Admin User</td> <td class="px-6 py-4 whitespace-nowrap">
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">admin@finmox.com</td> <div class="flex items-center">
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">Admin</td> <div class="flex-shrink-0 h-10 w-10">
<td class="px-6 py-4 whitespace-nowrap"><span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-green-100 text-green-800">Active</span></td> <img class="h-10 w-10 rounded-full" src="<?php echo htmlspecialchars($user['avatar']); ?>" alt="">
<td class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium"><a href="#" class="text-indigo-600 hover:text-indigo-900">Edit</a></td> </div>
<div class="ml-4">
<div class="text-sm font-medium text-gray-900"><?php echo htmlspecialchars($user['name']); ?></div>
<div class="text-sm text-gray-500"><?php echo htmlspecialchars($user['email']); ?></div>
</div>
</div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<?php if ($user['status'] === 'Active'): ?>
<span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-green-100 text-green-800">
Active
</span>
<?php else: ?>
<span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-yellow-100 text-yellow-800">
Invited
</span>
<?php endif; ?>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500"><?php echo htmlspecialchars($user['role']); ?></td>
<td class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
<a href="#" class="text-indigo-600 hover:text-indigo-900">Edit</a>
</td>
</tr> </tr>
<!-- More users... --> <?php endforeach; ?>
</tbody> </tbody>
</table> </table>
</div> </div>
</div> </div>
<div id="content-billing" class="hidden mt-6">
<div class="bg-white shadow rounded-lg p-6">
<h2 class="text-xl font-semibold mb-4">Billing</h2>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<!-- Current Plan -->
<div class="bg-gray-50 rounded-lg p-6">
<h3 class="font-semibold text-gray-900">Current Plan</h3>
<p class="mt-2 text-3xl font-bold text-gray-900">Pro Plan</p>
<p class="mt-1 text-sm text-gray-500">$99 / month</p>
<button class="mt-4 w-full bg-black text-white px-4 py-2 rounded-lg text-sm font-medium">Upgrade Plan</button>
</div>
<!-- Usage -->
<div class="bg-gray-50 rounded-lg p-6">
<h3 class="font-semibold text-gray-900">Usage this month</h3>
<ul class="mt-4 space-y-2 text-sm">
<li class="flex justify-between"><span>Team Members</span><span>1/10</span></li>
<li class="flex justify-between"><span>Workflows</span><span>5/20</span></li>
<li class="flex justify-between"><span>API Calls</span><span>1,203 / 10,000</span></li>
</ul>
</div>
</div>
<div class="mt-6">
<h3 class="font-semibold text-gray-900">Payment Method</h3>
<div class="mt-4 flex items-center justify-between p-4 border rounded-lg">
<div class="flex items-center gap-4">
<img src="https://upload.wikimedia.org/wikipedia/commons/a/a4/Visa_Inc._logo.svg" alt="Visa" class="h-6">
<span class="font-medium">Visa ending in 1234</span>
</div>
<button class="bg-gray-200 text-gray-700 px-4 py-2 rounded-lg text-sm font-medium">Update</button>
</div>
</div>
</div>
</div> </div>
</div> </div>
</main> </main>
</div> </div>
</div> </div>
<script>
function changeTab(tabName) {
const tabs = ['profile', 'integrations', 'team', 'billing'];
tabs.forEach(tab => {
document.getElementById('tab-' + tab).classList.remove('tab-active');
document.getElementById('tab-' + tab).classList.add('tab-inactive');
document.getElementById('content-' + tab).classList.add('hidden');
});
document.getElementById('tab-' + tabName).classList.add('tab-active');
document.getElementById('tab-' + tabName).classList.remove('tab-inactive');
document.getElementById('content-' + tabName).classList.remove('hidden');
}
</script>
</body> </body>
</html> </html>

View File

@ -107,81 +107,42 @@ $workflows = $stmt->fetchAll();
<title>FinMox Workflows</title> <title>FinMox Workflows</title>
<script src="https://cdn.tailwindcss.com"></script> <script src="https://cdn.tailwindcss.com"></script>
<style> <style>
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap');
* {
font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
}
body {
background-color: #fafafa;
-webkit-font-smoothing: antialiased;
}
.trigger-dropdown-options::-webkit-scrollbar { width: 6px; } .trigger-dropdown-options::-webkit-scrollbar { width: 6px; }
.trigger-dropdown-options::-webkit-scrollbar-track { background: #f1f1f1; } .trigger-dropdown-options::-webkit-scrollbar-track { background: #f1f1f1; }
.trigger-dropdown-options::-webkit-scrollbar-thumb { background: #888; border-radius: 3px; } .trigger-dropdown-options::-webkit-scrollbar-thumb { background: #888; border-radius: 3px; }
.trigger-dropdown-options::-webkit-scrollbar-thumb:hover { background: #555; } .trigger-dropdown-options::-webkit-scrollbar-thumb:hover { background: #555; }
</style> </style>
</head> </head>
<body class="bg-gray-50"> <body class="bg-gray-100">
<div class="flex h-screen bg-gray-200">
<header class="bg-gray-800 text-white shadow-md"> <!-- Sidebar -->
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8"> <div class="w-64 bg-white shadow-md">
<div class="flex items-center justify-between h-16"> <div class="p-6">
<div class="flex items-center">
<a href="app.php"> <a href="app.php">
<img src="assets/pasted-20251120-051320-b2b0cdfa.png" alt="FinMox Logo" style="height: 32px;"> <img src="assets/pasted-20251120-051320-b2b0cdfa.png" alt="FinMox Logo" style="height: 32px;">
</a> </a>
</div> </div>
<div class="hidden md:block"> <nav class="mt-6">
<div class="ml-10 flex items-baseline space-x-4"> <a href="dashboard.php" class="block py-2.5 px-4 rounded transition duration-200 hover:bg-gray-200 text-gray-700">Dashboard</a>
<a href="app.php" class="text-gray-300 hover:bg-gray-700 hover:text-white px-3 py-2 rounded-md text-sm font-medium">Home</a> <a href="chat.php" class="block py-2.5 px-4 rounded transition duration-200 hover:bg-gray-200 text-gray-700">Chat</a>
<a href="dashboard.php" class="text-gray-300 hover:bg-gray-700 hover:text-white px-3 py-2 rounded-md text-sm font-medium">Dashboard</a> <a href="workflows.php" class="block py-2.5 px-4 rounded transition duration-200 bg-gray-200 text-gray-900 font-semibold">Workflows</a>
<a href="workflows.php" class="bg-gray-900 text-white px-3 py-2 rounded-md text-sm font-medium" aria-current="page">Workflows</a> <a href="roles.php" class="block py-2.5 px-4 rounded transition duration-200 hover:bg-gray-200 text-gray-700">Roles</a>
<a href="settings.php" class="text-gray-300 hover:bg-gray-700 hover:text-white px-3 py-2 rounded-md text-sm font-medium">Settings</a> <a href="settings.php" class="block py-2.5 px-4 rounded transition duration-200 hover:bg-gray-200 text-gray-700">Settings</a>
<a href="logout.php" class="block py-2.5 px-4 rounded transition duration-200 hover:bg-gray-200 text-gray-700">Logout</a>
<!-- User Dropdown --> </nav>
<div class="relative ml-3">
<div>
<button type="button" id="user-menu-button" class="max-w-xs bg-gray-800 rounded-full flex items-center text-sm focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-800 focus:ring-white" aria-expanded="false" aria-haspopup="true">
<span class="sr-only">Open user menu</span>
<span class="text-white px-3 py-2 rounded-md text-sm font-medium"><?php echo htmlspecialchars($_SESSION['username']); ?></span>
</button>
</div>
<div id="user-menu" class="origin-top-right absolute right-0 mt-2 w-48 rounded-md shadow-lg py-1 bg-white ring-1 ring-black ring-opacity-5 focus:outline-none hidden" role="menu" aria-orientation="vertical" aria-labelledby="user-menu-button" tabindex="-1">
<a href="roles.php" class="block px-4 py-2 text-sm text-gray-700" role="menuitem" tabindex="-1" id="user-menu-item-1">Manage Roles</a>
<a href="logout.php" class="block px-4 py-2 text-sm text-gray-700" role="menuitem" tabindex="-1" id="user-menu-item-2">Logout</a>
</div>
</div>
</div>
</div>
<div class="-mr-2 flex md:hidden">
<!-- Mobile menu button -->
<button type="button" id="mobile-menu-button" class="bg-gray-800 inline-flex items-center justify-center p-2 rounded-md text-gray-400 hover:text-white hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-800 focus:ring-white" aria-controls="mobile-menu" aria-expanded="false">
<span class="sr-only">Open main menu</span>
<svg class="block h-6 w-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" />
</svg>
<svg class="hidden h-6 w-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
</div>
</div> </div>
<!-- Mobile menu, show/hide based on menu state. --> <!-- Main content -->
<div class="md:hidden hidden" id="mobile-menu"> <div class="flex-1 flex flex-col overflow-hidden">
<div class="px-2 pt-2 pb-3 space-y-1 sm:px-3"> <main class="flex-1 overflow-x-hidden overflow-y-auto bg-[#fafafa] pb-20 md:pb-0">
<a href="app.php" class="text-gray-300 hover:bg-gray-700 hover:text-white block px-3 py-2 rounded-md text-base font-medium">Home</a>
<a href="dashboard.php" class="text-gray-300 hover:bg-gray-700 hover:text-white block px-3 py-2 rounded-md text-base font-medium">Dashboard</a>
<a href="workflows.php" class="bg-gray-900 text-white block px-3 py-2 rounded-md text-base font-medium" aria-current="page">Workflows</a>
<a href="settings.php" class="text-gray-300 hover:bg-gray-700 hover:text-white block px-3 py-2 rounded-md text-base font-medium">Settings</a>
</div>
<div class="pt-4 pb-3 border-t border-gray-700">
<div class="flex items-center px-5">
<div class="ml-3">
<div class="text-base font-medium leading-none text-white"><?php echo htmlspecialchars($_SESSION['username']); ?></div>
</div>
</div>
<div class="mt-3 px-2 space-y-1">
<a href="roles.php" class="block px-3 py-2 rounded-md text-base font-medium text-gray-400 hover:text-white hover:bg-gray-700">Manage Roles</a>
<a href="logout.php" class="block px-3 py-2 rounded-md text-base font-medium text-gray-400 hover:text-white hover:bg-gray-700">Logout</a>
</div>
</div>
</div>
</header>
<div id="workflows-page" class="min-h-screen p-8"> <div id="workflows-page" class="min-h-screen p-8">
<div class="max-w-7xl mx-auto"> <div class="max-w-7xl mx-auto">
<!-- Header --> <!-- Header -->
@ -223,6 +184,9 @@ $workflows = $stmt->fetchAll();
</div> </div>
</div> </div>
</div> </div>
</main>
</div>
</div>
<!-- Add Workflow Modal --> <!-- Add Workflow Modal -->
<div id="addWorkflowModal" class="fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full hidden"> <div id="addWorkflowModal" class="fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full hidden">
@ -330,38 +294,6 @@ $workflows = $stmt->fetchAll();
</div> </div>
<script> <script>
// Header dropdowns
const userMenuButton = document.getElementById('user-menu-button');
const userMenu = document.getElementById('user-menu');
const mobileMenuButton = document.getElementById('mobile-menu-button');
const mobileMenu = document.getElementById('mobile-menu');
if (userMenuButton) {
userMenuButton.addEventListener('click', () => {
const isExpanded = userMenuButton.getAttribute('aria-expanded') === 'true';
userMenuButton.setAttribute('aria-expanded', !isExpanded);
userMenu.classList.toggle('hidden');
});
}
if (mobileMenuButton) {
mobileMenuButton.addEventListener('click', () => {
const isExpanded = mobileMenuButton.getAttribute('aria-expanded') === 'true';
mobileMenuButton.setAttribute('aria-expanded', !isExpanded);
mobileMenu.classList.toggle('hidden');
// Toggle icons
mobileMenuButton.querySelector('svg.block').classList.toggle('hidden');
mobileMenuButton.querySelector('svg.hidden').classList.toggle('hidden');
});
}
// Close dropdowns when clicking outside
document.addEventListener('click', (event) => {
if (userMenu && userMenuButton && !userMenu.classList.contains('hidden') && !userMenuButton.contains(event.target) && !userMenu.contains(event.target)) {
userMenu.classList.add('hidden');
userMenuButton.setAttribute('aria-expanded', 'false');
}
});
// Modal and Tab JS // Modal and Tab JS
const tabTemplates = document.getElementById('tab-templates'); const tabTemplates = document.getElementById('tab-templates');
const tabScratch = document.getElementById('tab-scratch'); const tabScratch = document.getElementById('tab-scratch');