kb and tag crud with views

This commit is contained in:
tp_dhu 2025-02-11 09:26:53 +00:00
parent 39b633d60a
commit 113a5774ad
8 changed files with 325 additions and 62 deletions

View File

@ -30,11 +30,11 @@ class KBController {
$args[] = $tag_param;
if($search_term){
$sql .= ' AND a.title LIKE ?';
$sql .= ' AND LOWER(a.title) LIKE LOWER(?)';
$args[] = '%' . $search_term . '%';
}
} else if ($search_term){
$sql .= ' WHERE a.title LIKE ?';
$sql .= ' WHERE LOWER(a.title) LIKE LOWER(?)';
$args[] = '%' . $search_term . '%';
}
@ -82,7 +82,7 @@ class KBController {
$db->exec(
'INSERT INTO kb (title, content, created_by, updated_by, created_at, updated_at)
VALUES (?,?,?,?, NOW(), NOW())',
[$title, $content, $created_by]
[$title, $content, $created_by, $created_by]
);
$article_id = $db->lastInsertId();
@ -92,24 +92,26 @@ class KBController {
$f3->reroute('/kb');
}
//
protected function check_kb_exists($article_id, $db, $f3){
$articles = $db->exec(
'SELECT * FROM kb WHERE id = ? LIMIT 1', [$article_id]
);
if(!$articles){
$f3->set('SESSION.error', 'Article not found');
$f3->reroute('/kb');
}
return $articles;
}
// view a single
public function view($f3){
$this->check_access($f3);
$article_id = $f3->get('PARAMS.id');
$db = $f3->get('DB');
$articles = $db->exec(
'SELECT a.*, u.username AS created_by_name
FROM kb AS a
LEFT JOIN users AS u ON a.created_by = u.id
WHERE a.id = ? LIMIT 1',
[$article_id]
);
if(!$articles){
$f3->set('SESSION.error', 'Article not found');
$f3->reroute('/kb');
}
$articles = $this->check_kb_exists($article_id, $db, $f3);
$article = $articles[0];
$f3->set('article', $article);
@ -118,7 +120,7 @@ class KBController {
$tags = $db->exec(
'SELECT t.* FROM tags AS t
JOIN kb_tags AS at ON t.id = at.tag_id
WHERE at.article_id = ?',
WHERE at.kb_id = ?',
[$article_id]
);
@ -128,4 +130,76 @@ class KBController {
$f3->clear('SESSION.error');
}
/**
* Form to edit existing kb article
*/
public function editForm($f3){
$this->check_access($f3);
$article_id = $f3->get('PARAMS.id');
$db = $f3->get('DB');
$articles = $this->check_kb_exists($article_id, $db, $f3);
$article = $articles[0];
$f3->set('article', $article);
// fetch current tags
$current_tag_ids = $db->exec(
'SELECT tag_id FROM kb_tags WHERE kb_id = ?', [$article_id]
);
$article_tag_ids = array_column($current_tag_ids, 'tag_id');
$f3->set('article_tag_ids', $article_tag_ids);
// render
$f3->set('content', '../ui/views/kb/edit.html');
echo \Template::instance()->render('../ui/templates/layout.html');
$f3->clear('SESSION.error');
}
/**
* Handle POST to edit existing article
*/
public function update($f3){
$this->check_access($f3);
$article_id = $f3->get('PARAMS.id');
$db = $f3->get('DB');
$articles = $this->check_kb_exists($article_id, $db, $f3);
$article = $articles[0];
$title = $f3->get('POST.title');
$content = $f3->get('POST.content');
$updated_by = $f3->get('SESSION.user.id');
$db->exec(
'UPDATE kb
SET title=?, content=?, updated_by =?, updated_at = NOW()
WHERE id = ?',
[$title, $content, $updated_by, $article_id]
);
// update tags - first delete
$db->exec('DELETE FROM kb_tags WHERE kb_id = ?', [$article_id]);
$tags_id = $f3->get('POST.tags');
if(!empty($tags_id) && is_array($tags_id)){
foreach($tags_id as $tag_id){
$db->exec(
'INSERT IGNORE INTO kb_tags (article_id, tag_id) VALUES (?,?)',
[$article_id, $tag_id]
);
}
}
$f3->reroute('/kb/'.$article_id);
}
}

View File

@ -0,0 +1,44 @@
<?php
class TagController {
protected function check_access($f3){
if(!$f3->exists('SESSION.user')){
$f3->reroute('/login');
}
}
/**
* List all tags
*/
public function index($f3){
$this->check_access($f3);
$db = $f3->get('DB');
$tags = $db->exec('SELECT * FROM tags ORDER BY name ASC');
$f3->set('tags', $tags);
$f3->set('content', '../ui/views/tag/index.html');
echo \Template::instance()->render('../ui/templates/layout.html');
}
public function createForm($f3){
$this->check_access($f3);
$f3->set('content', '../ui/views/tag/create.html');
echo \Template::instance()->render('../ui/templates/layout.html');
}
public function create($f3){
$this->check_access($f3);
$name = $f3->get('POST.name');
$color = $f3->get('POST.color');
$db = $f3->get('DB');
// insert new tag
$db->exec('INSERT IGNORE INTO tags (name, color) VALUES (?, ?)', [$name, $color]);
$f3->reroute('/tags');
}
}

View File

@ -1,16 +1,34 @@
<h1 class="title">Create Knowledge Base Article</h1>
<form action="/kb/create" method="POST">
<div class="content">
<form action="/kb/create" method="POST">
{{ BulmaForm::horizontal_field_input('Title:', 'title') }}
{{ BulmaForm::horizontal_field_input('Title:', 'title') }}
{{ BulmaForm::horizontal_field_textarea('Description:', 'description') }}
<div id="editor" class="block">
<div class="tabs">
<ul>
<li class="is-active"><a>Write</a></li>
<li class=""><a>Preivew</a></li>
</ul>
</div>
{{ BulmaForm::horizontal_field_select('Priority:', 'priority', ['Low', 'Medium', 'High'])}}
<div class="block">
{{ BulmaForm::horizontal_field_textarea('Content:', 'content') }}
</div>
</div>
{{ BulmaForm::horizontal_field_select('Status:', 'status', ['New', 'In Progress', 'On Hold', 'Completed'])}}
<!-- TODO: tags -->
<button class="button is-primary" type="submit">Create Ticket</button>
<!-- buttons -->
<div class="field is-grouped is-grouped-right">
<div class="control">
<a class="button is-secondary" href="/kb">Cancel</a>
</div>
<div class="control">
<button class="button is-primary" type="submit">Create KB Article</button>
</div>
</div>
</form>
</div>
</form>

25
ui/views/kb/edit.html Normal file
View File

@ -0,0 +1,25 @@
<h1 class="title">Edit Knowledge Base Article</h1>
<form action="/kb/{{@article.id}}/update" method="POST">
{{ BulmaForm::horizontal_field_input('Title:', 'title', @article.title)}}
<div id="editor" class="block">
<div class="tabs">
<ul>
<li class="is-active"><a>Write</a></li>
<li class=""><a>Preivew</a></li>
</ul>
</div>
<div class="block">
{{ BulmaForm::horizontal_field_textarea('Content:', 'content', @article.content) }}
</div>
</div>
<!-- TODO: tags -->
<button class="button is-primary" type="submit">Update Article</button>
</div>
</form>

View File

@ -6,32 +6,56 @@
</div>
</check>
<p><a href="/kb/create">create kb article</a></p>
<p><a href="/kb/create">create new kb article</a></p>
<hr>
<table class="table is-fullwidth is-bordered">
<thead>
<tr>
<th>id</th><th>title</th><th>description</th>
<th>status</th><th>priority</th><th>created_at</th>
<th></th>
</tr>
</thead>
<div class="block">
<form method="GET" action="/kb">
<div class="level">
<div class="level-item">
<input class="input" type="text" name="search" placeholder="Search by title...">
</div>
<div class="level-right">
<div class="select">
<select name="tag">
<option value="">--Filter by Tag</option>
<!-- TODO: load list of all tags-->
</select>
</div>
</div>
<div class="level-right">
<button class="button is-primary" type="submit">Search</button>
</div>
</div>
</form>
</div>
<tbody>
<repeat group="{{@tickets}}" value="{{@ticket}}">
<check if="{{@articles}}">
<table class="table is-fullwidth is-bordered">
<thead>
<tr>
<td>{{@ticket.id}}</td>
<td>{{@ticket.title}}</td>
<td>{{@ticket.description}}</td>
<td>{{@ticket.status}}</td>
<td>{{@ticket.priority}}</td>
<td>{{@ticket.created_at}}</td>
<td>
<a href="/ticket/{{@ticket.id}}"><i class="fa fa-eye"></i></a>
<a href="/ticket/{{@ticket.id}}/edit"><i class="fa fa-edit"></i></a>
</td>
<th>id</th><th>title</th><th>created_at</th>
<th></th>
</tr>
</repeat>
</tbody>
</table>
</thead>
<tbody>
<repeat group="{{@articles}}" value="{{@article}}">
<tr>
<td><a href="/kb/{{@article.id}}">{{@article.id}}</a></td>
<td>{{@article.title}}</td>
<td>{{@article.created_at}}</td>
<td>
<a href="/kb/{{@article.id}}/edit"><i class="fa fa-edit"></i></a>
</td>
</tr>
</repeat>
</tbody>
</table>
</check>
<check if="!@articles">
<div class="notification is-info is-light">
<p>No articles found.</p>
</div>
</check>

View File

@ -1,18 +1,22 @@
<h1 class="title">Knowledge Article</h1>
<h1 class="title">{{@article.title}}</h1>
<div class="box">
<div class="content">
{{ Parsedown::instance()->text(@article.content) }}
</div>
<h2 class="title">{{@article.title}}</h2>
<table class="table is-bordered is-fullwidth">
<thead>
<tr><th class="has-width-200">Property</th><th>Value</th></tr>
</thead>
<tbody>
<repeat group="{{ @ticket }}" key="{{ @key }}" value="{{ @value }}">
<tr><td>{{@key}}</td> <td>{{@value}}</td></tr>
</repeat>
</tbody>
</table>
</div>
<check if="{{ isset(@tags)}}">
<div class="box">
<table class="table is-bordered is-fullwidth">
<thead>
<tr><th class="has-width-200">Property</th><th>Value</th></tr>
</thead>
<tbody>
<repeat group="{{ @ticket }}" key="{{ @key }}" value="{{ @value }}">
<tr><td>{{@key}}</td> <td>{{@value}}</td></tr>
</repeat>
</tbody>
</table>
</div>
</check>

29
ui/views/tag/create.html Normal file
View File

@ -0,0 +1,29 @@
<h1 class="title">Create Tag</h1>
<div class="content">
<form action="/tag/create" method="POST">
<div class="field">
<label class="label">Name</label>
<div class="control">
<input name="name" class="input" type="text" placeholder="Tag Name">
</div>
</div>
<div class="field">
<label class="label">Color</label>
<div class="control">
<input name="color" class="input" type="text" placeholder="Tag Color">
</div>
</div>
<div class="field is-grouped is-grouped-right">
<div class="control">
<a class="button is-secondary" href="/tags">Cancel</a>
</div>
<div class="control">
<button class="button is-primary" type="submit">Create Tag</button>
</div>
</div>
</form>
</div>

45
ui/views/tag/index.html Normal file
View File

@ -0,0 +1,45 @@
<h1 class="title">Tags</h1>
<check if="{{isset(@SESSION.error)}}">
<div class="notification is-warning">
{{ @SESSION.error }}
</div>
</check>
<p><a href="/tag/create">create new tag</a></p>
<hr>
<check if="{{ count(@tags) > 1 }}">
<true>
<div class="tags are-medium">
<repeat group="{{ @tags }}" value="{{ @tag }}">
<a class="tag {{ (@tag.color ? 'is-' . @tag.color : '') }}">{{ @tag.name }}</a>
</repeat>
</div>
</true>
<false>
<div class="notification is-size-5">
<span><i class="fa fa-2xl fa-face-sad-tear"></i> </span>No tags found
</div>
</false>
</check>
<div class="box has-background-dark">
<div class="block">
<h3 class="subtitle has-text-white"><i class="fa-solid fa-lightbulb has-text-warning"></i> Color Examples</h3>
<p class="has-text-white">The following color names can be used for tags</p>
</div>
<div class="block">
<span class="tag is-black">Black</span>
<span class="tag is-dark">Dark</span>
<span class="tag is-light">Light</span>
<span class="tag is-white">White</span>
<span class="tag is-primary">Primary</span>
<span class="tag is-link">Link</span>
<span class="tag is-info">Info</span>
<span class="tag is-success">Success</span>
<span class="tag is-warning">Warning</span>
<span class="tag is-danger">Danger</span>
</div>
</div>