Compare commits
6 Commits
7083a55e03
...
23cad42bc1
| Author | SHA1 | Date | |
|---|---|---|---|
| 23cad42bc1 | |||
| 88b832dd03 | |||
| 9f7dab2a36 | |||
| c0f9ed1094 | |||
| b13cd3448f | |||
| ba3c93ba19 |
@ -46,6 +46,9 @@ POST /tag/create=TagController->create
|
||||
; parsedown preview
|
||||
POST /parsedown/preview=ParsedownPreview->view
|
||||
|
||||
; toggle-theme
|
||||
POST /toggle-theme = ThemeController->toggle
|
||||
|
||||
; dashboard
|
||||
GET /dashboard=DashboardController->index
|
||||
|
||||
|
||||
13
app/controllers/ThemeController.php
Normal file
13
app/controllers/ThemeController.php
Normal file
@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
class ThemeController
|
||||
{
|
||||
function toggle($f3)
|
||||
{
|
||||
$current = $f3->get('SESSION.theme') ?: 'light';
|
||||
$new_theme = ($current === 'light') ? 'dark' : 'light';
|
||||
$f3->set('SESSION.theme', $new_theme);
|
||||
|
||||
$f3->reroute($f3->get('HEADERS.Referer') ?: '/');
|
||||
}
|
||||
}
|
||||
@ -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());
|
||||
|
||||
if($filter){
|
||||
$tickets = $ticket_mapper->findFiltered($filter);
|
||||
} else {
|
||||
$tickets = $ticket_mapper->findAll();
|
||||
}
|
||||
|
||||
// render
|
||||
$this->renderView('../ui/views/ticket/index.html',
|
||||
@ -99,6 +106,9 @@ class TicketController extends BaseController implements CRUD {
|
||||
$this->f3->reroute('/tickets');
|
||||
}
|
||||
|
||||
//
|
||||
$f3->set('js', 'markdown_preview.js');
|
||||
|
||||
// dropdowns
|
||||
$priorities = (new TicketPriority($this->getDB()))->findAll();
|
||||
$statuses = (new TicketStatus($this->getDB()))->findAll();
|
||||
|
||||
@ -7,24 +7,30 @@ class BulmaFormHelper extends \Prefab {
|
||||
const H_FIELD_SELECT = 3;
|
||||
const H_FIELD_SELECT_NEW = 4;
|
||||
|
||||
const FIELD_INPUT = 10;
|
||||
const FIELD_TEXTAREA = 11;
|
||||
const FIELD_SELECT = 13;
|
||||
|
||||
static public function render($node) {
|
||||
|
||||
$attr = $node['@attrib'] ?? [];
|
||||
$type = strtoupper($attr['type']) ?? null;
|
||||
|
||||
// all *
|
||||
$label = $attr['label'];
|
||||
$name = $attr['name'];
|
||||
$value = isset($attr['value']) ? $attr['value'] : '';
|
||||
$label = $attr['label'] ?? '';
|
||||
$name = $attr['name'] ?? '';
|
||||
$value = $attr['value'] ?? '';
|
||||
$class = $attr['class'] ?? '';
|
||||
// select
|
||||
$options = isset($attr['options']) ? $attr['options'] : '';
|
||||
$selected = isset($attr['selected']) ? $attr['selected'] : '';
|
||||
//
|
||||
$options = $attr['options'] ?? [];
|
||||
$selected = $attr['selected'] ?? [];
|
||||
// textarea
|
||||
$rows = $attr['rows'] ?? '';
|
||||
|
||||
$label = \Template::instance()->build($label);
|
||||
$name = \Template::instance()->build($name);
|
||||
$value = \Template::instance()->build($value);
|
||||
$options_array = \Template::instance()->token($options);
|
||||
$selected = \Template::instance()->build($selected);
|
||||
|
||||
if(defined("BulmaFormHelper::$type")){
|
||||
|
||||
@ -43,6 +49,15 @@ class BulmaFormHelper extends \Prefab {
|
||||
case BulmaFormHelper::H_FIELD_SELECT_NEW:
|
||||
return BulmaFormHelper::build_h_field_select_new($attr);
|
||||
break;
|
||||
case BulmaFormHelper::FIELD_INPUT:
|
||||
return BulmaFormHelper::build_field_input($label, $name, $value, $class);
|
||||
break;
|
||||
case BulmaFormHelper::FIELD_TEXTAREA:
|
||||
return BulmaFormHelper::build_field_textarea($label, $name, $value, $class, $rows);
|
||||
break;
|
||||
case BulmaFormHelper::FIELD_SELECT:
|
||||
return BulmaFormHelper::build_field_select($attr);
|
||||
break;
|
||||
default:
|
||||
return '<div class="notification is-danger">Error: Bulma CSS Form TYPE ('.$type.') not defined.</div>';
|
||||
break;
|
||||
@ -54,6 +69,132 @@ class BulmaFormHelper extends \Prefab {
|
||||
|
||||
}
|
||||
|
||||
static function build_field_input($label, $name, $value, $class, $rows=10){
|
||||
|
||||
$string_label = $label !== '' ? sprintf('<label class="label">%1$s</label>', $label) : '';
|
||||
$string = '
|
||||
<div class="field %4$s">
|
||||
%1$s
|
||||
<div class="control">
|
||||
<input class="input" id="%2$s" name="%2$s" type="text" placeholder="" value="%3$s">
|
||||
</div>
|
||||
</div>
|
||||
';
|
||||
return sprintf($string, $string_label, $name, $value, $class, $rows);
|
||||
}
|
||||
|
||||
static function build_field_textarea($label, $name, $value, $class, $rows=10)
|
||||
{
|
||||
$string_label = $label !== '' ? sprintf('<label class="label">%1$s</label>', $label) : '';
|
||||
$string = '
|
||||
<div class="field %4$s">
|
||||
%1$s
|
||||
<div class="control">
|
||||
<textarea class="textarea" id="%2$s" name="%2$s" rows="%5$s">%3$s</textarea>
|
||||
</div>
|
||||
</div>
|
||||
';
|
||||
return sprintf($string, $string_label, $name, $value, $class,$rows);
|
||||
}
|
||||
|
||||
static function build_h_field_textarea($label, $name, $value){
|
||||
$string = '
|
||||
<div class="field is-horizontal">
|
||||
<div class="field-label is-normal">
|
||||
<label class="label">'.$label.'</label>
|
||||
</div>
|
||||
<div class="field-body">
|
||||
<div class="field">
|
||||
<div class="control">
|
||||
<textarea class="textarea" id="'.$name.'" name="'.
|
||||
$name.'">'.$value.'</textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
';
|
||||
return $string;
|
||||
}
|
||||
|
||||
static function build_h_field_input($label, $name, $value){
|
||||
$string = '
|
||||
<div class="field is-horizontal">
|
||||
<div class="field-label is-normal">
|
||||
<label class="label">'.$label.'</label>
|
||||
</div>
|
||||
<div class="field-body">
|
||||
<div class="field">
|
||||
<div class="control">
|
||||
<input class="input" type="text" id="'.$name.'" name="'.
|
||||
$name.'" value="'.
|
||||
$value.'">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
';
|
||||
return $string;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* build_field_select_new
|
||||
*
|
||||
* `<bulma type="H_FIELD_SELECT" label="Priority:" name="priority_id"
|
||||
* options="priorities" option_value="id" option_name="name"
|
||||
* selected="{{@ticket.priority_id}}"></bulma>`
|
||||
*
|
||||
* @param mixed $attr
|
||||
* @return void
|
||||
*/
|
||||
static function build_field_select($attr)
|
||||
{
|
||||
$f3 = \Base::instance();
|
||||
|
||||
$class = $attr['class'] ?? '';
|
||||
$label = $attr['label'] ?? '';
|
||||
$name = $attr['name'] ?? '';
|
||||
// $options_arr = $attr['options'] ?? [];
|
||||
$option_value = $attr['option_value'] ?? 'id';
|
||||
$option_name = $attr['option_name'] ?? 'name';
|
||||
|
||||
$options = \Template::instance()->token($attr['options']);
|
||||
$selected = \Template::instance()->token($attr['selected']);
|
||||
|
||||
// TODO: label - this could be moved into a seperate function
|
||||
$html_label = $label !== '' ? sprintf('<label class="label">%1$s</label>', $label) : '';
|
||||
|
||||
$tmp_options = '<?php echo \BulmaFormHelper::instance()->field_select('.
|
||||
$options.', '.$selected.', "'.$option_value.'", "'.$option_name.'"); ?>';
|
||||
|
||||
$html = '
|
||||
<div class="field %4$s">
|
||||
%1$s
|
||||
<div class="control">
|
||||
<div class="select">
|
||||
<select name="%3$s" id="%3$s">
|
||||
%2$s
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
';
|
||||
|
||||
return sprintf($html, $html_label, $tmp_options, $name, $class);
|
||||
}
|
||||
|
||||
function field_select($options, $selected, $option_value, $option_name){
|
||||
$html_options = '';
|
||||
foreach ($options as $option) {
|
||||
$value = $option[$option_value] ?? '';
|
||||
$text = $option[$option_name] ?? '';
|
||||
$html_selected = ((string)$value === (string)$selected) ? ' selected="selected"' : '';
|
||||
$html_option = '<option value="%s"%s>%s</option>';
|
||||
$html_options .= sprintf($html_option, $value, $html_selected, $text);
|
||||
}
|
||||
echo $html_options;
|
||||
}
|
||||
|
||||
static function build_h_field_select_new($attr)
|
||||
{
|
||||
$f3 = \Base::instance();
|
||||
@ -88,25 +229,7 @@ class BulmaFormHelper extends \Prefab {
|
||||
return $html;
|
||||
}
|
||||
|
||||
static function build_h_field_input($label, $name, $value){
|
||||
$string = '
|
||||
<div class="field is-horizontal">
|
||||
<div class="field-label is-normal">
|
||||
<label class="label">'.$label.'</label>
|
||||
</div>
|
||||
<div class="field-body">
|
||||
<div class="field">
|
||||
<div class="control">
|
||||
<input class="input" type="text" id="'.$name.'" name="'.
|
||||
$name.'" value="'.
|
||||
$value.'">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
';
|
||||
return $string;
|
||||
}
|
||||
|
||||
|
||||
static function build_h_field_select($label, $name, $options, $selected){
|
||||
$opts = json_decode(str_replace("'", '"', $options));
|
||||
@ -139,25 +262,6 @@ class BulmaFormHelper extends \Prefab {
|
||||
return $string;
|
||||
}
|
||||
|
||||
static function build_h_field_textarea($label, $name, $value){
|
||||
$string = '
|
||||
<div class="field is-horizontal">
|
||||
<div class="field-label is-normal">
|
||||
<label class="label">'.$label.'</label>
|
||||
</div>
|
||||
<div class="field-body">
|
||||
<div class="field">
|
||||
<div class="control">
|
||||
<textarea class="textarea" id="'.$name.'" name="'.
|
||||
$name.'">'.$value.'</textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
';
|
||||
return $string;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
\Template::instance()->extend('bulma', 'BulmaFormHelper::render');
|
||||
@ -3,10 +3,17 @@
|
||||
class IconsHelper extends \Prefab {
|
||||
|
||||
static public $status_icons = [
|
||||
'New' => ['fas fa-circle-dot has-text-success', "🆕"],
|
||||
'In Progress' => ['fas fa-circle-play has-text-link', "🔄"],
|
||||
'On Hold' => ['fas fa-pause-circle has-text-warning',"⏸️"],
|
||||
'Completed' => ['fas fa-check-circle has-text-danger', "✅"]
|
||||
'open' => ['fas fa-circle-dot has-text-success', "🆕"],
|
||||
'in_progress' => ['fas fa-circle-play has-text-link', "🔄"],
|
||||
'on_hold' => ['fas fa-pause-circle has-text-warning',"⏸️"],
|
||||
'completed' => ['fas fa-check has-text-danger', "✅"]
|
||||
];
|
||||
|
||||
static public $status_names = [
|
||||
'open' => 'Open',
|
||||
'in_progress' => 'In Progress',
|
||||
'on_hold' => 'On Hold',
|
||||
'completed' => 'Completed'
|
||||
];
|
||||
|
||||
static public $priority_icons = [
|
||||
@ -38,6 +45,9 @@ class IconsHelper extends \Prefab {
|
||||
|
||||
static function do_the_switch($type, $value){
|
||||
|
||||
if($value !== null) {
|
||||
$value = str_replace(' ', '_', strtolower($value));
|
||||
}
|
||||
$icon_class = '';
|
||||
switch(strtolower($type)){
|
||||
case 'status':
|
||||
|
||||
@ -2,27 +2,65 @@
|
||||
|
||||
class ParsedownTableExtension extends Parsedown
|
||||
{
|
||||
protected function blockTable($Line, ?array $Block = null)
|
||||
protected function blockTable($Line, array $Block = null)
|
||||
{
|
||||
// Let Parsedown do its normal 'start-of-table' parsing.
|
||||
$Block = parent::blockTable($Line, $Block);
|
||||
if(!isset($Block)){
|
||||
|
||||
// If this line didn't create or start a table, do nothing.
|
||||
if (!isset($Block)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// add classes to the table element
|
||||
$Block['element']['attributes'] = [
|
||||
'class' => 'table is-bordered',
|
||||
];
|
||||
// Flag it so we know in blockTableComplete that this is a table block.
|
||||
$Block['isMyTable'] = true;
|
||||
|
||||
// wrap the table in a bulma div
|
||||
$Block['element'] = [
|
||||
return $Block;
|
||||
}
|
||||
|
||||
protected function blockTableContinue($Line, array $Block)
|
||||
{
|
||||
// Continue letting Parsedown do its normal table parsing.
|
||||
$Block = parent::blockTableContinue($Line, $Block);
|
||||
return $Block;
|
||||
}
|
||||
|
||||
protected function blockTableComplete(array $Block)
|
||||
{
|
||||
// Let Parsedown finalize the table structure.
|
||||
// $Block = parent::blockTableComplete($Block);
|
||||
// If we flagged this as our table block, wrap it now.
|
||||
if (!empty($Block['isMyTable'])) {
|
||||
// $Block['element'] should now be fully formed, e.g.:
|
||||
// [
|
||||
// 'name' => 'table',
|
||||
// 'handler' => 'elements',
|
||||
// 'text' => [ ... ],
|
||||
// 'attributes' => [...],
|
||||
// ]
|
||||
|
||||
// Add your custom class to the <table> itself:
|
||||
if (!isset($Block['element']['attributes'])) {
|
||||
$Block['element']['attributes'] = [];
|
||||
}
|
||||
$Block['element']['attributes']['class'] = 'table is-bordered';
|
||||
|
||||
// Wrap the <table> in a <div class="table-container">:
|
||||
$wrapped = [
|
||||
'name' => 'div',
|
||||
'attributes' => [
|
||||
'class' => 'table-container'
|
||||
'class' => 'table-container',
|
||||
],
|
||||
'handler' => 'elements',
|
||||
'text' => [
|
||||
$Block['element'], // the <table> itself
|
||||
],
|
||||
'handler' => 'element',
|
||||
'text' => $Block['element'],
|
||||
];
|
||||
|
||||
// Replace the original element with our wrapped version:
|
||||
$Block['element'] = $wrapped;
|
||||
}
|
||||
|
||||
return $Block;
|
||||
}
|
||||
}
|
||||
79
app/models/Tag.php
Normal file
79
app/models/Tag.php
Normal 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);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@ -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');
|
||||
|
||||
2
public/css/main.min.css
vendored
2
public/css/main.min.css
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
73
public/js/markdown_preview.js
Normal file
73
public/js/markdown_preview.js
Normal file
@ -0,0 +1,73 @@
|
||||
class TabSwitcherController {
|
||||
constructor({ tabSelector, contentPrefix, textareaSelector, previewUrl }) {
|
||||
this.tabSelector = tabSelector;
|
||||
this.contentPrefix = contentPrefix;
|
||||
this.textareaSelector = textareaSelector;
|
||||
this.previewUrl = previewUrl;
|
||||
|
||||
this.tabParent = document.querySelector(tabSelector);
|
||||
this.tabLinks = this.tabParent.querySelectorAll('a');
|
||||
|
||||
this.init();
|
||||
}
|
||||
|
||||
init() {
|
||||
this.tabLinks.forEach(link => {
|
||||
link.addEventListener('click', (e) => this.handleTabClick(e, link));
|
||||
});
|
||||
}
|
||||
|
||||
async handleTabClick(e, link) {
|
||||
e.preventDefault();
|
||||
const selectedTab = link.getAttribute('data-tab');
|
||||
|
||||
// Update active tab
|
||||
this.tabParent.querySelectorAll('li').forEach(li => li.classList.remove('is-active'));
|
||||
link.parentElement.classList.add('is-active');
|
||||
|
||||
// Show active content
|
||||
document.querySelectorAll('.tab-content').forEach(el => el.style.display = 'none');
|
||||
const activeContent = document.getElementById(`${this.contentPrefix}-${selectedTab}`);
|
||||
if (activeContent) activeContent.style.display = '';
|
||||
|
||||
if (selectedTab === 'preview') {
|
||||
await this.loadPreview();
|
||||
}
|
||||
}
|
||||
|
||||
async loadPreview() {
|
||||
const previewTarget = document.getElementById('preview-output');
|
||||
if (!previewTarget) return;
|
||||
|
||||
previewTarget.innerHTML = `
|
||||
<div class="skeleton-lines">
|
||||
<div></div><div></div><div></div><div></div><div></div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 500));
|
||||
|
||||
const textarea = document.querySelector(this.textareaSelector);
|
||||
const markdown = textarea ? textarea.value : '';
|
||||
|
||||
const res = await fetch(this.previewUrl, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||
body: `content=${encodeURIComponent(markdown)}`
|
||||
});
|
||||
|
||||
const html = await res.text();
|
||||
previewTarget.innerHTML = html;
|
||||
}
|
||||
}
|
||||
|
||||
// Usage
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
new TabSwitcherController({
|
||||
tabSelector: '.tabs',
|
||||
contentPrefix: 'tab',
|
||||
textareaSelector: '#description',
|
||||
previewUrl: '/parsedown/preview'
|
||||
});
|
||||
});
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -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 /*
|
||||
<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">
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<html data-theme="{{ @SESSION.theme ?? 'light' }}" lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
@ -52,6 +52,13 @@
|
||||
<div class="navbar-end">
|
||||
<div class="navbar-item">
|
||||
<div class="buttons">
|
||||
<form id="theme-toggle-form" method="post" action="/toggle-theme" style="display:inline">
|
||||
<button type="submit" id="theme-toggle-button" class="button is-small" aria-label="Toggle Theme">
|
||||
<span class="icon">
|
||||
<i class="fas fa-circle-half-stroke" id="theme-icon"></i>
|
||||
</span>
|
||||
</button>
|
||||
</form>
|
||||
<check if="{{ isset(@SESSION.user) }}">
|
||||
<true>
|
||||
<a class="button is-primary" href="/logout">Log Out</a>
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
<div class="box">
|
||||
<div class="content">
|
||||
<h2 class="title">Attachments</h2>
|
||||
<h4 class="title is-4">Attachments</h4>
|
||||
<div class="block">
|
||||
<check if="isset( {{@attachments }})">
|
||||
<check if="count({{@attachments}}) > 0">
|
||||
@ -37,7 +37,6 @@
|
||||
</check>
|
||||
</check>
|
||||
<div class="block">
|
||||
<h3 class="title">Upload attachment</h3>
|
||||
<form action="/ticket/{{@PARAMS.id}}/attachments/upload" method="POST" enctype="multipart/form-data">
|
||||
<div class="field has-addons">
|
||||
<div class="control has-icons-left"><!-- is-expanded -->
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
<hr>
|
||||
<div class="box" id="comments">
|
||||
<h2 class="title">Comments</h2>
|
||||
<h3 class="title is-3">Comments</h3>
|
||||
<check if="{{ !empty(@comments) }}">
|
||||
<div class="list">
|
||||
<repeat group="{{ @comments }}" value="{{ @comment}}">
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -1,60 +1,141 @@
|
||||
<h1 class="title">Edit Ticket Form</h1>
|
||||
|
||||
<!-- Ticket - Edit -->
|
||||
<!-- made to look more in line with view-->
|
||||
<form action="/ticket/{{ @PARAMS.id }}/update" method="POST">
|
||||
<div class="is-flex">
|
||||
<div class="is-flex-grow-1">
|
||||
<bulma type="FIELD_INPUT" name="title" value="{{@ticket.title}}" class="mr-3"></bulma>
|
||||
</div>
|
||||
<div class="field is-grouped">
|
||||
<!-- <p class="control"><a class="button" href="/ticket/{{ @ticket.id}}/edit">edit ticket</a></p>
|
||||
<p class="control"><a class="button is-primary" href="/ticket/create">new ticket</a></p>-->
|
||||
<div class="control">
|
||||
<a class="button is-secondary" href="/ticket/{{ @ticket.id }}">Cancel</a>
|
||||
</div>
|
||||
<div class="control">
|
||||
<button class="button is-primary" type="submit">Save</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<bulma type="H_FIELD_INPUT" label="Title:" name="title" value="{{@ticket.title}}"></bulma>
|
||||
<bulma type="H_FIELD_INPUT" label="Created At:" name="created_at" value="{{@ticket.created_at}}"></bulma>
|
||||
<bulma type="H_FIELD_TEXTAREA" label="Description:" name="description" value="{{@ticket.description}}"></bulma>
|
||||
<hr>
|
||||
<div class="block">
|
||||
|
||||
<div class="columns">
|
||||
<div class="column is-two-thirds">
|
||||
|
||||
<bulma type="H_FIELD_SELECT_NEW" label="Priority:" name="priority_id"
|
||||
options="priorities" option_value="id" option_name="name"
|
||||
<div class="block">
|
||||
<bulma type="FIELD_INPUT" label="Created At:" name="created_at" value="{{@ticket.created_at}}"></bulma>
|
||||
</div>
|
||||
|
||||
<div class="tabs">
|
||||
<ul>
|
||||
<li class="is-active">
|
||||
<a data-tab="write"><span class="icon is-small">
|
||||
<i class="fas fa-pen" aria-hidden="true"></i></span><span>Write</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a data-tab="preview"><span class="icon is-small">
|
||||
<i class="fas fa-magnifying-glass" aria-hidden="true"></i></span><span>Preview</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div id="tab-write" class="tab-content block">
|
||||
<bulma type="FIELD_TEXTAREA" name="description" value="{{@ticket.description}}" rows="20"></bulma>
|
||||
</div>
|
||||
<div id="tab-preview" class="tab-content content">
|
||||
<div id="preview-output"></div>
|
||||
</div>
|
||||
</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="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>
|
||||
|
||||
<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">
|
||||
<h3 class="title is-5">Custom Fields</h3>
|
||||
<!-- existing fields-->
|
||||
<table class="table is-bordered is-fullwidth">
|
||||
<thead>
|
||||
<tr><th class="has-width-100">Property</th><th>Value</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<repeat group="{{ @ticket }}" key="{{ @key }}" value="{{ @value }}">
|
||||
<check if="{{ @key !== 'description'}}">
|
||||
<tr><td>{{@key}}</td> <td>{{@value}}</td></tr>
|
||||
</check>
|
||||
</repeat>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!-- form to add child ticket relationships -->
|
||||
<div class="box">
|
||||
<h4 class="title is-4">Linked Tickets</h4>
|
||||
<!-- parent -->
|
||||
<?php
|
||||
/*
|
||||
<check if="{{ @parent_tickets }}">
|
||||
<div class="block">
|
||||
<repeat group="{{ @ticket_meta }}" value="{{ @m }}">
|
||||
<div class="field is-grouped is-grouped-right">
|
||||
<input type="hidden" name="meta_id[]" value=" {{ @m.id }}">
|
||||
<div class="control">
|
||||
<label class="label">Key:</label>
|
||||
<input class="input" type="text" name="meta_key[]" value="{{ @m.meta_key }}"
|
||||
placeholder="eg. Department">
|
||||
</div>
|
||||
<div class="control">
|
||||
<label class="label">Value:</label>
|
||||
<input class="input" type="text" name="meta_value[]" value="{{ @m.meta_value }}"
|
||||
placeholder="eg. Finance">
|
||||
</div>
|
||||
<h4 class="title">Parent Tickets</h4>
|
||||
<ul>
|
||||
<repeat group="{{ @parent_tickets }}" value="{{ @p }}">
|
||||
<li><a href="/ticket/{{ @p.id }}">{{ @p.title }}</a></li>
|
||||
</repeat>
|
||||
</ul>
|
||||
</div>
|
||||
</check>
|
||||
<!-- child tickets -->
|
||||
<check if="{{ @child_tickets }}">
|
||||
<div class="block">
|
||||
<h4 class="title">Child Tickets</h4>
|
||||
<ul>
|
||||
<repeat group="{{ @child_tickets }}" value="{{ @c }}">
|
||||
<li><a href="/ticket/{{ @c.id }}">{{ @c.title }}</a></li>
|
||||
</repeat>
|
||||
</div>
|
||||
<hr>
|
||||
<!-- adding new custom meta -->
|
||||
<div class="block">
|
||||
<div class="field is-grouped is-grouped-right">
|
||||
<div class="control">
|
||||
<label class="label">Key:</label>
|
||||
<input class="input" type="text" name="meta_key[]"
|
||||
placeholder="eg. Department">
|
||||
</div>
|
||||
<div class="control">
|
||||
<label class="label">Value:</label>
|
||||
<input class="input" type="text" name="meta_value[]"
|
||||
placeholder="eg. Finance">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</check>
|
||||
|
||||
<button class="button is-primary" type="submit">Save Ticket</button>
|
||||
<form action="/ticket/{{ @ticket.id }}/add-subtask" method="POST">
|
||||
<div class="field">
|
||||
<label class="label">Add existing ticket as child ticket (ID):</label>
|
||||
<div class="control">
|
||||
<input class="input" type="number" placeholder="Child Ticket ID" required
|
||||
name="child_ticket_id">
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<div class="control">
|
||||
<button class="button is-link" type="submit">Link</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
*/ ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<hr>
|
||||
|
||||
<div>
|
||||
|
||||
|
||||
|
||||
<include href="../ui/views/attachment/index.html">
|
||||
<include href="../ui/views/comments/view.html">
|
||||
|
||||
|
||||
<!--
|
||||
<div class="block" id="attachments"></div>
|
||||
<div class="block" id="comments"></div>
|
||||
-->
|
||||
|
||||
</div>
|
||||
|
||||
60
ui/views/ticket/edit.html.v1
Normal file
60
ui/views/ticket/edit.html.v1
Normal file
@ -0,0 +1,60 @@
|
||||
<h1 class="title">Edit Ticket Form</h1>
|
||||
|
||||
<form action="/ticket/{{ @PARAMS.id }}/update" method="POST">
|
||||
|
||||
<bulma type="H_FIELD_INPUT" label="Title:" name="title" value="{{@ticket.title}}"></bulma>
|
||||
<bulma type="H_FIELD_INPUT" label="Created At:" name="created_at" value="{{@ticket.created_at}}"></bulma>
|
||||
<bulma type="H_FIELD_TEXTAREA" label="Description:" name="description" value="{{@ticket.description}}"></bulma>
|
||||
|
||||
|
||||
<bulma type="H_FIELD_SELECT_NEW" label="Priority:" name="priority_id"
|
||||
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>
|
||||
|
||||
<div class="block">
|
||||
<h3 class="title is-5">Custom Fields</h3>
|
||||
<!-- existing fields-->
|
||||
<div class="block">
|
||||
<repeat group="{{ @ticket_meta }}" value="{{ @m }}">
|
||||
<div class="field is-grouped is-grouped-right">
|
||||
<input type="hidden" name="meta_id[]" value=" {{ @m.id }}">
|
||||
<div class="control">
|
||||
<label class="label">Key:</label>
|
||||
<input class="input" type="text" name="meta_key[]" value="{{ @m.meta_key }}"
|
||||
placeholder="eg. Department">
|
||||
</div>
|
||||
<div class="control">
|
||||
<label class="label">Value:</label>
|
||||
<input class="input" type="text" name="meta_value[]" value="{{ @m.meta_value }}"
|
||||
placeholder="eg. Finance">
|
||||
</div>
|
||||
</div>
|
||||
</repeat>
|
||||
</div>
|
||||
<hr>
|
||||
<!-- adding new custom meta -->
|
||||
<div class="block">
|
||||
<div class="field is-grouped is-grouped-right">
|
||||
<div class="control">
|
||||
<label class="label">Key:</label>
|
||||
<input class="input" type="text" name="meta_key[]"
|
||||
placeholder="eg. Department">
|
||||
</div>
|
||||
<div class="control">
|
||||
<label class="label">Value:</label>
|
||||
<input class="input" type="text" name="meta_value[]"
|
||||
placeholder="eg. Finance">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button class="button is-primary" type="submit">Save Ticket</button>
|
||||
</div>
|
||||
</form>
|
||||
@ -2,22 +2,38 @@
|
||||
<include href="/ui/session/error.html"></include>
|
||||
</include>
|
||||
<!-- updated design -- inspiration gitea -->
|
||||
<div class="field is-grouped">
|
||||
<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>
|
||||
<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="field">
|
||||
<div class="control">
|
||||
<p><a class="button is-primary" href="/ticket/create">create ticket</a></p>
|
||||
</div>
|
||||
</div>
|
||||
</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>
|
||||
|
||||
|
||||
@ -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>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user