858 lines
27 KiB
PHP
858 lines
27 KiB
PHP
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>tinyTask</title>
|
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.5/font/bootstrap-icons.css">
|
|
<link rel="icon" href="" type="image/svg+xml" sizes="any">
|
|
|
|
<style>
|
|
html, body {
|
|
height: 100%;
|
|
margin: 0;
|
|
font-size: 8px;
|
|
}
|
|
|
|
.project-card {
|
|
background-color: #f9f9fc;
|
|
padding: 0px;
|
|
margin-bottom: 1rem;
|
|
width: 100%;
|
|
max-width: 100%;
|
|
width: 100%;
|
|
min-width: 200px;
|
|
font-size: 8px;
|
|
}
|
|
|
|
.project-header {
|
|
background-color: #e0e0f0;
|
|
padding: 0.2rem 0.5rem;
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.project-title {
|
|
flex-grow: 1;
|
|
font-weight: 800;
|
|
font-size: 1.4rem;
|
|
}
|
|
|
|
.project-body {
|
|
padding: 0.2rem 0.5rem;
|
|
font-size: 1.4rem;
|
|
}
|
|
|
|
.modal {
|
|
padding: 2rem; /* or try 3rem or a % like 5% */
|
|
}
|
|
|
|
.modal.show {
|
|
display: flex !important;
|
|
align-items: center;
|
|
justify-content: center;
|
|
}
|
|
|
|
.modal-dialog {
|
|
margin: 0;
|
|
}
|
|
|
|
.modal-content {
|
|
font-size: 2.0rem; /* slightly larger than Bootstrap's default (~0.875rem) */
|
|
}
|
|
|
|
.modal-title {
|
|
font-size: 2.0rem;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.modal-body .form-control {
|
|
font-size: 2.0rem;
|
|
}
|
|
|
|
.modal-footer .btn {
|
|
font-size: 0.95rem;
|
|
padding: 0.5rem 1rem;
|
|
}
|
|
|
|
.add-project-btn {
|
|
font-size: 1.5rem; /* increase to your desired size */
|
|
line-height: 1;
|
|
}
|
|
|
|
.settings-btn {
|
|
font-size: 1.5rem; /* increase to your desired size */
|
|
line-height: 1;
|
|
color: #bbb;
|
|
}
|
|
|
|
.modal .btn {
|
|
font-size: 1.5rem; /* or 1.1rem for slightly larger */
|
|
}
|
|
|
|
.task-text {
|
|
font-weight: 700;
|
|
}
|
|
|
|
.highlighted {
|
|
border: 2px solid red;
|
|
border-radius: 6px;
|
|
padding: 2px 6px;
|
|
background-color: #transparent;
|
|
}
|
|
|
|
.completed {
|
|
text-decoration: line-through;
|
|
opacity: 0.5;
|
|
}
|
|
|
|
.btn-icon {
|
|
width: 20px;
|
|
height: 20px;
|
|
padding: 0;
|
|
font-size: 14px;
|
|
line-height: 1;
|
|
border-radius: 6px;
|
|
text-align: center;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
background-color: transparent;
|
|
border: none;
|
|
color: #555;
|
|
}
|
|
|
|
.btn-icon:hover,
|
|
.btn-icon:focus {
|
|
background-color: rgba(0, 0, 0, 0.05);
|
|
color: #000;
|
|
}
|
|
|
|
.btn-icon.text-danger:hover {
|
|
background-color: rgba(255, 0, 0, 0.1);
|
|
color: #d00;
|
|
}
|
|
|
|
.project-color-0 { background-color: #f9f9fc; }
|
|
.project-color-1 { background-color: #eefaf5; }
|
|
.project-color-2 { background-color: #fef7e0; }
|
|
.project-color-3 { background-color: #f0f4ff; }
|
|
.project-color-4 { background-color: #fff0f3; }
|
|
.project-color-5 { background-color: #f4f9f9; }
|
|
.project-color-6 { background-color: #f5f5ff; }
|
|
.project-color-7 { background-color: #f0fff4; }
|
|
.project-color-8 { background-color: #fff8e1; }
|
|
.project-color-9 { background-color: #eaf2ff; }
|
|
|
|
.project-color-0 .project-header { background-color: #e0e0f0; }
|
|
.project-color-1 .project-header { background-color: #d6f0e5; }
|
|
.project-color-2 .project-header { background-color: #fbeec2; }
|
|
.project-color-3 .project-header { background-color: #dce6ff; }
|
|
.project-color-4 .project-header { background-color: #ffe0e6; }
|
|
.project-color-5 .project-header { background-color: #dceeee; }
|
|
.project-color-6 .project-header { background-color: #e2e2ff; }
|
|
.project-color-7 .project-header { background-color: #d3f8e2; }
|
|
.project-color-8 .project-header { background-color: #fff3c2; }
|
|
.project-color-9 .project-header { background-color: #d0e4ff; }
|
|
|
|
|
|
.project-column {
|
|
width: 100%; /* 1 per row */
|
|
}
|
|
|
|
|
|
/* Small devices (landscape phones, ≥576px) */
|
|
@media (min-width: 576px) and (max-width: 767.98px) {
|
|
.project-column {
|
|
width: 50%; /* 2 per row */
|
|
}
|
|
}
|
|
|
|
/* Medium devices (tablets, ≥768px) */
|
|
@media (min-width: 768px) and (max-width: 991.98px) {
|
|
.project-column {
|
|
width: 33.3333%; /* 3 per row */
|
|
}
|
|
}
|
|
|
|
/* Large devices (desktops, ≥992px) */
|
|
@media (min-width: 992px) and (max-width: 1199.98px) {
|
|
.project-column {
|
|
width: 25%; /* 4 per row */
|
|
}
|
|
}
|
|
|
|
/* Extra Large (≥1200px) */
|
|
@media (min-width: 1200px) and (max-width: 1279.98px) {
|
|
.project-column {
|
|
width: 20%; /* 5 per row */
|
|
}
|
|
}
|
|
|
|
/* Super wide screens */
|
|
@media (min-width: 1280px) and (max-width: 1599.98px) {
|
|
.project-column {
|
|
width: 16.6666%; /* 6 per row */
|
|
}
|
|
}
|
|
|
|
@media (min-width: 1600px) and (max-width: 1919.98px) {
|
|
.project-column {
|
|
width: 13.0%; /* 8 per row */
|
|
}
|
|
}
|
|
|
|
@media (min-width: 1920px) and (max-width: 2559.98px) {
|
|
.project-column {
|
|
width: 11%; /* 10 per row */
|
|
}
|
|
}
|
|
|
|
@media (min-width: 2560px) {
|
|
.project-column {
|
|
width: 8.3333%; /* 12 per row */
|
|
}
|
|
}
|
|
</style>
|
|
|
|
</head>
|
|
<body>
|
|
<h1 class="mb-3 d-flex justify-content-between align-items-center px-3">
|
|
<span id="appTitle" class="d-flex align-items-center">
|
|
<i id="appIcon" class="bi me-2"></i>
|
|
<span class="fw-semibold me-2"></span>
|
|
<button class="btn btn-sm btn-clear p-0 settings-btn" data-bs-toggle="modal" data-bs-target="#settingsModal" title="Settings">
|
|
<i class="bi bi-gear-fill"></i>
|
|
</button>
|
|
</span>
|
|
<button class="btn btn-sm btn-success add-project-btn" data-bs-toggle="modal" data-bs-target="#addProjectModal" title="Add Project">
|
|
<i class="bi bi-plus"></i>
|
|
</button>
|
|
</h1>
|
|
|
|
|
|
<div class="container-fluid px-3">
|
|
<div class="row g-2" id="projectGrid" data-masonry='{"percentPosition": true }'>
|
|
<!-- JavaScript will populate projects here -->
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Modal: Add New Project -->
|
|
<div class="modal fade" id="addProjectModal" tabindex="-1" aria-labelledby="addProjectModalLabel" aria-hidden="true">
|
|
<div class="modal-dialog modal-dialog-centered">
|
|
<form id="addProjectForm">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title">Add New Project</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<input type="text" class="form-control" id="projectName" placeholder="Project name" required>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="submit" class="btn btn-primary">Save</button>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Modal: Add Task -->
|
|
<div class="modal fade" id="addTaskModal" tabindex="-1" aria-labelledby="addTaskModalLabel" aria-hidden="true">
|
|
<div class="modal-dialog modal-dialog-centered">
|
|
<form id="addTaskForm">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title" id="addTaskModalLabel">Add Task</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<input type="text" class="form-control" id="taskName" placeholder="Task name" required>
|
|
<input type="hidden" id="taskProjectId">
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="submit" class="btn btn-primary">Save Task</button>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Modal: Add Subtask -->
|
|
<div class="modal fade" id="addSubtaskModal" tabindex="-1" aria-labelledby="addSubtaskModalLabel" aria-hidden="true">
|
|
<div class="modal-dialog modal-dialog-centered">
|
|
<form id="addSubtaskForm">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title" id="addSubtaskModalLabel">Add Subtask</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<input type="text" class="form-control" id="subtaskName" placeholder="Subtask name" required>
|
|
<input type="hidden" id="subtaskTaskId">
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="submit" class="btn btn-primary">Save Subtask</button>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Modal: Settings -->
|
|
<div class="modal fade" id="settingsModal" tabindex="-1" aria-labelledby="settingsModalLabel" aria-hidden="true">
|
|
<div class="modal-dialog modal-dialog-centered">
|
|
<form id="settingsForm">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title" id="settingsModalLabel">Settings</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
|
|
<div class="mb-3">
|
|
<label class="form-label">App Title</label>
|
|
<input type="text" class="form-control" id="settingTitle">
|
|
</div>
|
|
|
|
<div class="mb-3">
|
|
<label class="form-label">Icon Class (e.g. kanban)</label>
|
|
<input type="text" class="form-control" id="settingIcon">
|
|
<div class="form-text">Use any <a href="https://icons.getbootstrap.com/" target="_blank">Bootstrap Icon</a> class.</div>
|
|
</div>
|
|
|
|
<div class="mb-3">
|
|
<label class="form-label">Icon Color</label>
|
|
<input type="color" class="form-control form-control-color" id="settingColor">
|
|
</div>
|
|
|
|
</div>
|
|
<div class="modal-footer">
|
|
|
|
<div class="d-flex justify-content-between gap-2 w-100">
|
|
<button type="button" class="btn btn-secondary" id="resetSettings">Reset to Default</button>
|
|
<button type="submit" class="btn btn-primary">Save Settings</button>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Modal: Confirmation -->
|
|
<div class="modal fade" id="confirmationModal" tabindex="-1" aria-hidden="true">
|
|
<div class="modal-dialog modal-dialog-centered">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title">Confirm Action</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
</div>
|
|
<div class="modal-body" id="confirmationModalBody">
|
|
Are you sure you want to continue?
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
|
<button type="button" class="btn btn-danger" id="confirmModalYes">Yes</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Modal: Login -->
|
|
<div class="modal fade" id="loginModal" tabindex="-1" aria-hidden="true" data-bs-backdrop="static" data-bs-keyboard="false">
|
|
<div class="modal-dialog modal-dialog-centered">
|
|
<form id="loginForm" class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title">Sign In</h5>
|
|
</div>
|
|
<div class="modal-body">
|
|
<div class="mb-3">
|
|
<label class="form-label">Email</label>
|
|
<input type="email" class="form-control" id="loginEmail" required>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label class="form-label">Password</label>
|
|
<input type="password" class="form-control" id="loginPassword" required>
|
|
</div>
|
|
<div class="text-danger" id="loginError" style="display:none;"></div>
|
|
</div>
|
|
<div class="modal-footer d-flex justify-content-between w-100">
|
|
<button type="button" class="btn btn-outline-secondary" id="showRegisterBtn">Create account</button>
|
|
<button type="submit" class="btn btn-primary">Sign In</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Modal: Register -->
|
|
<div class="modal fade" id="registerModal" tabindex="-1" aria-hidden="true">
|
|
<div class="modal-dialog modal-dialog-centered">
|
|
<form id="registerForm" class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title">Create Account</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<div class="mb-3">
|
|
<label class="form-label">Email</label>
|
|
<input type="email" class="form-control" id="registerEmail" required>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label class="form-label">Password (8+ chars)</label>
|
|
<input type="password" class="form-control" id="registerPassword" required minlength="8">
|
|
</div>
|
|
<div class="text-danger" id="registerError" style="display:none;"></div>
|
|
<div class="text-success" id="registerSuccess" style="display:none;"></div>
|
|
</div>
|
|
<div class="modal-footer d-flex justify-content-between w-100">
|
|
<button type="button" class="btn btn-outline-secondary" id="backToLoginBtn">Back</button>
|
|
<button type="submit" class="btn btn-primary">Create</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
|
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
|
<script src="https://unpkg.com/masonry-layout@4/dist/masonry.pkgd.min.js"></script>
|
|
<script>
|
|
|
|
function showConfirmation(message, onConfirm) {
|
|
const modal = new bootstrap.Modal(document.getElementById('confirmationModal'));
|
|
document.getElementById('confirmationModalBody').textContent = message;
|
|
|
|
const confirmBtn = document.getElementById('confirmModalYes');
|
|
const newConfirmBtn = confirmBtn.cloneNode(true);
|
|
confirmBtn.parentNode.replaceChild(newConfirmBtn, confirmBtn); // remove old handler
|
|
|
|
newConfirmBtn.addEventListener('click', () => {
|
|
modal.hide();
|
|
onConfirm();
|
|
});
|
|
|
|
modal.show();
|
|
}
|
|
|
|
function loadSettings() {
|
|
fetch('get_settings.php')
|
|
.then(res => res.json())
|
|
.then(data => {
|
|
// Update form fields
|
|
document.getElementById('settingTitle').value = data.title;
|
|
document.getElementById('settingIcon').value = data.icon_class;
|
|
document.getElementById('settingColor').value = data.icon_color;
|
|
|
|
// Update title area
|
|
document.getElementById('appTitle').innerHTML = `
|
|
<i id="appIcon" class="bi bi-${data.icon_class} me-2" style="color:${data.icon_color};"></i>
|
|
<span class="fw-semibold me-2">${data.title}</span>
|
|
<button class="btn btn-sm btn-clear p-0 settings-btn" data-bs-toggle="modal" data-bs-target="#settingsModal" title="Settings">
|
|
<i class="bi bi-gear-fill"></i>
|
|
</button>
|
|
`;
|
|
|
|
// Set favicon
|
|
const iconClass = data.icon_class; // e.g. "bi-kanban"
|
|
const iconName = iconClass.replace("bi-", ""); // e.g. "kanban"
|
|
const iconURL = `https://cdn.jsdelivr.net/npm/bootstrap-icons/icons/${iconName}.svg`;
|
|
|
|
fetch(iconURL)
|
|
.then(res => res.text())
|
|
.then(svg => {
|
|
const coloredSvg = svg
|
|
.replace(/fill="currentColor"/g, '') // remove existing fill
|
|
.replace(/<svg([^>]+)>/, `<svg$1 fill="${data.icon_color}">`);
|
|
|
|
const encoded = "data:image/svg+xml," + encodeURIComponent(coloredSvg);
|
|
|
|
let favicon = document.querySelector("link[rel~='icon']");
|
|
if (!favicon) {
|
|
favicon = document.createElement("link");
|
|
favicon.rel = "icon";
|
|
document.head.appendChild(favicon);
|
|
}
|
|
favicon.href = encoded;
|
|
favicon.type = "image/svg+xml";
|
|
});
|
|
});
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function fetchProjects() {
|
|
fetch('get_data.php')
|
|
.then(res => res.json())
|
|
.then(data => {
|
|
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');
|
|
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 = `
|
|
<div class="project-card ${colorClass}">
|
|
<div class="project-header">
|
|
<span class="project-title">${project.name}</span>
|
|
<div class="d-flex align-items-center">
|
|
<button class="btn-icon add-task-btn" data-project-id="${project.id}" title="Add Task">
|
|
<i class="bi bi-plus"></i>
|
|
</button>
|
|
<button class="btn-icon text-danger delete-project-btn" data-project-id="${project.id}" title="Delete Project">
|
|
<i class="bi bi-dash"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="project-body">
|
|
${project.tasks.map(task => `
|
|
<div class="d-flex justify-content-between align-items-center small-text mb-1 task-text">
|
|
<span class="${task.highlighted == 1 ? 'highlighted' : ''} ${task.highlighted == 2 ? 'completed' : ''}".trim()
|
|
data-task-id="${task.id}">${task.name}</span>
|
|
<div class="d-flex">
|
|
<button class="btn-icon add-subtask-btn" data-task-id="${task.id}" title="Add Subtask">
|
|
<i class="bi bi-plus"></i>
|
|
</button>
|
|
<button class="btn-icon text-danger delete-task-btn" data-task-id="${task.id}" title="Delete Task">
|
|
<i class="bi bi-dash"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="ms-3">
|
|
${task.subtasks.map(sub => `
|
|
<div class="d-flex justify-content-between align-items-center small-text mb-1">
|
|
<span class="${sub.highlighted == 1 ? 'highlighted' : ''} ${sub.highlighted == 2 ? 'completed' : ''}".trim()
|
|
data-subtask-id="${sub.id}">${sub.name}</span>
|
|
<button class="btn-icon text-danger delete-subtask-btn" data-subtask-id="${sub.id}" title="Delete Subtask">
|
|
<i class="bi bi-dash"></i>
|
|
</button>
|
|
</div>
|
|
`).join('')}
|
|
</div>
|
|
`).join('')}
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
container.appendChild(col);
|
|
});
|
|
|
|
|
|
// Create the drop indicator element
|
|
const placeholder = document.createElement('div');
|
|
placeholder.className = 'project-column';
|
|
placeholder.innerHTML = `<div class="project-placeholder"></div>`;
|
|
|
|
|
|
// 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('dragend', () => {
|
|
col.classList.remove('dragging');
|
|
placeholder.remove();
|
|
});
|
|
|
|
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;
|
|
|
|
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
|
|
}
|
|
});
|
|
});
|
|
|
|
|
|
// Enable drag-and-drop sorting
|
|
new Sortable(container, {
|
|
animation: 150,
|
|
handle: '.project-header',
|
|
onEnd: function () {
|
|
const order = [...container.querySelectorAll('.project-column')].map((el, idx) => ({
|
|
id: el.dataset.projectId,
|
|
order: idx
|
|
}));
|
|
|
|
fetch('update_project_order.php', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(order)
|
|
}).then(() => {
|
|
fetchProjects();
|
|
});
|
|
}
|
|
});
|
|
|
|
const msnry = new Masonry(container, {
|
|
itemSelector: '.project-column',
|
|
columnWidth: '.project-column',
|
|
gutter: 0,
|
|
percentPosition: true
|
|
});
|
|
|
|
|
|
|
|
|
|
document.querySelectorAll('.add-task-btn').forEach(btn => {
|
|
btn.addEventListener('click', () => {
|
|
const projectId = btn.dataset.projectId;
|
|
document.getElementById('taskProjectId').value = projectId;
|
|
document.getElementById('taskName').value = '';
|
|
const modal = new bootstrap.Modal(document.getElementById('addTaskModal'));
|
|
modal.show();
|
|
});
|
|
});
|
|
|
|
document.querySelectorAll('.add-subtask-btn').forEach(btn => {
|
|
btn.addEventListener('click', () => {
|
|
const taskId = btn.dataset.taskId;
|
|
document.getElementById('subtaskTaskId').value = taskId;
|
|
document.getElementById('subtaskName').value = '';
|
|
const modal = new bootstrap.Modal(document.getElementById('addSubtaskModal'));
|
|
modal.show();
|
|
});
|
|
});
|
|
|
|
// Delete Project
|
|
document.querySelectorAll('.delete-project-btn').forEach(btn => {
|
|
btn.addEventListener('click', () => {
|
|
const id = btn.dataset.projectId;
|
|
showConfirmation('Delete this project and all its tasks?', () => {
|
|
fetch(`delete_project.php?id=${id}`).then(() => fetchProjects());
|
|
});
|
|
});
|
|
});
|
|
|
|
|
|
// Delete Task
|
|
document.querySelectorAll('.delete-task-btn').forEach(btn => {
|
|
btn.addEventListener('click', () => {
|
|
const id = btn.dataset.taskId;
|
|
showConfirmation('Delete this task and all its subtasks?', () => {
|
|
fetch(`delete_task.php?id=${id}`).then(() => fetchProjects());
|
|
});
|
|
});
|
|
});
|
|
|
|
// Delete Subtask
|
|
document.querySelectorAll('.delete-subtask-btn').forEach(btn => {
|
|
btn.addEventListener('click', () => {
|
|
const id = btn.dataset.subtaskId;
|
|
showConfirmation('Delete this task?', () => {
|
|
fetch(`delete_subtask.php?id=${id}`).then(() => fetchProjects());
|
|
});
|
|
});
|
|
});
|
|
|
|
document.querySelectorAll('[data-task-id]').forEach(span => {
|
|
span.addEventListener('click', () => {
|
|
const taskId = span.dataset.taskId;
|
|
|
|
fetch('toggle_highlight.php', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ type: 'task', id: taskId })
|
|
})
|
|
.then(res => res.json())
|
|
.then(data => {
|
|
if (!data.success) return;
|
|
|
|
span.classList.toggle('highlighted', data.highlighted === 1);
|
|
span.classList.toggle('completed', data.highlighted === 2);
|
|
});
|
|
});
|
|
});
|
|
|
|
document.querySelectorAll('[data-subtask-id]').forEach(span => {
|
|
span.addEventListener('click', () => {
|
|
const subtaskId = span.dataset.subtaskId;
|
|
|
|
fetch('toggle_highlight.php', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ type: 'subtask', id: subtaskId })
|
|
})
|
|
.then(res => res.json())
|
|
.then(data => {
|
|
if (!data.success) return;
|
|
|
|
span.classList.toggle('highlighted', data.highlighted === 1);
|
|
span.classList.toggle('completed', data.highlighted === 2);
|
|
});
|
|
});
|
|
});
|
|
|
|
let itemToDelete = null;
|
|
|
|
document.querySelectorAll('.delete-task-btn').forEach(btn => {
|
|
btn.addEventListener('click', () => {
|
|
itemToDelete = btn.dataset.taskId;
|
|
const modal = new bootstrap.Modal(document.getElementById('confirmDeleteModal'));
|
|
modal.show();
|
|
});
|
|
});
|
|
|
|
document.getElementById('confirmDeleteBtn').addEventListener('click', () => {
|
|
if (itemToDelete) {
|
|
fetch(`delete_task.php?id=${itemToDelete}`)
|
|
.then(() => location.reload()); // or re-fetch your UI
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
|
|
|
|
|
|
document.getElementById('addProjectForm').addEventListener('submit', function(e) {
|
|
e.preventDefault();
|
|
const name = document.getElementById('projectName').value.trim();
|
|
if (!name) return;
|
|
|
|
fetch('add_project.php', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ name })
|
|
})
|
|
.then(res => res.json())
|
|
.then(response => {
|
|
if (response.success) {
|
|
document.getElementById('projectName').value = '';
|
|
bootstrap.Modal.getInstance(document.getElementById('addProjectModal')).hide();
|
|
fetchProjects(); // or your equivalent project reloader
|
|
}
|
|
});
|
|
});
|
|
|
|
document.getElementById('addTaskForm').addEventListener('submit', function(e) {
|
|
e.preventDefault();
|
|
const name = document.getElementById('taskName').value.trim();
|
|
const projectId = document.getElementById('taskProjectId').value;
|
|
|
|
if (!name || !projectId) return;
|
|
|
|
fetch('add_task.php', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ name, project_id: projectId })
|
|
})
|
|
.then(res => res.json())
|
|
.then(response => {
|
|
if (response.success) {
|
|
bootstrap.Modal.getInstance(document.getElementById('addTaskModal')).hide();
|
|
fetchProjects();
|
|
}
|
|
});
|
|
});
|
|
|
|
document.getElementById('addSubtaskForm').addEventListener('submit', function(e) {
|
|
e.preventDefault();
|
|
const name = document.getElementById('subtaskName').value.trim();
|
|
const taskId = document.getElementById('subtaskTaskId').value;
|
|
|
|
if (!name || !taskId) return;
|
|
|
|
fetch('add_subtask.php', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ name, task_id: taskId })
|
|
})
|
|
.then(res => res.json())
|
|
.then(response => {
|
|
if (response.success) {
|
|
bootstrap.Modal.getInstance(document.getElementById('addSubtaskModal')).hide();
|
|
location.reload(); // or call your re-rendering logic
|
|
}
|
|
});
|
|
});
|
|
|
|
document.getElementById('settingsForm').addEventListener('submit', function (e) {
|
|
e.preventDefault();
|
|
|
|
const newTitle = document.getElementById('titleText').value.trim();
|
|
const newIcon = document.getElementById('iconClass').value.trim();
|
|
|
|
if (newTitle) {
|
|
document.getElementById('appTitle').childNodes[1].textContent = newTitle;
|
|
}
|
|
|
|
if (newIcon) {
|
|
const icon = document.getElementById('appIcon');
|
|
icon.className = `bi ${newIcon} me-2`;
|
|
}
|
|
|
|
bootstrap.Modal.getInstance(document.getElementById('settingsModal')).hide();
|
|
});
|
|
|
|
document.getElementById('settingsForm').addEventListener('submit', function (e) {
|
|
e.preventDefault();
|
|
|
|
const title = document.getElementById('settingTitle').value;
|
|
const icon_class = document.getElementById('settingIcon').value;
|
|
const icon_color = document.getElementById('settingColor').value;
|
|
|
|
fetch('update_settings.php', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ title, icon_class, icon_color })
|
|
})
|
|
.then(res => res.json())
|
|
.then(() => {
|
|
const modal = bootstrap.Modal.getInstance(document.getElementById('settingsModal'));
|
|
modal.hide();
|
|
loadSettings();
|
|
});
|
|
});
|
|
|
|
document.getElementById('resetSettings').addEventListener('click', () => {
|
|
fetch('reset_settings.php')
|
|
.then(() => {
|
|
loadSettings();
|
|
});
|
|
});
|
|
|
|
window.addEventListener('DOMContentLoaded', () => {
|
|
loadSettings();
|
|
});
|
|
|
|
document.addEventListener('DOMContentLoaded', fetchProjects);
|
|
|
|
</script>
|
|
|
|
<script src="https://cdn.jsdelivr.net/npm/sortablejs@1.15.0/Sortable.min.js"></script>
|
|
|
|
</body>
|
|
</html>
|