for adding comments to tickets
This commit is contained in:
parent
a8fe0add5c
commit
2a711584cd
91
app/controllers/CommentController.php
Normal file
91
app/controllers/CommentController.php
Normal file
@ -0,0 +1,91 @@
|
||||
<?php
|
||||
|
||||
class CommentController {
|
||||
|
||||
/**
|
||||
* Add a new comment to a ticket.
|
||||
* Expects POST data: comment (text)
|
||||
* Route: POST /ticket/@id/comment
|
||||
*/
|
||||
public function create($f3){
|
||||
// check logged in
|
||||
if(!$f3->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');
|
||||
}
|
||||
}
|
||||
30
public/js/ticket_view.js
Normal file
30
public/js/ticket_view.js
Normal file
@ -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')
|
||||
});
|
||||
|
||||
119
public/style.css
119
public/style.css
@ -4,3 +4,122 @@ html, body, #sidebar, #page,#base_body {
|
||||
}
|
||||
|
||||
#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
|
||||
}
|
||||
}
|
||||
@ -94,20 +94,21 @@
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<!-- JavaScript for Bulma navbar burger (mobile) -->
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const burgers = Array.prototype.slice.call(document.querySelectorAll('.navbar-burger'), 0);
|
||||
if (burgers.length > 0) {
|
||||
burgers.forEach(el => {
|
||||
el.addEventListener('click', () => {
|
||||
const target = document.getElementById(el.dataset.target);
|
||||
el.classList.toggle('is-active');
|
||||
target.classList.toggle('is-active');
|
||||
<!-- JavaScript for Bulma navbar burger (mobile) -->
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const burgers = Array.prototype.slice.call(document.querySelectorAll('.navbar-burger'), 0);
|
||||
if (burgers.length > 0) {
|
||||
burgers.forEach(el => {
|
||||
el.addEventListener('click', () => {
|
||||
const target = document.getElementById(el.dataset.target);
|
||||
el.classList.toggle('is-active');
|
||||
target.classList.toggle('is-active');
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
41
ui/views/comments/view.html
Normal file
41
ui/views/comments/view.html
Normal file
@ -0,0 +1,41 @@
|
||||
<div class="box" id="comments">
|
||||
<h2 class="title">Comments</h2>
|
||||
<div class="block">
|
||||
<form action="/ticket/{{@PARAMS.id}}/comment" method="POST">
|
||||
<div class="field">
|
||||
<label class="label">Add comment:</label>
|
||||
<div class="control">
|
||||
<textarea class="textarea" name="comment" rows="4" cols="50"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<button class="button is-primary" type="submit">Submit Comment</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<check if="{{ !empty(@comments) }}">
|
||||
<div class="list">
|
||||
<repeat group="{{ @comments }}" value="{{ @comment}}">
|
||||
<div class="list-item">
|
||||
<div class="list-item-image">
|
||||
<figure class="image is-48x48">
|
||||
<img class="is-rounded"
|
||||
src="https://placehold.co/200x200/66d1ff/FFF?text=TP">
|
||||
<!-- <img class="is-rounded"
|
||||
src="https://loremflickr.com/200/200/dog?{{ (int)rand()}}">-->
|
||||
</figure>
|
||||
</div>
|
||||
<div class="list-item-content">
|
||||
<div class="list-item-title is-flex is-justify-content-space-between">
|
||||
<span>{{ @comment.author_name}}</span>
|
||||
<span class="has-text-weight-normal has-text-grey">{{ @comment.created_at }}</span>
|
||||
</div>
|
||||
<div class="list-item-description">
|
||||
<parsedown>{{ @comment.comment | raw }}</parsedown>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</repeat>
|
||||
</div>
|
||||
</check>
|
||||
</div>
|
||||
Loading…
x
Reference in New Issue
Block a user