updated edit view to match view - to help reduce cognitive load and improve ux. updated BulmaFormHelper to implement FIELD_INPUT and FIELD_TEXTAREA

This commit is contained in:
tp_dhu 2025-03-29 04:27:36 +00:00
parent ba3c93ba19
commit b13cd3448f
7 changed files with 348 additions and 102 deletions

View File

@ -99,6 +99,9 @@ class TicketController extends BaseController implements CRUD {
$this->f3->reroute('/tickets'); $this->f3->reroute('/tickets');
} }
//
$f3->set('js', 'markdown_preview.js');
// dropdowns // dropdowns
$priorities = (new TicketPriority($this->getDB()))->findAll(); $priorities = (new TicketPriority($this->getDB()))->findAll();
$statuses = (new TicketStatus($this->getDB()))->findAll(); $statuses = (new TicketStatus($this->getDB()))->findAll();

View File

@ -6,6 +6,9 @@ class BulmaFormHelper extends \Prefab {
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; const H_FIELD_SELECT_NEW = 4;
const FIELD_INPUT = 10;
const FIELD_TEXTAREA = 11;
static public function render($node) { static public function render($node) {
@ -13,18 +16,19 @@ class BulmaFormHelper extends \Prefab {
$type = strtoupper($attr['type']) ?? null; $type = strtoupper($attr['type']) ?? null;
// all * // all *
$label = $attr['label']; $label = $attr['label'] ?? '';
$name = $attr['name']; $name = $attr['name'] ?? '';
$value = isset($attr['value']) ? $attr['value'] : ''; $value = $attr['value'] ?? '';
$class = $attr['class'] ?? '';
// select // select
$options = isset($attr['options']) ? $attr['options'] : ''; $options = $attr['options'] ?? [];
$selected = isset($attr['selected']) ? $attr['selected'] : ''; $selected = $attr['selected'] ?? [];
// // textarea
$rows = $attr['rows'] ?? '';
$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")){
@ -43,6 +47,12 @@ class BulmaFormHelper extends \Prefab {
case BulmaFormHelper::H_FIELD_SELECT_NEW: case BulmaFormHelper::H_FIELD_SELECT_NEW:
return BulmaFormHelper::build_h_field_select_new($attr); return BulmaFormHelper::build_h_field_select_new($attr);
break; 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;
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;
@ -54,6 +64,73 @@ 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;
}
static function build_h_field_select_new($attr) static function build_h_field_select_new($attr)
{ {
$f3 = \Base::instance(); $f3 = \Base::instance();
@ -88,25 +165,7 @@ class BulmaFormHelper extends \Prefab {
return $html; 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){ static function build_h_field_select($label, $name, $options, $selected){
$opts = json_decode(str_replace("'", '"', $options)); $opts = json_decode(str_replace("'", '"', $options));
@ -139,25 +198,6 @@ class BulmaFormHelper extends \Prefab {
return $string; 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'); \Template::instance()->extend('bulma', 'BulmaFormHelper::render');

View 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'
});
});

View File

@ -1,6 +1,6 @@
<div class="box"> <div class="box">
<div class="content"> <div class="content">
<h2 class="title">Attachments</h2> <h4 class="title is-4">Attachments</h4>
<div class="block"> <div class="block">
<check if="isset( {{@attachments }})"> <check if="isset( {{@attachments }})">
<check if="count({{@attachments}}) > 0"> <check if="count({{@attachments}}) > 0">
@ -37,7 +37,6 @@
</check> </check>
</check> </check>
<div class="block"> <div class="block">
<h3 class="title">Upload attachment</h3>
<form action="/ticket/{{@PARAMS.id}}/attachments/upload" method="POST" enctype="multipart/form-data"> <form action="/ticket/{{@PARAMS.id}}/attachments/upload" method="POST" enctype="multipart/form-data">
<div class="field has-addons"> <div class="field has-addons">
<div class="control has-icons-left"><!-- is-expanded --> <div class="control has-icons-left"><!-- is-expanded -->

View File

@ -1,6 +1,6 @@
<hr> <hr>
<div class="box" id="comments"> <div class="box" id="comments">
<h2 class="title">Comments</h2> <h3 class="title is-3">Comments</h3>
<check if="{{ !empty(@comments) }}"> <check if="{{ !empty(@comments) }}">
<div class="list"> <div class="list">
<repeat group="{{ @comments }}" value="{{ @comment}}"> <repeat group="{{ @comments }}" value="{{ @comment}}">

View File

@ -1,60 +1,131 @@
<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"> <form action="/ticket/{{ @PARAMS.id }}/update" method="POST">
<div class="is-flex">
<bulma type="H_FIELD_INPUT" label="Title:" name="title" value="{{@ticket.title}}"></bulma> <div class="is-flex-grow-1">
<bulma type="H_FIELD_INPUT" label="Created At:" name="created_at" value="{{@ticket.created_at}}"></bulma> <bulma type="FIELD_INPUT" name="title" value="{{@ticket.title}}" class="mr-3"></bulma>
<bulma type="H_FIELD_TEXTAREA" label="Description:" name="description" value="{{@ticket.description}}"></bulma> </div>
<div class="field is-grouped">
<!-- <p class="control"><a class="button" href="/ticket/{{ @ticket.id}}/edit">edit ticket</a></p>
<bulma type="H_FIELD_SELECT_NEW" label="Priority:" name="priority_id" <p class="control"><a class="button is-primary" href="/ticket/create">new ticket</a></p>-->
options="priorities" option_value="id" option_name="name" <div class="control">
selected="{{@ticket.priority_id}}"></bulma> <a class="button is-secondary" href="/ticket/{{ @ticket.id }}">Cancel</a>
<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> </div>
<hr> <div class="control">
<!-- adding new custom meta --> <button class="button is-primary" type="submit">Save</button>
<div class="block"> </div>
<div class="field is-grouped is-grouped-right"> </div>
<div class="control"> </div>
<label class="label">Key:</label>
<input class="input" type="text" name="meta_key[]" <hr>
placeholder="eg. Department"> <div class="block">
</div>
<div class="control"> <div class="columns">
<label class="label">Value:</label> <div class="column is-two-thirds">
<input class="input" type="text" name="meta_value[]"
placeholder="eg. Finance"> <div class="block">
</div> <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">
<!-- meta data -->
<div class="block">
<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">
<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>
</check>
<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>
</div> </div>
<button class="button is-primary" type="submit">Save Ticket</button>
</div> </div>
</form> </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>

View 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>