refactored tickets to include tags

This commit is contained in:
tp_dhu 2025-04-03 13:21:45 +01:00
parent b13cd3448f
commit c0f9ed1094
12 changed files with 190 additions and 27 deletions

View File

@ -9,9 +9,16 @@ class TicketController extends BaseController implements CRUD {
$this->requireLogin();
$filter = $f3->get('GET.status');
// retrieve tickets
$ticket_mapper = new Ticket($this->getDB());
$tickets = $ticket_mapper->findAll();
if($filter){
$tickets = $ticket_mapper->findFiltered($filter);
} else {
$tickets = $ticket_mapper->findAll();
}
// render
$this->renderView('../ui/views/ticket/index.html',

79
app/models/Tag.php Normal file
View File

@ -0,0 +1,79 @@
<?php
class Tag extends \DB\SQL\Mapper
{
protected $tag_table, $tag_table_id;
function __construct($db, $type = null)
{
if($type == null){
// do tag mapping
parent::__construct($db, 'tags');
} else {
$this->tag_table = $type . '_tags';
$this->tag_table_id = $type . '_id';
parent::__construct($db, $this->tag_table);
}
return $this;
}
// VERIFY: possible issue with this?
public function getTagsFor($objects, $id_key = 'id')
{
// echo $this->get('_type_id'); exit;
// printf('<pre>%s</pre>', print_r($this,1)); exit;
if(empty($objects)) return [];
$ids = array_column($objects, $id_key);
$placeholders = implode(',', array_fill(0, count($ids), '?'));
$sql =
'SELECT tt.%1$s, t.id, t.name, t.color
FROM %2$s tt
INNER JOIN tags t ON tt.tag_id = t.id
WHERE tt.%1$s IN (%3$s)';
$sql_sprintf = sprintf($sql, $this->tag_table_id, $this->tag_table, $placeholders);
$rows = $this->db->exec($sql_sprintf, $ids);
$tags_map = [];
foreach($rows as $row)
{
$tags_map[$row[$this->tag_table_id]][] = $row;
}
foreach($objects as &$object)
{
$object['tags'] = $tags_map[$object[$id_key]] ?? [];
}
return $objects;
}
public function getTagsForID($id, $id_key = 'id')
{
$sql = 'SELECT tt.%1$s, t.id, t.name, t.color
FROM %2$s tt
INNER JOIN tags t ON tt.tag_id = t.id
WHERE tt.%1$s = ?';
$sql_sprintf = sprintf($sql, $this->tag_table_id, $this->tag_table);
$rows = $this->db->exec($sql_sprintf, $id);
return $rows;
}
public function findLinkedTags($id = '')
{
$sql = '
SELECT t.name, t.color
FROM `?` tt
LEFT JOIN `tags` t ON t.id = tt.id
WHERE tt.`?` = ?
';
$params = [
$this->_type,
$this->_type_id,
$id
];
return $this->db->exec($sql, $params);
}
}

View File

@ -3,6 +3,7 @@
class Ticket extends \DB\SQL\Mapper {
function __construct($db){
parent::__construct($db, 'tickets');
}
/**
@ -10,8 +11,9 @@ class Ticket extends \DB\SQL\Mapper {
*/
public function findAll(): array
{
return $this->db->exec(
'SELECT t.* , tp.name AS priority_name, ts.name AS status_name, u.display_name
$tickets = $this->db->exec(
'SELECT t.id, t.title, t.created_at,
tp.name AS priority_name, ts.name AS status_name, u.display_name
FROM tickets t
LEFT JOIN ticket_priorities tp ON t.priority_id = tp.id
LEFT JOIN ticket_statuses ts ON t.status_id = ts.id
@ -19,11 +21,60 @@ class Ticket extends \DB\SQL\Mapper {
WHERE t.recycled = 0
ORDER BY t.created_at DESC'
);
$result = $this->getTagsForTickets($tickets);
return $result;
}
public function findFiltered(string $filter): array
{
$sql = '
SELECT t.*, tp.name AS priority_name, ts.name AS status_name, u.display_name
FROM tickets t
LEFT JOIN ticket_priorities tp ON t.priority_id = tp.id
LEFT JOIN ticket_statuses ts ON t.status_id = ts.id
LEFT JOIN users u ON t.created_by = u.id
WHERE t.recycled = 0
';
$params = [];
switch($filter){
case 'open':
$sql .= ' AND status_id = ?';
$params[] = 1;
break;
case 'in_progress':
$sql .= ' AND status_id = ?';
$params[] = 2;
break;
case 'on_hold':
$sql .= ' AND status_id = ?';
$params[] = 3;
break;
case 'completed':
$sql .= ' AND status_id = ?';
$params[] = 4;
break;
}
$sql .= ' ORDER BY t.created_at DESC';
$tickets = $this->db->exec($sql, $params);
$result = $this->getTagsForTickets($tickets);
return $result;
}
public function getTagsForTickets(array $tickets)
{
$tag_mapper = new Tag($this->db, 'ticket');
$tickets = $tag_mapper->getTagsFor($tickets);
return $tickets;
}
public function findById($id): ?Ticket
{
$this->status_name = 'SELECT name FROM ticket_statuses WHERE tickets.status_id = ticket_statuses.id';
$this->priority_name = 'SELECT name FROM ticket_priorities WHERE tickets.priority_id = ticket_priorities.id';
$this->load(['id = ?', $id]);
$this->tags = (new Tag($this->db,'ticket'))->getTagsForID($id, 'ticket_id');
return $this->dry() ? null : $this;
}
@ -35,7 +86,7 @@ class Ticket extends \DB\SQL\Mapper {
$this->description = $data['description'] ?? '';
//
$this->priority_id = $data['priority_id'] ?? null;
$this->status = $data['status_id'] ?? null;
$this->status_id = $data['status_id'] ?? null;
//
$this->created_by = $data['created_by'] ?? null;
$this->created_at = ($data['created_at'] == '' ? date('Y-m-d H:i:s') : $data['created_at']) ?? date('Y-m-d H:i:s');

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -28,12 +28,13 @@
@extend .is-flex;
@extend .is-justify-content-flex-start;
@extend .is-flex-wrap-wrap;
@extend .mb-1;
align-items: center;
.ticket-title {
@extend .title;
@extend .mb-0;
@extend .is-5;
@extend .mb-1;
font-weight: normal;
}

View File

@ -15,16 +15,10 @@
<span class="ticket-title">
<a href="/ticket/{{ @ticket.id }}">{{ @ticket.title }}</a>
</span>
<div class="tags">
<div class="tags ml-2">
<!-- TODO: get tags -->
<span class="tag is-link">tag</span>
</div>
<?php /*
<div class="tags ml-2">
<repeat group="{{ @ticket.tags }}" value="{{ @tag }}">
<span class="tag is-link">{{ @tag }}</span>
<span class="tag is-{{@tag.color}}">{{ @tag.name }}</span>
</repeat>
*/ ?>
</div>
</div>
<div class="ticket-meta">

View File

@ -6,7 +6,7 @@
<bulma type="H_FIELD_INPUT" label="Created At:" name="created_at" value=""></bulma>
<bulma type="H_FIELD_TEXTAREA" label="Description:" name="description" value=""></bulma>
<!-- TODO implement priority and status -->
<!-- priority and status -->
<bulma type="H_FIELD_SELECT_NEW" label="Priority:" name="priority_id"
options="priorities" option_value="id" option_name="name"
selected="2"></bulma>

View File

@ -51,6 +51,16 @@
</div>
<div class="column">
<div class="block">
<!-- priority and status -->
<bulma type="FIELD_SELECT" label="Priority:" name="priority_id"
options="{{@priorities}}" option_value="id" option_name="name"
selected="{{@ticket.priority_id}}"></bulma>
<bulma type="FIELD_SELECT" label="Status:" name="status_id"
options="{{@statuses}}" option_value="id" option_name="name"
selected="{{@ticket.status_id}}"></bulma>
</div>
<!-- meta data -->
<div class="block">
<table class="table is-bordered is-fullwidth">

View File

@ -13,7 +13,7 @@
<bulma type="H_FIELD_SELECT_NEW" label="Status:" name="status_id"
options="statuses" option_value="id" option_name="name"
selected="@ticket.status_id"></bulma>
selected="{{@ticket.status_id}}"></bulma>
<include href="/ui/parts/clipboard.html"></include>

View File

@ -2,22 +2,38 @@
<include href="/ui/session/error.html"></include>
</include>
<!-- updated design -- inspiration gitea -->
<div class="field is-grouped">
<div class="control is-expanded">
<div class="field has-addons is-expanded">
<div class="control is-expanded">
<input class="input" type="text" placeholder="Find a ticket">
<div class="block">
<div class="field is-grouped">
<div class="control is-expanded">
<div class="field has-addons is-expanded">
<div class="control is-expanded">
<input class="input" type="text" placeholder="Find a ticket">
</div>
<div class="control">
<button class="button is-info"><span class="icon"><i
class="fas fa-magnifying-glass"></i></span></button>
</div>
</div>
</div>
<div class="field">
<div class="control">
<button class="button is-info"><span class="icon"><i class="fas fa-magnifying-glass"></i></span></button>
<p><a class="button is-primary" href="/ticket/create">create ticket</a></p>
</div>
</div>
</div>
<div class="field">
<div class="control">
<p><a class="button is-primary" href="/ticket/create">create ticket</a></p>
</div>
</div>
<div class="block">
<div class="field has-addons">
<!-- TODO: move this into a template -->
<repeat group="{{ IconsHelper::$status_icons}}" key="{{ @k }}" value="{{ @icon }}">
<p class="control">
<a href="{{ @PATH }}/?status={{ @k }}" class="button">
<span class="icon is-small"><i class="fas fa-{{ @icon[0] }}"></i></span>
<span>{{ IconsHelper::$status_names[@k] }}</span>
</a>
</p>
</repeat>
</div>
</div>
<hr>

View File

@ -24,6 +24,11 @@
<div class="column">
<!-- meta data -->
<div class="block">
<div class="tags">
<repeat group="{{ @ticket.tags }}" value="{{@tag}}">
<span class="tag is-{{@tag.color}}">{{@tag.name}}</span>
</repeat>
</div>
<table class="table is-bordered is-fullwidth">
<thead>
<tr><th class="has-width-100">Property</th><th>Value</th></tr>