Compare commits

..

5 Commits

24 changed files with 21928 additions and 46 deletions

3
.gitignore vendored
View File

@ -1,4 +1,5 @@
composer.lock composer.lock
package-lock.json
lib/ lib/
downloads/ downloads/
public/tmp/ public/tmp/
@ -6,3 +7,5 @@ storage/
public/test.php public/test.php
app/config/.env.cfg app/config/.env.cfg
app/.env.cfg app/.env.cfg
app/config/tp_servicedesk.sql
node_modules

View File

@ -61,3 +61,22 @@ POST /project/@id/update=ProjectController->update
GET /users=UserController->index GET /users=UserController->index
GET /user/@id/edit=UserController->editForm GET /user/@id/edit=UserController->editForm
POST /user/@id/update=UserController->update POST /user/@id/update=UserController->update
; admin
GET /admin=Admin\HomeController->index
; admin/priority
GET /admin/priority=Admin\TicketOptionsController->listPriorities
GET /admin/priority/create=Admin\TicketOptionsController->createPriorityForm
POST /admin/priority/create=Admin\TicketOptionsController->createPriority
GET /admin/priority/@id/edit=Admin\TicketController->editPriorityForm
POST /admin/priority/@id/update=Admin\TicketController->updatePriority
GET /admin/priority/@id/delete=Admin\TicketController->deletePriority
; admin/status
GET /admin/status=Admin\TicketOptionsController->listStatuses
GET /admin/status/create=Admin\TicketOptionsController->createStatusForm
POST /admin/status/create=Admin\TicketOptionsController->createStatus
GET /admin/status/@id/edit=Admin\TicketController->editStatusForm
POST /admin/status/@id/update=Admin\TicketController->updateStatus
GET /admin/status/@id/delete=Admin\TicketController->deleteStatus

View File

@ -0,0 +1,11 @@
<?php
namespace Admin;
class HomeController extends \BaseController
{
public function index($f3)
{
$this->renderView('/ui/views/admin/index.html');
}
}

View File

@ -0,0 +1,36 @@
<?php
namespace Admin;
class TicketOptionsController extends \BaseController
{
public function listPriorities()
{
$this->requireLogin();
// TODO: check admin
$model = new \TicketPriority($this->getDB());
$priorities = $model->findAll();
$this->renderView('/ui/views/admin/priorities/index.html', [
'priorities' => $priorities
]);
}
public function createPriorityForm()
{
$this->requireLogin();
$this->renderView('/ui/views/admin/priorities/create.html');
}
public function createPriority()
{
$this->requireLogin();
$p = new \TicketPriority($this->getDB());
$p->name = $this->f3->get('POST.name');
$p->sort_order = $this->f3->get('POST.sort_order');
$p->save();
}
// TODO: editPriorityForm(), updatePriorityForm(), deletePriorityForm()
}

View File

@ -3,16 +3,23 @@
class IconsHelper extends \Prefab { class IconsHelper extends \Prefab {
static public $status_icons = [ static public $status_icons = [
'New' => ['fas fa-plus-circle has-text-warning', "🆕"], 'New' => ['fas fa-circle-dot has-text-success', "🆕"],
'In Progress' => ['fas fa-repeat has-text-link', "🔄"], 'In Progress' => ['fas fa-circle-play has-text-link', "🔄"],
'On Hold' => ['fas fa-pause-circle has-text-danger',"⏸️"], 'On Hold' => ['fas fa-pause-circle has-text-warning',"⏸️"],
'Completed' => ['fas fa-check-circle has-text-success', ""] 'Completed' => ['fas fa-check-circle has-text-danger', ""]
]; ];
static public $priority_icons = [ static public $priority_icons = [
'Low' => ['fas fa-arrow-down',"🟢"], 'Low' => ['fas fa-circle-down',"🟢"],
'Medium' => ['fas fa-minus', "🟡"], 'Medium' => ['fas fa-circle-dot', "🟡"],
'High' => ['fas fa-arrow-up', "🔴"] 'High' => ['fas fa-circle-up', "🔴"]
];
static public $priority_colors = [
'Low' => 'success',
'Medium' => 'warning',
'High' => 'danger',
'' => 'info'
]; ];
static public function icons($node){ static public function icons($node){
@ -38,13 +45,21 @@ class IconsHelper extends \Prefab {
break; break;
case 'priority': case 'priority':
$icon_class = IconsHelper::$priority_icons[$value] ?? ['fas fa-question-circle', "🔲"]; $icon_class = IconsHelper::$priority_icons[$value] ?? ['fas fa-question-circle', "🔲"];
$icon_color = IconsHelper::$priority_colors[$value] ?? 'info';
break; break;
default: default:
$icon_class = 'fas fa-question-circle'; $icon_class = 'fas fa-question-circle';
} }
if($type == 'priority'){
// return '<p class="button"><span class="icon is-small">'
return '<span class="icon is-medium">
<i class="'.$icon_class[0].' fa-lg has-text-'.$icon_color.'"></i>
</span>';
} else {
return '<span class="icon is-medium"><i class="'.$icon_class[0].' fa-lg"></i></span>';
}
return '<span class="is-size-5">'.$icon_class[1].'</span>'; return '<span class="is-size-5">'.$icon_class[1].'</span>';
return '<i class="'.$icon_class[0].' is-size-4"></i>';
} }

View File

@ -11,10 +11,11 @@ class Ticket extends \DB\SQL\Mapper {
public function findAll(): array public function findAll(): array
{ {
return $this->db->exec( return $this->db->exec(
'SELECT t.* , tp.name AS priority_name, ts.name AS status_name 'SELECT t.* , tp.name AS priority_name, ts.name AS status_name, u.display_name
FROM tickets t FROM tickets t
LEFT JOIN ticket_priorities tp ON t.priority_id = tp.id LEFT JOIN ticket_priorities tp ON t.priority_id = tp.id
LEFT JOIN ticket_statuses ts ON t.status_id = ts.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 WHERE t.recycled = 0
ORDER BY t.created_at DESC' ORDER BY t.created_at DESC'
); );

10
package.json Normal file
View File

@ -0,0 +1,10 @@
{
"dependencies": {
"bulma": "^1.0.3"
},
"scripts": {
"sass": "sass scss/main.scss public/css/main.css",
"sass:min": "sass scss/main.scss public/css/main.min.css --style compressed",
"sass:watch": "sass --watch scss/main.scss:public/css/main.css"
}
}

21605
public/css/main.css Normal file

File diff suppressed because it is too large Load Diff

1
public/css/main.css.map Normal file

File diff suppressed because one or more lines are too long

1
public/css/main.min.css vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -8,6 +8,8 @@ i.fa { font-weight: 100 !important ; }
.table th.th-icon { width: 2rem; } .table th.th-icon { width: 2rem; }
#ticket_list .g-flex-item { border-bottom: 1px solid var(--bulma-text-soft); }
/* List Component */ /* List Component */
.list{ .list{
--be-list-color:var(--bulma-text); --be-list-color:var(--bulma-text);

View File

@ -0,0 +1,59 @@
@use "../vendor/bulma";
.ticket-item {
@extend .is-flex;
@extend .mb-1;
@extend .pt-1;
@extend .pb-2;
@extend .is-align-items-flex-start;
border-bottom: 1px solid var(--bulma-text-90);
.ticket-icon {
@extend .mr-2;
display: flex;
align-items: baseline;
.checkbox {
margin-right: 0.5rem;
}
}
.ticket-content {
@extend .is-flex;
@extend .is-flex-direction-column;
@extend .is-flex-grow-1;
align-self: baseline;
.ticket-header {
@extend .is-flex;
@extend .is-justify-content-flex-start;
@extend .is-flex-wrap-wrap;
align-items: center;
.ticket-title {
@extend .title;
@extend .is-5;
@extend .mb-1;
font-weight: normal;
}
.tags {
@extend .ml-2;
}
}
.ticket-meta {
@extend .is-flex;
align-items: center;
flex-wrap: wrap;
gap: 0.25rem;
p {
@extend .subtitle;
@extend .is-6;
font-weight: 300;
margin: 0;
}
}
}
}

5
scss/main.scss Normal file
View File

@ -0,0 +1,5 @@
// import bulma
@use "vendor/bulma";
// import custom components
@use "components/ticket-item";

3
scss/vendor/_bulma-tools.scss vendored Normal file
View File

@ -0,0 +1,3 @@
@use "../../node_modules/bulma/sass/utilities/" as bulma-utils;
@use "../../node_modules/bulma/sass/helpers/" as bulma-helpers;
@use "../../node_modules/bulma/sass/elements/" as bulma-elements;

1
scss/vendor/_bulma.scss vendored Normal file
View File

@ -0,0 +1 @@
@forward "../../node_modules/bulma/bulma";

View File

@ -0,0 +1,34 @@
<div class="ticket-item">
<div class="ticket-icon">
<div class="field">
<label class="b-checkbox checkbox">
<input type="checkbox" value="false" />
<span class="check"></span>
<span class="control-label"></span>
</label>
</div>
<span class="icon is-medium"><icons type="status">{{@ticket.status_name}}</icons></span>
</div>
<div class="ticket-content">
<div class="ticket-header">
<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>
</repeat>
*/ ?>
</div>
</div>
<div class="ticket-meta">
<p>#{{ @ticket.id }} opened {{ @ticket.created_at }} by {{ @ticket.display_name }}</p>
</div>
</div>
</div>

View File

@ -5,14 +5,16 @@
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>TP ServiceDesk</title> <title>TP ServiceDesk</title>
<link rel="stylesheet" href="/style.css">
<link rel="stylesheet" href="/css/main.min.css">
<!-- bulma.io--> <!-- bulma.io-->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@1.0.2/css/bulma.min.css"> <!-- <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@1.0.2/css/bulma.min.css"> -->
<!-- bulma helpers --> <!-- bulma helpers -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bulma-helpers/0.4.3/css/bulma-helpers.min.css" <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bulma-helpers/0.4.3/css/bulma-helpers.min.css"
integrity="sha512-U6ELnUi7oqVEjkLmFw5r5UR5LEtvpImS/jUykBKneVhD0lxZxfJZ3k3pe003ktrtNZYungd9u3Urp2X09wKwXg==" integrity="sha512-U6ELnUi7oqVEjkLmFw5r5UR5LEtvpImS/jUykBKneVhD0lxZxfJZ3k3pe003ktrtNZYungd9u3Urp2X09wKwXg=="
crossorigin="anonymous" referrerpolicy="no-referrer" /> crossorigin="anonymous" referrerpolicy="no-referrer" />
<link rel="stylesheet" href="/style.css"> <!-- bulma-checkbox -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma-checkradio@2.1/dist/css/bulma-checkradio.min.css"> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma-checkbox@1.2.1/css/main.min.css" integrity="sha256-wvxLpriInkhouxrLZ5oo74cJpCtZJkR9bRJwFDvdd4w=" crossorigin="anonymous">
<!-- font awesome --> <!-- font awesome -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.2/css/all.min.css" <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.2/css/all.min.css"
integrity="sha512-Evv84Mr4kqVGRNSgIGL/F/aIDqQb7xQ2vcrdIwxfjThSH8CSR7PBEakCr51Ck+w+/U6swU2Im1vVX0SVk9ABhg==" integrity="sha512-Evv84Mr4kqVGRNSgIGL/F/aIDqQb7xQ2vcrdIwxfjThSH8CSR7PBEakCr51Ck+w+/U6swU2Im1vVX0SVk9ABhg=="

View File

@ -0,0 +1,8 @@
<h1 class="title">Admin</h1>
<include href="/ui/session/error.html">
<hr>
<section>
<p><a href="/admin/priority">Ticket &gt; Priorities</a></p>
<p><a href="/admin/status">Ticket &gt; Statuses</a></p>
</section>

View File

@ -0,0 +1,2 @@
<h1 class="title">Create Ticket Priority</h1>
<p>TODO:</p>

View File

@ -0,0 +1,33 @@
<h1 class="title">Admin: Ticket Priorities</h1>
<include href="/ui/session/error.html">
<p><a class="button" href="/admin/priority/create">create priority</a></p>
<hr>
<table class="table is-fullwidth is-bordered">
<thead>
<tr class="has-background-grey">
<th class="has-text-light">id</th>
<th class="has-text-light">name</th>
<th class="has-text-light">sort_order</th>
<th class="has-text-light"></th>
</tr>
</thead>
<tbody>
<repeat group="{{@priorities}}" value="{{@priority}}">
<tr>
<td>{{@priority.id}}</td>
<td><a href="/admin/priority/{{@priority.id}}">{{@priority.name}}</a></td>
<td>{{@priority.sort_order}}</td>
<td>
<a class="button is-link is-small" href="/admin/priority/{{@priority.id}}/edit">
<i class="fa fa-edit"></i></a>
<a class="button is-danger is-small"
href="/admin/priority/{{@priority.id}}/delete"
onclick="return confirm('Are you sure you want to delete this ticket?');">
<i class="fa fa-trash-can"></i></a>
</td>
</tr>
</repeat>
</tbody>
</table>

View File

@ -1,34 +1,38 @@
<h1 class="title">Tickets</h1> <h1 class="title">Tickets</h1>
<include href="/ui/session/error.html"> <include href="/ui/session/error.html"></include>
<p><a class="button" href="/ticket/create">create ticket</a></p> </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>
<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">
<p><a class="button is-primary" href="/ticket/create">create ticket</a></p>
</div>
</div>
</div>
<hr> <hr>
<table class="table is-fullwidth is-bordered"> <div id="ticket_list">
<thead> <repeat group="{{@tickets}}" value="{{@ticket}}">
<tr class="has-background-warning"> <include href="/ui/partials/ticket_item.html"></include>
<th>id</th><th>title</th> </repeat>
<th>status</th><th>priority</th><th>created_at</th> </div>
<th></th>
</tr>
</thead>
<tbody> <?php
<repeat group="{{@tickets}}" value="{{@ticket}}"> /*
<tr> <div id="ticket_list">
<td>{{@ticket.id}}</td> <repeat group="{{@tickets}}" value="{{@ticket}}">
<td><a href="/ticket/{{@ticket.id}}">{{@ticket.title}}</a></td> <include href="/ui/views/ticket/index_row.html"></include>
<td><icons type="status">{{@ticket.status_name}}</icons></td> </repeat>
<td><icons type="priority">{{@ticket.priority_name}}</icons></td> </div>
<td>{{@ticket.created_at}}</td> */
<td> ?>
<a class="button is-link is-small" href="/ticket/{{@ticket.id}}/edit">
<i class="fa fa-edit"></i></a>
<a class="button is-danger is-small"
href="/ticket/{{@ticket.id}}/delete"
onclick="return confirm('Are you sure you want to delete this ticket?');">
<i class="fa fa-trash-can"></i></a>
</td>
</tr>
</repeat>
</tbody>
</table>

View File

@ -0,0 +1,20 @@
<div class="g-flex-item is-flex is-align-items-flex-start mb-1 pt-1 pb-2">
<div class="g-flex-item-icon is-align-self-baseline mr-2">
<label class="checkbox mr-2"><input type="checkbox"></label>
<span class="icon is-medium"><icons type="status">{{@ticket.status_name}}</icons></span>
</div>
<div class="g-flex-item-main is-flex is-flex-direction-column is-flex-grow-1 is-align-self-baseline">
<div class="g-flex-item-header is-flex is-justify-content-flex-start is-flex-wrap-wrap">
<div class="">
<span class="title is-5 has-text-weight-normal"><a href="/ticket/{{@ticket.id}}">{{@ticket.title}}</a></span>
</div>
<div class="tags ml-2">
<!-- TODO: get tags -->
<span class="tag is-link">tag</span>
</div>
</div>
<div class="g-flex-item-body is-flex is-align-items-centre flex-wrap gap ">
<p class="subtitle is-6 has-text-weight-light">#{{@ticket.id}} opened 2025-03-25 by {{@ticket.display_name}}</p>
</div>
</div>
</div>

View File

@ -1,6 +1,12 @@
<!-- Ticket - View --> <!-- Ticket - View -->
<h1 class="title">{{ @ticket.title }}</h1> <div class="is-flex">
<p><a href="/ticket/{{ @ticket.id}}/edit">edit ticket</a></p> <h1 class="title is-flex-grow-1">{{ @ticket.title }}</h1>
<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>
</div>
<hr> <hr>
<div class="content"> <div class="content">