Compare commits

...

4 Commits

12 changed files with 193 additions and 38 deletions

View File

@ -1,9 +1,11 @@
<?php <?php
class DashboardController { class DashboardController extends BaseController {
function index($f3){ function index($f3){
$f3->set('content', '../ui/views/dashboard.html');
echo \Template::instance()->render('../ui/templates/layout.html'); $this->requireLogin();
$this->renderView('/ui/views/dashboard.html');
} }
} }

View File

@ -1,12 +1,11 @@
<?php <?php
class HomeController { class HomeController extends BaseController {
public function display($f3){ public function display($f3){
$f3->set('content', '/ui/views/home.html'); $this->renderView('/ui/views/home.html');
echo \Template::instance()->render('../ui/templates/layout.html');
} }
// ... // ...
} }

View File

@ -45,9 +45,15 @@ class TicketController extends BaseController implements CRUD {
// show create form // show create form
// TODO_PROJECTS: dropdown to associate ticket with project // TODO_PROJECTS: dropdown to associate ticket with project
public function createForm($f3){ public function createForm($f3){
$db = $this->getDB();
$priorities = (new TicketPriority($db))->findAll();
$statuses = (new TicketStatus($db))->findAll();
$this->requireLogin(); $this->requireLogin();
$this->renderView('../ui/views/ticket/create.html'); $this->renderView('../ui/views/ticket/create.html',[
'priorities' => $priorities,
'statuses' => $statuses
]);
} }
// handle POST // handle POST
@ -60,8 +66,8 @@ class TicketController extends BaseController implements CRUD {
'title' => $this->f3->get('POST.title'), 'title' => $this->f3->get('POST.title'),
'created_at' => $this->f3->get('POST.created_at'), 'created_at' => $this->f3->get('POST.created_at'),
'description' => $this->f3->get('POST.description'), 'description' => $this->f3->get('POST.description'),
'priority' => $this->f3->get('POST.priority'), 'priority_id' => $this->f3->get('POST.priority_id'),
'status' => $this->f3->get('POST.status'), 'status_id' => $this->f3->get('POST.status_id'),
'created_by' => $this->f3->get('SESSION.user.id') 'created_by' => $this->f3->get('SESSION.user.id')
]; ];
@ -80,7 +86,8 @@ class TicketController extends BaseController implements CRUD {
// show edit form // show edit form
// including custom forms // including custom forms
// TODO_PROJECTS: allow reasssigning or removing a project association // TODO_PROJECTS: allow reasssigning or removing a project association
public function editForm($f3){ public function editForm($f3)
{
$this->requireLogin(); $this->requireLogin();
$ticket_id = $f3->get('PARAMS.id'); $ticket_id = $f3->get('PARAMS.id');
@ -92,16 +99,23 @@ class TicketController extends BaseController implements CRUD {
$this->f3->reroute('/tickets'); $this->f3->reroute('/tickets');
} }
// dropdowns
$priorities = (new TicketPriority($this->getDB()))->findAll();
$statuses = (new TicketStatus($this->getDB()))->findAll();
$this->renderView('../ui/views/ticket/edit.html',[ $this->renderView('../ui/views/ticket/edit.html',[
'ticket' => $ticket, 'ticket' => $ticket,
'ticket_meta' => $ticket->getMeta() 'ticket_meta' => $ticket->getMeta(),
'priorities' => $priorities,
'statuses' => $statuses
] ]
); );
return; return;
} }
// process edit POST TODO: if assigned or admin // process edit POST TODO: if assigned or admin
public function update($f3){ public function update($f3)
{
$this->requireLogin(); $this->requireLogin();
@ -118,8 +132,8 @@ class TicketController extends BaseController implements CRUD {
'title' => $this->f3->get('POST.title'), 'title' => $this->f3->get('POST.title'),
'created_at' => $this->f3->get('POST.created_at'), 'created_at' => $this->f3->get('POST.created_at'),
'description' => $this->f3->get('POST.description'), 'description' => $this->f3->get('POST.description'),
'priority' => $this->f3->get('POST.priority'), 'priority_id' => $this->f3->get('POST.priority_id'),
'status' => $this->f3->get('POST.status'), 'status_id' => $this->f3->get('POST.status_id'),
'updated_by' => $this->f3->get('SESSION.user.id') 'updated_by' => $this->f3->get('SESSION.user.id')
]; ];
$ticket->updateTicket($data); $ticket->updateTicket($data);

View File

@ -5,21 +5,26 @@ class BulmaFormHelper extends \Prefab {
const H_FIELD_INPUT = 1; const H_FIELD_INPUT = 1;
const H_FIELD_TEXTAREA = 2; const H_FIELD_TEXTAREA = 2;
const H_FIELD_SELECT = 3; const H_FIELD_SELECT = 3;
const H_FIELD_SELECT_NEW = 4;
static public function render($args) { static public function render($node) {
$attr = $node['@attrib'] ?? [];
$type = strtoupper($attr['type']) ?? null;
$type = strtoupper($args['@attrib']['type']);
// all * // all *
$label = $args['@attrib']['label']; $label = $attr['label'];
$name = $args['@attrib']['name']; $name = $attr['name'];
$value = isset($args['@attrib']['value']) ? $args['@attrib']['value'] : ''; $value = isset($attr['value']) ? $attr['value'] : '';
// select // select
$options = isset($args['@attrib']['options']) ? $args['@attrib']['options'] : ''; $options = isset($attr['options']) ? $attr['options'] : '';
$selected = isset($args['@attrib']['selected']) ? $args['@attrib']['selected'] : ''; $selected = isset($attr['selected']) ? $attr['selected'] : '';
// //
$label = \Template::instance()->build($label); $label = \Template::instance()->build($label);
$name = \Template::instance()->build($name); $name = \Template::instance()->build($name);
$value = \Template::instance()->build($value); $value = \Template::instance()->build($value);
$options_array = \Template::instance()->token($options);
if(defined("BulmaFormHelper::$type")){ if(defined("BulmaFormHelper::$type")){
@ -35,6 +40,9 @@ class BulmaFormHelper extends \Prefab {
case BulmaFormHelper::H_FIELD_SELECT: case BulmaFormHelper::H_FIELD_SELECT:
return BulmaFormHelper::build_h_field_select($label, $name, $options, $selected); return BulmaFormHelper::build_h_field_select($label, $name, $options, $selected);
break; break;
case BulmaFormHelper::H_FIELD_SELECT_NEW:
return BulmaFormHelper::build_h_field_select_new($attr);
break;
default: default:
return '<div class="notification is-danger">Error: Bulma CSS Form TYPE ('.$type.') not defined.</div>'; return '<div class="notification is-danger">Error: Bulma CSS Form TYPE ('.$type.') not defined.</div>';
break; break;
@ -46,6 +54,40 @@ class BulmaFormHelper extends \Prefab {
} }
static function build_h_field_select_new($attr)
{
$f3 = \Base::instance();
$label = $attr['label'] ?? '';
$name = $attr['name'] ?? '';
$options_arr = $attr['options'] ?? [];
$optionValue = $attr['option_value'] ?? 'id';
$optionName = $attr['option_name'] ?? 'name';
$selected = $attr['selected'] ?? '';
$options = $f3->get($options_arr);
$html = '<div class="field is-horizontal"><div class="field-label is-normal">';
if (!empty($label)) {
$html .= '<label class="label">'.$label.'</label>';
}
$html .= '</div><div class="field-body"><div class="field">';
$html .= '<div class="select">';
$html .= '<select name="'.$name.'">';
foreach ($options as $option) {
$value = $option[$optionValue] ?? '';
$text = $option[$optionName] ?? '';
$sel = ((string)$value === (string)$selected) ? ' selected="selected"' : '';
$html .= '<option value="'.$value.'"'.$sel.'>'.$text.'</option>';
}
$html .= '</select>';
$html .= '</div></div></div></div>';
return $html;
}
static function build_h_field_input($label, $name, $value){ static function build_h_field_input($label, $name, $value){
$string = ' $string = '
<div class="field is-horizontal"> <div class="field is-horizontal">

View File

@ -0,0 +1,53 @@
<?php
class IconsHelper extends \Prefab {
static public $status_icons = [
'New' => ['fas fa-plus-circle has-text-warning', "🆕"],
'In Progress' => ['fas fa-repeat has-text-link', "🔄"],
'On Hold' => ['fas fa-pause-circle has-text-danger',"⏸️"],
'Completed' => ['fas fa-check-circle has-text-success', ""]
];
static public $priority_icons = [
'Low' => ['fas fa-arrow-down',"🟢"],
'Medium' => ['fas fa-minus', "🟡"],
'High' => ['fas fa-arrow-up', "🔴"]
];
static public function icons($node){
$attr = $node['@attrib'];
$tpl = Template::instance();
$f3 = Base::instance();
$context = $f3->hive();
$inner = $tpl->token($node[0], $context);
return '<?php echo IconsHelper::do_the_switch("' . $attr['type'] . '", ' . $inner . '); ?>';
}
static function do_the_switch($type, $value){
$icon_class = '';
switch(strtolower($type)){
case 'status':
$icon_class = IconsHelper::$status_icons[$value] ?? ['fas fa-question-circle has-text-info', "🔲"];
break;
case 'priority':
$icon_class = IconsHelper::$priority_icons[$value] ?? ['fas fa-question-circle', "🔲"];
break;
default:
$icon_class = 'fas fa-question-circle';
}
return '<span class="is-size-5">'.$icon_class[1].'</span>';
return '<i class="'.$icon_class[0].' is-size-4"></i>';
}
}
\Template::instance()->extend('icons', 'IconsHelper::icons');

View File

@ -11,10 +11,12 @@ class Ticket extends \DB\SQL\Mapper {
public function findAll(): array public function findAll(): array
{ {
return $this->db->exec( return $this->db->exec(
'SELECT * 'SELECT t.* , tp.name AS priority_name, ts.name AS status_name
FROM tickets FROM tickets t
WHERE recycled = 0 LEFT JOIN ticket_priorities tp ON t.priority_id = tp.id
ORDER BY created_at DESC' LEFT JOIN ticket_statuses ts ON t.status_id = ts.id
WHERE t.recycled = 0
ORDER BY t.created_at DESC'
); );
} }
@ -30,10 +32,12 @@ class Ticket extends \DB\SQL\Mapper {
$this->title = $data['title'] ?? ''; $this->title = $data['title'] ?? '';
$this->description = $data['description'] ?? ''; $this->description = $data['description'] ?? '';
$this->priority = $data['priority'] ?? 'Low'; //
$this->status = $data['status'] ?? 'New'; $this->priority_id = $data['priority_id'] ?? null;
$this->status = $data['status_id'] ?? null;
//
$this->created_by = $data['created_by'] ?? null; $this->created_by = $data['created_by'] ?? null;
$this->created_at = $data['created_at'] ?? date('Y-m-d H:i:s'); $this->created_at = ($data['created_at'] == '' ? date('Y-m-d H:i:s') : $data['created_at']) ?? date('Y-m-d H:i:s');
$this->updated_at = date('Y-m-d H:i:s'); $this->updated_at = date('Y-m-d H:i:s');
$this->save(); $this->save();
@ -44,8 +48,8 @@ class Ticket extends \DB\SQL\Mapper {
{ {
if(isset($data['title'])){ $this->title = $data['title']; } if(isset($data['title'])){ $this->title = $data['title']; }
if(isset($data['description'])) { $this->description = $data['description']; } if(isset($data['description'])) { $this->description = $data['description']; }
if(isset($data['priority'])) { $this->priority = $data['priority']; } if(isset($data['priority_id'])) { $this->priority_id = $data['priority_id']; }
if(isset($data['status'])) { $this->status = $data['status']; } if(isset($data['status_id'])) { $this->status_id = $data['status_id']; }
if(isset($data['updated_by'])) { $this->updated_by = $data['updated_by']; } if(isset($data['updated_by'])) { $this->updated_by = $data['updated_by']; }
$this->created_at = ($data['created_at'] == '' ? date('Y-m-d H:i:s') : $data['created_at']) ?? date('Y-m-d H:i:s'); $this->created_at = ($data['created_at'] == '' ? date('Y-m-d H:i:s') : $data['created_at']) ?? date('Y-m-d H:i:s');
$this->updated_at = date('Y-m-d H:i:s'); $this->updated_at = date('Y-m-d H:i:s');

View File

@ -0,0 +1,16 @@
<?php
class TicketPriority extends \DB\SQL\Mapper
{
function __construct($db)
{
parent::__construct($db, 'ticket_priorities');
}
public function findAll(): array
{
return $this->db->exec(
'SELECT * FROM ticket_priorities ORDER BY sort_order ASC'
);
}
}

View File

@ -0,0 +1,16 @@
<?php
class TicketStatus extends \DB\SQL\Mapper
{
function __construct($db)
{
parent::__construct($db, 'ticket_statuses');
}
public function findAll(): array
{
return $this->db->exec(
'SELECT * FROM ticket_statuses ORDER BY sort_order ASC'
);
}
}

View File

@ -15,7 +15,7 @@ $htmlpurifier = \HTMLPurifier::instance();
$md = \ParsedownTableExtension::instance(); $md = \ParsedownTableExtension::instance();
$md->setSafeMode(true); $md->setSafeMode(true);
$f3->set('EXT', [new ParsedownHelper, new BulmaFormHelper]); $f3->set('EXT', [new ParsedownHelper, new BulmaFormHelper, new IconsHelper]);
$f3->set('DB', new \DB\SQL( $f3->set('DB', new \DB\SQL(
'mysql:host=localhost;port=3306;dbname=' . $f3->get('database.db_name'), 'mysql:host=localhost;port=3306;dbname=' . $f3->get('database.db_name'),

View File

@ -1,6 +1,5 @@
<h1 class="title">Create Ticket Form</h1> <h1 class="title">Create Ticket Form</h1>
<form action="/ticket/create" method="POST"> <form action="/ticket/create" method="POST">
<bulma type="H_FIELD_INPUT" label="Title:" name="title" value=""></bulma> <bulma type="H_FIELD_INPUT" label="Title:" name="title" value=""></bulma>
@ -8,8 +7,13 @@
<bulma type="H_FIELD_TEXTAREA" label="Description:" name="description" value=""></bulma> <bulma type="H_FIELD_TEXTAREA" label="Description:" name="description" value=""></bulma>
<!-- TODO implement priority and status --> <!-- TODO implement priority and status -->
<bulma type="H_FIELD_SELECT" label="Priority:" name="priority" options="['Low', 'Medium', 'High']" selected="Medium"></bulma> <bulma type="H_FIELD_SELECT_NEW" label="Priority:" name="priority_id"
<bulma type="H_FIELD_SELECT" label="Status:" name="status" options="['New', 'In Progress', 'On Hold', 'Completed']" selected="New"></bulma> options="priorities" option_value="id" option_name="name"
selected="2"></bulma>
<bulma type="H_FIELD_SELECT_NEW" label="Status:" name="status_id"
options="statuses" option_value="id" option_name="name"
selected="1"></bulma>
<!-- custom fields --> <!-- custom fields -->
<hr> <hr>

View File

@ -7,8 +7,13 @@
<bulma type="H_FIELD_TEXTAREA" label="Description:" name="description" value="{{@ticket.description}}"></bulma> <bulma type="H_FIELD_TEXTAREA" label="Description:" name="description" value="{{@ticket.description}}"></bulma>
<bulma type="H_FIELD_SELECT" label="Priority:" name="priority" options="['Low', 'Medium', 'High']" selected="{{@ticket.priority}}"></bulma> <bulma type="H_FIELD_SELECT_NEW" label="Priority:" name="priority_id"
<bulma type="H_FIELD_SELECT" label="Status:" name="status" options="['New', 'In Progress', 'On Hold', 'Completed']" selected="{{@ticket.status}}"></bulma> options="priorities" option_value="id" option_name="name"
selected="{{@ticket.priority_id}}"></bulma>
<bulma type="H_FIELD_SELECT_NEW" label="Status:" name="status_id"
options="statuses" option_value="id" option_name="name"
selected="@ticket.status_id"></bulma>
<include href="/ui/parts/clipboard.html"></include> <include href="/ui/parts/clipboard.html"></include>

View File

@ -17,8 +17,8 @@
<tr> <tr>
<td>{{@ticket.id}}</td> <td>{{@ticket.id}}</td>
<td><a href="/ticket/{{@ticket.id}}">{{@ticket.title}}</a></td> <td><a href="/ticket/{{@ticket.id}}">{{@ticket.title}}</a></td>
<td>{{@ticket.status}}</td> <td><icons type="status">{{@ticket.status_name}}</icons></td>
<td>{{@ticket.priority}}</td> <td><icons type="priority">{{@ticket.priority_name}}</icons></td>
<td>{{@ticket.created_at}}</td> <td>{{@ticket.created_at}}</td>
<td> <td>
<a class="button is-link is-small" href="/ticket/{{@ticket.id}}/edit"> <a class="button is-link is-small" href="/ticket/{{@ticket.id}}/edit">