test
This commit is contained in:
139
views/tasks.php
139
views/tasks.php
@@ -54,6 +54,31 @@ if($_SERVER['REQUEST_METHOD'] === 'POST' && (isset($_POST['delete_attachment'])
|
||||
}
|
||||
}
|
||||
|
||||
// Handle AJAX file upload for existing tasks
|
||||
if($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['upload_attachment_ajax'])) {
|
||||
$task_id = (int)($_POST['task_id'] ?? 0);
|
||||
$task = query("SELECT id FROM tasks WHERE id=?", [$task_id])->fetch();
|
||||
if($task && isset($_FILES['attachment_file'])) {
|
||||
$files = $_FILES['attachment_file'];
|
||||
$names = is_array($files['name']) ? $files['name'] : [$files['name']];
|
||||
$tmps = is_array($files['tmp_name']) ? $files['tmp_name'] : [$files['tmp_name']];
|
||||
$errors = is_array($files['error']) ? $files['error'] : [$files['error']];
|
||||
for($i = 0; $i < count($names); $i++) {
|
||||
if($names[$i] && $errors[$i] === 0) {
|
||||
$ext = strtolower(pathinfo($names[$i], PATHINFO_EXTENSION));
|
||||
$path = 'uploads/t_' . time() . '_' . rand(100, 999) . '.' . $ext;
|
||||
if(!is_dir('uploads')) mkdir('uploads');
|
||||
if(move_uploaded_file($tmps[$i], $path)) {
|
||||
$s = $pdo->prepare("INSERT INTO task_attachments (task_id, file_name, file_path, owner_id) VALUES (?, ?, ?, ?)");
|
||||
$s->execute([$task_id, $names[$i], $path, $user_id]);
|
||||
}
|
||||
}
|
||||
}
|
||||
echo json_encode(['success' => true]); exit;
|
||||
}
|
||||
echo json_encode(['success' => false, 'error' => 'Invalid task']); exit;
|
||||
}
|
||||
|
||||
// Handle form submission
|
||||
if($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['create_task'])) {
|
||||
$title = $_POST['title'];
|
||||
@@ -279,8 +304,70 @@ body.dark-mode .kanban-task { border: 1px solid rgba(255,255,255,0.05); border-l
|
||||
transition:background 0.2s;
|
||||
}
|
||||
.att-btn:hover { background:rgba(255,255,255,0.5); color:white; }
|
||||
|
||||
/* Attachment Lightbox */
|
||||
#att-lightbox {
|
||||
display:none; position:fixed; inset:0; background:rgba(0,0,0,0.75);
|
||||
z-index:10000; align-items:center; justify-content:center;
|
||||
animation: lbFadeIn 0.18s ease;
|
||||
}
|
||||
#att-lightbox.open { display:flex; }
|
||||
@keyframes lbFadeIn { from{opacity:0} to{opacity:1} }
|
||||
#att-lightbox-inner {
|
||||
position:relative; background:var(--surface); border-radius:1rem;
|
||||
max-width:90vw; max-height:90vh; overflow:hidden;
|
||||
display:flex; flex-direction:column;
|
||||
box-shadow:0 25px 60px rgba(0,0,0,0.4);
|
||||
animation: lbScaleIn 0.18s ease;
|
||||
}
|
||||
@keyframes lbScaleIn { from{transform:scale(0.92)} to{transform:scale(1)} }
|
||||
#att-lightbox-header {
|
||||
display:flex; align-items:center; justify-content:space-between;
|
||||
padding:0.75rem 1rem; border-bottom:1px solid var(--border);
|
||||
gap:1rem; min-width:0;
|
||||
}
|
||||
#att-lightbox-title {
|
||||
font-size:0.875rem; font-weight:600; color:var(--text);
|
||||
white-space:nowrap; overflow:hidden; text-overflow:ellipsis;
|
||||
}
|
||||
#att-lightbox-close {
|
||||
flex-shrink:0; width:28px; height:28px; border-radius:50%;
|
||||
background:var(--bg); border:1px solid var(--border);
|
||||
color:var(--text-light); cursor:pointer; font-size:1rem; line-height:1;
|
||||
display:flex; align-items:center; justify-content:center;
|
||||
transition:background 0.15s, color 0.15s;
|
||||
}
|
||||
#att-lightbox-close:hover { background:#EF4444; color:white; border-color:#EF4444; }
|
||||
#att-lightbox-body {
|
||||
overflow:auto; flex:1;
|
||||
display:flex; align-items:center; justify-content:center;
|
||||
padding:1rem;
|
||||
}
|
||||
#att-lightbox-body img {
|
||||
max-width:100%; max-height:calc(90vh - 80px);
|
||||
border-radius:0.5rem; object-fit:contain; display:block;
|
||||
}
|
||||
#att-lightbox-body pre {
|
||||
margin:0; font-size:0.8rem; font-family:monospace; white-space:pre-wrap;
|
||||
word-break:break-all; color:var(--text); max-height:calc(90vh - 80px);
|
||||
max-width:80vw; overflow:auto;
|
||||
}
|
||||
#att-lightbox-body iframe {
|
||||
border:none; width:80vw; height:75vh;
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- Attachment Lightbox Modal -->
|
||||
<div id="att-lightbox" onclick="lbClickOutside(event)">
|
||||
<div id="att-lightbox-inner">
|
||||
<div id="att-lightbox-header">
|
||||
<span id="att-lightbox-title">Preview</span>
|
||||
<button id="att-lightbox-close" onclick="closeAttachmentPreview()" title="Close">✕</button>
|
||||
</div>
|
||||
<div id="att-lightbox-body" id="att-lightbox-body"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card" style="margin-bottom: 1.5rem; padding: 0.75rem 1rem; box-shadow:0 1px 3px rgba(0,0,0,0.1); border-radius:12px;">
|
||||
<form id="create-task-form" method="POST" enctype="multipart/form-data" style="display: flex; flex-direction: column;">
|
||||
<div style="display:flex; gap:0.5rem; align-items:center; flex-wrap:wrap;">
|
||||
@@ -382,7 +469,7 @@ body.dark-mode .kanban-task { border: 1px solid rgba(255,255,255,0.05); border-l
|
||||
}
|
||||
|
||||
if($t['due_date']) {
|
||||
$days_diff = (strtotime($t['due_date']) - strtotime(date('Y-m-d'))) / 86400;
|
||||
$days_diff = (int)round((strtotime($t['due_date']) - strtotime(date('Y-m-d'))) / 86400);
|
||||
if($days_diff < 0) {
|
||||
echo '<span class="badge" style="background:#EF4444; color:white; padding:2px 8px; border-radius:12px; font-weight:600;">Overdue by '.abs($days_diff).'d</span>';
|
||||
} elseif($days_diff == 0) {
|
||||
@@ -464,9 +551,9 @@ body.dark-mode .kanban-task { border: 1px solid rgba(255,255,255,0.05); border-l
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="att-overlay">
|
||||
<a href="<?= htmlspecialchars((string)$att['file_path']) ?>" target="_blank" class="att-btn" title="Preview">
|
||||
<button type="button" class="att-btn" title="Preview" onclick="openAttachmentPreview('<?= htmlspecialchars(addslashes((string)$att['file_path'])) ?>', '<?= htmlspecialchars(addslashes((string)$att['file_name'])) ?>', <?= $is_img ? 'true' : 'false' ?>, <?= $is_txt ? 'true' : 'false' ?>)">
|
||||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"></path><circle cx="12" cy="12" r="3"></circle></svg>
|
||||
</a>
|
||||
</button>
|
||||
<a href="<?= htmlspecialchars((string)$att['file_path']) ?>" download class="att-btn" title="Download">
|
||||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path><polyline points="7 10 12 15 17 10"></polyline><line x1="12" y1="15" x2="12" y2="3"></line></svg>
|
||||
</a>
|
||||
@@ -858,15 +945,49 @@ function deleteAttachmentAjax(form, attId, fileName) {
|
||||
return false;
|
||||
}
|
||||
|
||||
function updateNewTaskFileInput(input) {
|
||||
let list = document.getElementById('new-task-files-list');
|
||||
if(input.files.length > 0) {
|
||||
list.innerHTML = `<span style="color:var(--primary); font-weight:600;">${input.files.length} file(s) attached</span>`;
|
||||
// (removed dead updateNewTaskFileInput – element new-task-files-list does not exist in DOM)
|
||||
|
||||
function openAttachmentPreview(url, fileName, isImage, isText) {
|
||||
let lb = document.getElementById('att-lightbox');
|
||||
let body = document.getElementById('att-lightbox-body');
|
||||
let title = document.getElementById('att-lightbox-title');
|
||||
title.textContent = fileName;
|
||||
body.innerHTML = '';
|
||||
|
||||
if (isImage) {
|
||||
let img = document.createElement('img');
|
||||
img.src = url;
|
||||
img.alt = fileName;
|
||||
body.appendChild(img);
|
||||
} else if (isText) {
|
||||
let pre = document.createElement('pre');
|
||||
pre.textContent = 'Loading…';
|
||||
body.appendChild(pre);
|
||||
fetch(url).then(r => r.text()).then(t => { pre.textContent = t; }).catch(() => { pre.textContent = 'Could not load file.'; });
|
||||
} else {
|
||||
list.textContent = "";
|
||||
let iframe = document.createElement('iframe');
|
||||
iframe.src = url;
|
||||
body.appendChild(iframe);
|
||||
}
|
||||
|
||||
lb.classList.add('open');
|
||||
document.body.style.overflow = 'hidden';
|
||||
}
|
||||
|
||||
function closeAttachmentPreview() {
|
||||
document.getElementById('att-lightbox').classList.remove('open');
|
||||
document.getElementById('att-lightbox-body').innerHTML = '';
|
||||
document.body.style.overflow = '';
|
||||
}
|
||||
|
||||
function lbClickOutside(e) {
|
||||
if (e.target.id === 'att-lightbox') closeAttachmentPreview();
|
||||
}
|
||||
|
||||
document.addEventListener('keydown', function(e) {
|
||||
if (e.key === 'Escape') closeAttachmentPreview();
|
||||
});
|
||||
|
||||
function showCustomAlert(title, msg, type, callback) {
|
||||
let overlay = document.createElement('div');
|
||||
overlay.style.cssText = 'position:fixed; top:0; left:0; width:100vw; height:100vh; background:rgba(0,0,0,0.5); display:flex; align-items:center; justify-content:center; z-index:9999; opacity:0; transition:opacity 0.2s;';
|
||||
@@ -952,6 +1073,4 @@ function uploadFilesAjax(taskId, files) {
|
||||
};
|
||||
xhr.send(formData);
|
||||
}
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user