From 2a711584cd2d9fd650c68244d4eaad8575c03712 Mon Sep 17 00:00:00 2001 From: tp_dhu Date: Sun, 16 Feb 2025 22:07:02 +0000 Subject: [PATCH] for adding comments to tickets --- app/controllers/CommentController.php | 91 +++++++++++++++++++ public/js/ticket_view.js | 30 +++++++ public/style.css | 121 +++++++++++++++++++++++++- ui/templates/layout.html | 35 ++++---- ui/views/comments/view.html | 41 +++++++++ 5 files changed, 300 insertions(+), 18 deletions(-) create mode 100644 app/controllers/CommentController.php create mode 100644 public/js/ticket_view.js create mode 100644 ui/views/comments/view.html diff --git a/app/controllers/CommentController.php b/app/controllers/CommentController.php new file mode 100644 index 0000000..2f449b3 --- /dev/null +++ b/app/controllers/CommentController.php @@ -0,0 +1,91 @@ +exists('SESSION.user')){ + $f3->reroute('/login'); + } + + $ticket_id = (int) $f3->get('PARAMS.id'); + $comment_text = $f3->get('POST.comment'); + $current_user_id = $f3->get('SESSION.user.id'); + + if(empty($comment_text)){ + $f3->set('SESSION.error', 'ticket not updated. No content'); + $f3->reroute('/ticket/' . $ticket_id); + } + + // insert comment + $db = $f3->get('DB'); + $db->exec( + 'INSERT INTO ticket_comments (ticket_id, comment, created_by, created_at) + VALUES (?, ?, ?, NOW())', + [$ticket_id, $comment_text, $current_user_id] + ); + + $f3->reroute('/ticket/' . $ticket_id); + } + + /** + * Delete an existing comment + * Route: GET /tickey/@id/comment/@comment_id/delete + */ + public function delete($f3){ + if(!$f3->exists('SESSION.user')){ + $f3->reroute('/login'); + } + + $ticket_id = (int) $f3->get('PARAMS.id'); + $comment_id = (int) $f3->get('PARAMS.comment_id'); + $current_user = $f3->get('SESSION.user'); + + $db = $f3->get('DB'); + + //optional: check if user is allowed to delete comment. + // fetch who created the comment + $comment_row = $db->exec( + 'SELECT created_by FROM ticket_comments WHERE id = ? AND ticket_id = ? LIMIT 1', + [$comment_id, $ticket_id] + ); + if(!$comment_row){ + $f3->set('SESSION.error', 'Error: Ticket comment ID not found.'); + $f3->reroute('/ticket/'.$ticket_id); + } + $comment_owner = $comment_row[0]['created_by']; + // TODO: $is_admin = () + if($current_user['id'] !== $comment_owner){ + // no permission + $f3->set('SESSION.error', 'You do not have permission to delete this ticket'); + $f3->reroute('/ticket/'. $ticket_id); + } + + // Delete - addition, rather than delete, we set a delete flag + $db->exec('UPDATE ticket_comments SET deleted = 1 WHERE id = ?', [$comment_id]); + $f3->reroute('/ticket/' . $ticket_id); + } + + // view comments + public function index($f3){ + $ticket_id = (int) $f3->get('PARAMS.id'); + $db = $f3->get('DB'); + $results = $db->exec(' + SELECT c.*, u.username AS author_name + FROM ticket_comments c + LEFT JOIN users u ON c.created_by = u.id + WHERE c.ticket_id = ? + ORDER BY c.created_at DESC', + [$ticket_id] + ); + $comments = $results; + $f3->set('comments', $comments); + + echo \Template::instance()->render('../ui/views/comments/view.html'); + } +} \ No newline at end of file diff --git a/public/js/ticket_view.js b/public/js/ticket_view.js new file mode 100644 index 0000000..46c05d5 --- /dev/null +++ b/public/js/ticket_view.js @@ -0,0 +1,30 @@ +document.addEventListener('DOMContentLoaded', function(){ + const ticket_id = window.location.pathname.split('/')[2]; + const comments_url = `/ticket/${ticket_id}/comments`; + const attachments_url = `/ticket/${ticket_id}/attachments`; + + function ajax(url, containerID){ + fetch(url) + .then(response => { + if(!response.ok){ + throw new Error('Network response was not ok.'); + } + return response.text(); + }) + .then(html => { + const container_el = document.getElementById(containerID); + if(container_el){ + container_el.innerHTML += html; + } else { + throw new Error('Coments container does not exist'); + } + }) + .catch(error => { + console.log('Error fetching comments', error); + }); + } + + ajax(attachments_url, 'attachments') + ajax(comments_url, 'comments') +}); + diff --git a/public/style.css b/public/style.css index 41b2afb..351b7f7 100644 --- a/public/style.css +++ b/public/style.css @@ -3,4 +3,123 @@ html, body, #sidebar, #page,#base_body { min-height: 100% } -#page { min-height: calc(100vh - 170px - 52px) } \ No newline at end of file +#page { min-height: calc(100vh - 170px - 52px) } + +.table th.th-icon { width: 2rem; } + +/* List Component */ +.list{ + --be-list-color:var(--bulma-text); + --be-list-item-description-color:var(--bulma-text-50); + --be-list-item-divider-color:var(--bulma-border); + --be-list-item-hover-color:var(--bulma-scheme-main-bis); + --be-list-item-image-margin:.75em; + --be-list-item-padding:.75em; + --be-list-item-title-color:var(--bulma-text-strong); + --be-list-item-title-weight:var(--bulma-weight-semibold); + color:var(--be-list-color); + flex-direction:column; + display:flex +} +.list.has-hidden-images .list-item-image{ + display:none +} +.list.has-hoverable-list-items .list-item:hover{ + background-color:var(--be-list-item-hover-color) +} +.list.has-overflow-ellipsis .list-item-content{ + min-inline-size:0; + max-inline-size:calc(var(--length)*1ch) +} +.list.has-overflow-ellipsis .list-item-content>*{ + text-overflow:ellipsis; + white-space:nowrap; + overflow:hidden +} +@media (hover:hover){ + .list:not(.has-visible-pointer-controls) .list-item-controls{ + opacity:0; + visibility:hidden + } +} +.list .list-item{ + align-items:center; + transition:background-color .125s ease-out; + display:flex; + position:relative; + /* TP: update + align top */ + align-items: flex-start; +} +@media (hover:hover){ + .list .list-item:hover .list-item-controls,.list .list-item:focus-within .list-item-controls{ + opacity:initial; + visibility:initial + } +} +.list .list-item:not(.box){ + padding-block:var(--be-list-item-padding); + padding-inline:var(--be-list-item-padding) +} +.list .list-item:not(:last-child):not(.box){ + border-block-end:1px solid var(--be-list-item-divider-color) +} +@media screen and (width<=768px){ + .list:not(.has-overflow-ellipsis) .list .list-item{ + flex-wrap:wrap + } +} +.list .list-item-image{ + flex-shrink:0; + margin-inline-end:var(--be-list-item-image-margin); + /* TP: update + add margin-top */ + margin-top: 0.5rem; +} +@media screen and (width<=768px){ + .list .list-item-image{ + padding-block:.5rem; + padding-inline:0 + } +} +.list .list-item-content{ + flex-direction:column; + flex-grow:1; + display:flex +} +@media screen and (width<=768px){ + .list .list-item-content{ + padding-block:.5rem; + padding-inline:0 + } +} +.list .list-item-title{ + color:var(--be-list-item-title-color); + font-weight:var(--be-list-item-title-weight); + margin-bottom: .25rem; +} +.list .list-item-description{ + color:var(--be-list-item-description-color) +} +.list .list-item-controls{ + flex-shrink:0; + transition:opacity .125s ease-out +} +@media screen and (width<=768px){ + .list .list-item-controls{ + flex-wrap:wrap; + padding-block:.5rem; + padding-inline:0 + } +} +@media screen and (width>=769px),print{ + .list .list-item-controls{ + padding-inline-start:var(--be-list-item-padding) + } + .list:not(.has-visible-pointer-controls) .list .list-item-controls{ + block-size:100%; + align-items:center; + padding-block-end:var(--be-list-item-padding); + display:flex; + position:absolute; + inset-inline-end:0 + } +} \ No newline at end of file diff --git a/ui/templates/layout.html b/ui/templates/layout.html index e024bf7..d1e4475 100644 --- a/ui/templates/layout.html +++ b/ui/templates/layout.html @@ -8,14 +8,14 @@ - @@ -94,20 +94,21 @@ - - + } + }); + + \ No newline at end of file diff --git a/ui/views/comments/view.html b/ui/views/comments/view.html new file mode 100644 index 0000000..3aad8d7 --- /dev/null +++ b/ui/views/comments/view.html @@ -0,0 +1,41 @@ +
+

Comments

+
+
+
+ +
+ +
+
+
+ +
+
+
+ +
+ +
+
+
+ + +
+
+
+
+ {{ @comment.author_name}} + {{ @comment.created_at }} +
+
+ {{ @comment.comment | raw }} +
+
+
+
+
+
+
\ No newline at end of file