From 8fc8d737e59748c9546e062ca7b2f8979d6c4bcb Mon Sep 17 00:00:00 2001 From: deathcat Date: Thu, 12 Feb 2026 14:17:24 -0800 Subject: [PATCH] Added UI and functionality for profiles --- add_project.php | 11 +- get_data.php | 13 +- get_profiles.php | 18 +++ index.php | 311 +++++++++++++++++++++++++++-------------------- login.php | 24 +++- register.php | 14 +++ set_profile.php | 30 +++++ 7 files changed, 282 insertions(+), 139 deletions(-) create mode 100644 get_profiles.php create mode 100644 set_profile.php diff --git a/add_project.php b/add_project.php index 7975510..db6204f 100644 --- a/add_project.php +++ b/add_project.php @@ -6,6 +6,13 @@ header('Content-Type: application/json'); require_login(); $user_id = current_user_id(); +$profile_id = $_SESSION['active_profile_id'] ?? null; + +if (!$profile_id) { + http_response_code(400); + echo json_encode(['success' => false, 'error' => 'No active profile']); + exit; +} $data = json_decode(file_get_contents('php://input'), true); $name = trim($data['name'] ?? ''); @@ -16,7 +23,7 @@ if ($name === '') { exit; } -$stmt = $pdo->prepare("INSERT INTO projects (user_id, name) VALUES (?, ?)"); -$stmt->execute([$user_id, $name]); +$stmt = $pdo->prepare("INSERT INTO projects (user_id, profile_id, name) VALUES (?, ?, ?)"); +$stmt->execute([$user_id, $profile_id, $name]); echo json_encode(['success' => true, 'id' => $pdo->lastInsertId()]); diff --git a/get_data.php b/get_data.php index 4955210..9233f0a 100644 --- a/get_data.php +++ b/get_data.php @@ -6,9 +6,16 @@ header('Content-Type: application/json'); require_login(); $user_id = current_user_id(); +$profile_id = $_SESSION['active_profile_id'] ?? null; -$stmt = $pdo->prepare("SELECT * FROM projects WHERE user_id = ? ORDER BY sort_order ASC"); -$stmt->execute([$user_id]); +if (!$profile_id) { + http_response_code(400); + echo json_encode(['success' => false, 'error' => 'No active profile']); + exit; +} + +$stmt = $pdo->prepare("SELECT * FROM projects WHERE user_id = ? AND profile_id = ? ORDER BY sort_order ASC"); +$stmt->execute([$user_id, $profile_id]); $projects = $stmt->fetchAll(); foreach ($projects as &$project) { @@ -23,4 +30,4 @@ foreach ($projects as &$project) { } } -echo json_encode($projects); \ No newline at end of file +echo json_encode($projects); diff --git a/get_profiles.php b/get_profiles.php new file mode 100644 index 0000000..3d91a32 --- /dev/null +++ b/get_profiles.php @@ -0,0 +1,18 @@ +prepare("SELECT id, name, is_default FROM profiles WHERE user_id = ? ORDER BY is_default DESC, name ASC"); +$stmt->execute([$user_id]); +$profiles = $stmt->fetchAll(PDO::FETCH_ASSOC); + +echo json_encode([ + 'success' => true, + 'active_profile_id' => $_SESSION['active_profile_id'] ?? null, + 'profiles' => $profiles +]); diff --git a/index.php b/index.php index 1b89a1a..e3851b9 100644 --- a/index.php +++ b/index.php @@ -144,8 +144,19 @@ justify-content: center; } + .profile-select { + height: 32px; /* match your header-action-btn buttons */ + font-size: 1.5rem; + font-weight: 700; /* or 700 for stronger bold */ + padding-right: 2.5rem; /* key: reserve space for the arrow */ + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + .add-project-btn { - font-size: 1.4rem; + font-size: 1.5rem; + font-weight: 600; /* or 700 for stronger bold */ padding: 0.5rem 1rem; } @@ -259,37 +270,36 @@ -
- - - - - - + + + +
+ @@ -538,131 +548,149 @@ favicon.type = "image/svg+xml"; }); }); -} + } + function loadProfiles() { + fetch('get_profiles.php') + .then(r => r.json()) + .then(data => { + if (!data.success) return; + const sel = document.getElementById('profileSelect'); + sel.innerHTML = ''; + data.profiles.forEach(p => { + const opt = document.createElement('option'); + opt.value = p.id; + opt.textContent = p.name; + sel.appendChild(opt); + }); + if (data.active_profile_id) { + sel.value = String(data.active_profile_id); + } + }); + } - function fetchProjects() { - fetch('get_data.php') - .then(res => res.json()) - .then(data => { + function fetchProjects() { + fetch('get_data.php') + .then(res => res.json()) + .then(data => { - if (!Array.isArray(data)) { - if (data && data.error === 'Not authenticated') { - new bootstrap.Modal(document.getElementById('loginModal')).show(); - return; - } - console.error('get_data.php returned non-array:', data); + if (!Array.isArray(data)) { + if (data && data.error === 'Not authenticated') { + new bootstrap.Modal(document.getElementById('loginModal')).show(); return; } + console.error('get_data.php returned non-array:', data); + return; + } - const container = document.getElementById('projectGrid'); - container.innerHTML = ''; + const container = document.getElementById('projectGrid'); + container.innerHTML = ''; - data.forEach((project, idx) => { - - const projectId = `project-${project.id}`; - const colorClass = `project-color-${idx % 10}`; - - const col = document.createElement('div'); + data.forEach((project, idx) => { + + const projectId = `project-${project.id}`; + const colorClass = `project-color-${idx % 10}`; + + const col = document.createElement('div'); - col.className = `col-12 col-sm-6 col-md-4 col-lg-2 col-xl-2 col-xxl-1 g-2 m-0 project-column`; - col.dataset.projectId = project.id; - col.classList.add("draggable-project"); - col.innerHTML = ` -
-
- ${project.name} -
- - -
-
-
- ${project.tasks.map(task => ` -
- ${task.name} -
- - -
-
-
- ${task.subtasks.map(sub => ` -
- ${sub.name} - -
- `).join('')} -
- `).join('')} -
-
- `; + col.className = `col-12 col-sm-6 col-md-4 col-lg-2 col-xl-2 col-xxl-1 g-2 m-0 project-column`; + col.dataset.projectId = project.id; + col.classList.add("draggable-project"); + col.innerHTML = ` +
+
+ ${project.name} +
+ + +
+
+
+ ${project.tasks.map(task => ` +
+ ${task.name} +
+ + +
+
+
+ ${task.subtasks.map(sub => ` +
+ ${sub.name} + +
+ `).join('')} +
+ `).join('')} +
+
+ `; - container.appendChild(col); - }); + container.appendChild(col); + }); -// Create the drop indicator element -const placeholder = document.createElement('div'); -placeholder.className = 'project-column'; -placeholder.innerHTML = `
`; + // Create the drop indicator element + const placeholder = document.createElement('div'); + placeholder.className = 'project-column'; + placeholder.innerHTML = `
`; -// Setup drag events for project reordering -document.querySelectorAll('.project-column').forEach(col => { - col.setAttribute('draggable', 'true'); + // Setup drag events for project reordering + document.querySelectorAll('.project-column').forEach(col => { + col.setAttribute('draggable', 'true'); - col.addEventListener('dragstart', (e) => { - e.dataTransfer.setData('text/plain', col.dataset.projectId); - col.classList.add('dragging'); - }); + col.addEventListener('dragstart', (e) => { + e.dataTransfer.setData('text/plain', col.dataset.projectId); + col.classList.add('dragging'); + }); - col.addEventListener('dragend', () => { - col.classList.remove('dragging'); - placeholder.remove(); - }); + col.addEventListener('dragend', () => { + col.classList.remove('dragging'); + placeholder.remove(); + }); - col.addEventListener('dragover', (e) => { - e.preventDefault(); - const dragging = document.querySelector('.dragging'); - if (!dragging || dragging === col) return; + col.addEventListener('dragover', (e) => { + e.preventDefault(); + const dragging = document.querySelector('.dragging'); + if (!dragging || dragging === col) return; - const bounding = col.getBoundingClientRect(); - const offset = e.clientY - bounding.top; + const bounding = col.getBoundingClientRect(); + const offset = e.clientY - bounding.top; - if (offset < bounding.height / 2) { - col.parentNode.insertBefore(placeholder, col); - } else { - col.parentNode.insertBefore(placeholder, col.nextSibling); - } - }); + if (offset < bounding.height / 2) { + col.parentNode.insertBefore(placeholder, col); + } else { + col.parentNode.insertBefore(placeholder, col.nextSibling); + } + }); - col.addEventListener('drop', (e) => { - e.preventDefault(); - const dragging = document.querySelector('.dragging'); - if (placeholder && placeholder.parentNode) { - placeholder.parentNode.insertBefore(dragging, placeholder); - placeholder.remove(); - updateProjectOrder(); // This already exists in your code - } - }); -}); + col.addEventListener('drop', (e) => { + e.preventDefault(); + const dragging = document.querySelector('.dragging'); + if (placeholder && placeholder.parentNode) { + placeholder.parentNode.insertBefore(dragging, placeholder); + placeholder.remove(); + updateProjectOrder(); // This already exists in your code + } + }); + }); // Enable drag-and-drop sorting @@ -956,7 +984,23 @@ document.querySelectorAll('.project-column').forEach(col => { err.style.display = 'block'; }); }); - + + document.getElementById('profileSelect').addEventListener('change', () => { + const profileId = parseInt(document.getElementById('profileSelect').value, 10); + + fetch('set_profile.php', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ profile_id: profileId }) + }) + .then(r => r.json()) + .then(data => { + if (!data.success) return; + fetchProjects(); + }); + }); + + document.addEventListener('DOMContentLoaded', () => { loadSettings(); @@ -979,6 +1023,7 @@ document.querySelectorAll('.project-column').forEach(col => { document.querySelectorAll('.settings-btn').forEach(b => b.style.display = 'none'); } + loadProfiles(); fetchProjects(); }); }); diff --git a/login.php b/login.php index 1f24908..dc9b156 100644 --- a/login.php +++ b/login.php @@ -33,6 +33,27 @@ $_SESSION['user'] = [ 'can_manage_settings' => intval($user['can_manage_settings']), ]; +// Set active profile for this session (default profile if available) +$stmt = $pdo->prepare("SELECT id FROM profiles WHERE user_id = ? AND is_default = 1 LIMIT 1"); +$stmt->execute([$_SESSION['user']['id']]); +$profileId = $stmt->fetchColumn(); + +if (!$profileId) { + // Fallback to first profile + $stmt = $pdo->prepare("SELECT id FROM profiles WHERE user_id = ? ORDER BY id ASC LIMIT 1"); + $stmt->execute([$_SESSION['user']['id']]); + $profileId = $stmt->fetchColumn(); +} + +if (!$profileId) { + // Last-resort: create a default profile if none exist (useful for new users) + $stmt = $pdo->prepare("INSERT INTO profiles (user_id, name, is_default) VALUES (?, 'Default', 1)"); + $stmt->execute([$_SESSION['user']['id']]); + $profileId = $pdo->lastInsertId(); +} + +$_SESSION['active_profile_id'] = (int)$profileId; + echo json_encode([ 'success' => true, 'user' => [ @@ -40,5 +61,6 @@ echo json_encode([ 'email' => $_SESSION['user']['email'], 'role' => $_SESSION['user']['role'], 'can_manage_settings' => $_SESSION['user']['can_manage_settings'], - ] + ], + 'active_profile_id' => $_SESSION['active_profile_id'] ]); diff --git a/register.php b/register.php index 49d6020..56f1153 100644 --- a/register.php +++ b/register.php @@ -33,10 +33,24 @@ if ($role_id <= 0) { } try { + $pdo->beginTransaction(); + + // Create user $stmt = $pdo->prepare("INSERT INTO users (email, password_hash, role_id) VALUES (?, ?, ?)"); $stmt->execute([$email, $hash, $role_id]); + $userId = (int)$pdo->lastInsertId(); + + // Create default profile for this user + $stmt = $pdo->prepare("INSERT INTO profiles (user_id, name, is_default) VALUES (?, 'Default', 1)"); + $stmt->execute([$userId]); + + $pdo->commit(); + echo json_encode(['success' => true]); + } catch (Throwable $e) { + if ($pdo->inTransaction()) $pdo->rollBack(); + http_response_code(409); echo json_encode(['success' => false, 'error' => 'Account already exists']); } diff --git a/set_profile.php b/set_profile.php new file mode 100644 index 0000000..552ad31 --- /dev/null +++ b/set_profile.php @@ -0,0 +1,30 @@ + false, 'error' => 'Invalid profile_id']); + exit; +} + +$stmt = $pdo->prepare("SELECT id FROM profiles WHERE id = ? AND user_id = ?"); +$stmt->execute([$profile_id, $user_id]); + +if (!$stmt->fetchColumn()) { + http_response_code(403); + echo json_encode(['success' => false, 'error' => 'Profile not allowed']); + exit; +} + +$_SESSION['active_profile_id'] = $profile_id; + +echo json_encode(['success' => true, 'active_profile_id' => $profile_id]);