Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,24 @@
# Help Desk

<p>
<p align="center">
<a href="https://laravel.com"><img alt="Laravel v8.x" src="https://img.shields.io/badge/Laravel-v8.x-FF2D20?style=for-the-badge&logo=laravel"></a>
<a href="https://laravel-livewire.com"><img alt="Livewire v2.x" src="https://img.shields.io/badge/Livewire-v2.x-FB70A9?style=for-the-badge"></a>
<a href="https://filamentphp.com/"><img alt="Filament v2.x" src="https://img.shields.io/badge/Filament-v2.x-e9b228?style=for-the-badge"></a>
<a href="https://php.net"><img alt="PHP 8.0" src="https://img.shields.io/badge/PHP-8.0-777BB4?style=for-the-badge&logo=php"></a>
<br/>
<a href="https://github.com/devaslanphp/help-desk/releases/">
<img src="https://img.shields.io/github/tag/devaslanphp/help-desk?include_prereleases=&sort=semver&color=blue&style=for-the-badge" alt="GitHub tag">
</a>
<a href="#license">
<img src="https://img.shields.io/badge/License-MIT-blue?style=for-the-badge" alt="License">
</a>
<a href="https://github.com/devaslanphp/help-desk/issues">
<img src="https://img.shields.io/github/issues/devaslanphp/help-desk?style=for-the-badge" alt="issues - help-desk">
</a>
<br/>
<a href="http://helpdesk.devaslan.com/docs" title="Go to project documentation">
<img src="https://img.shields.io/badge/view-Documentation-blue?style=for-the-badge" alt="view - Documentation">
</a>
</p>

Help Desk is a Laravel based project, that let you manage your support tickets and communicate with your customers, with
Expand Down
148 changes: 148 additions & 0 deletions app/Http/Livewire/Kanban.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
<?php

namespace App\Http\Livewire;

use App\Jobs\TicketUpdatedJob;
use App\Models\Ticket;
use Filament\Notifications\Notification;
use Illuminate\Support\Collection;
use Illuminate\Support\HtmlString;
use Illuminate\Support\Str;
use InvadersXX\FilamentKanbanBoard\Pages\FilamentKanbanBoard;

class Kanban extends FilamentKanbanBoard
{
protected static ?string $title = '';
public bool $sortable = true;
public bool $sortableBetweenStatuses = true;
public bool $recordClickEnabled = true;

/**
* Statuses list Definition
*
* @return Collection
*/
protected function statuses(): Collection
{
return collect(statuses_list_for_kanban());
}

/**
* Records list definitino
*
* @return Collection
*/
protected function records(): Collection
{
$query = Ticket::query();
$query->withCount('comments');
if (has_all_permissions(auth()->user(), 'view-own-tickets') && !has_all_permissions(auth()->user(), 'view-all-tickets')) {
$query->where(function ($query) {
$query->where('owner_id', auth()->user()->id)
->orWhere('responsible_id', auth()->user()->id);
});
}
return $query->get()
->map(function (Ticket $ticket) {
$priority = config('system.priorities.' . $ticket->priority);
$type = config('system.types.' . $ticket->type);
return [
'id' => $ticket->id,
'title' => new HtmlString('
<div class="w-full flex flex-col space-y-3">
<div class="w-full flex items-center gap-2">
<div title="' . $type['title'] . '" class="text-xs rounded-full w-6 h-6 flex items-center justify-center text-center ' . $type['text-color'] . ' ' . $type['bg-color'] . '">
<i class="fa ' . $type['icon'] . '"></i>
</div>
<div title="' . $priority['title'] . '" class="text-xs rounded-full w-6 h-6 flex items-center justify-center text-center ' . $priority['text-color'] . ' ' . $priority['bg-color'] . '">
<i class="fa ' . $priority['icon'] . '"></i>
</div>
<span class="text-sm font-normal" title="' . $ticket->title . '">' . Str::limit($ticket->title, 15) . '</span>
</div>
<div class="w-full text-xs font-light">
' . Str::limit(htmlspecialchars(strip_tags($ticket->content))) . '
</div>
<div class="w-full flex items-center space-x-4">
<div class="flex items-center gap-1">
'.
($ticket->responsible ? '
<img src="' . $ticket->responsible->avatar_url . '" alt="' . $ticket->responsible->name . '" class="rounded-full shadow" style="width: 20px; height: 20px;" />
<span class="font-light text-xs">' . $ticket->responsible->name . '</span>
' : '<span class="text-xs font-normal text-gray-400">' . __('Not assigned yet!') . '</span>')
.'
</div>
<div class="flex items-center gap-1 text-xs text-gray-500">
' . $ticket->comments_count . '
<i class="fa fa-comment-o"></i>
</div>
</div>
</div>
'),
'status' => $ticket->status,
];
});
}

/**
* Customizing kanban board styles
*
* @return string[]
*/
protected function styles(): array
{
return [
'wrapper' => 'w-full h-full flex space-x-4 overflow-x-auto',
'kanbanWrapper' => 'h-full flex-1',
'kanban' => 'border border-gray-150 flex flex-col h-full rounded',
'kanbanHeader' => 'px-3 py-3 font-bold text-xs w-full border-b border-gray-150',
'kanbanFooter' => '',
'kanbanRecords' => 'space-y-4 p-3 flex-1 overflow-y-auto w-64',
'record' => 'bg-white dark:bg-gray-800 p-4 border border-gray-150 rounded cursor-pointer w-62 hover:bg-gray-50 hover:shadow-lg',
'recordContent' => 'w-full',
];
}

/**
* Event launched when the record status is changed
*
* @param $recordId
* @param $statusId
* @param $fromOrderedIds
* @param $toOrderedIds
* @return void
*/
public function onStatusChanged($recordId, $statusId, $fromOrderedIds, $toOrderedIds): void
{
$ticket = Ticket::find($recordId);
if ((has_all_permissions(auth()->user(), 'update-all-tickets') || (has_all_permissions(auth()->user(), 'update-own-tickets') && ($ticket->owner_id === auth()->user() || $ticket->responsible_id === auth()->user()->id))) && has_all_permissions(auth()->user(), 'change-status-tickets')) {
$before = __(config('system.statuses.' . $ticket->status . '.title')) ?? '-';
$ticket->status = $statusId;
$ticket->save();
Notification::make()
->success()
->title(__('Status updated'))
->body(__('The ticket status has been successfully updated'))
->send();
TicketUpdatedJob::dispatch($ticket, __('Status'), $before, __(config('system.statuses.' . $ticket->status . '.title') ?? '-'));
} else {
Notification::make()
->success()
->title(__('Oops!'))
->body(__("You don't have permissions to change this ticket status"))
->send();
}
}

/**
* Event launched when the record is clicked
*
* @param $recordId
* @return void
*/
public function onRecordClick($recordId): void
{
$ticket = Ticket::find($recordId);
$url = route('tickets.details', ['ticket' => $ticket, 'slug' => Str::slug($ticket->title)]);
$this->dispatchBrowserEvent('open-ticket', ['url' => $url]);
}
}
4 changes: 2 additions & 2 deletions app/Http/Livewire/TicketDetails/Priority.php
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ public function update(): void
public function save(): void
{
$data = $this->form->getState();
$before = config('system.priorities.' . $this->ticket->priority . '.title') ?? '-';
$before = __(config('system.priorities.' . $this->ticket->priority . '.title')) ?? '-';
$this->ticket->priority = $data['priority'];
$this->ticket->save();
Notification::make()
Expand All @@ -79,6 +79,6 @@ public function save(): void
]);
$this->updating = false;
$this->emit('ticketSaved');
TicketUpdatedJob::dispatch($this->ticket, __('Priority'), $before, (config('system.priorities.' . $this->ticket->priority . '.title') ?? '-'));
TicketUpdatedJob::dispatch($this->ticket, __('Priority'), $before, __(config('system.priorities.' . $this->ticket->priority . '.title') ?? '-'));
}
}
4 changes: 2 additions & 2 deletions app/Http/Livewire/TicketDetails/Status.php
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ public function update(): void
public function save(): void
{
$data = $this->form->getState();
$before = config('system.statuses.' . $this->ticket->status . '.title') ?? '-';
$before = __(config('system.statuses.' . $this->ticket->status . '.title')) ?? '-';
$this->ticket->status = $data['status'];
$this->ticket->save();
Notification::make()
Expand All @@ -78,6 +78,6 @@ public function save(): void
]);
$this->updating = false;
$this->emit('ticketSaved');
TicketUpdatedJob::dispatch($this->ticket, __('Status'), $before, (config('system.statuses.' . $this->ticket->status . '.title') ?? '-'));
TicketUpdatedJob::dispatch($this->ticket, __('Status'), $before, __(config('system.statuses.' . $this->ticket->status . '.title') ?? '-'));
}
}
4 changes: 2 additions & 2 deletions app/Http/Livewire/TicketDetails/Type.php
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ public function update(): void
public function save(): void
{
$data = $this->form->getState();
$before = config('system.types.' . $this->ticket->type . '.title') ?? '-';
$before = __(config('system.types.' . $this->ticket->type . '.title')) ?? '-';
$this->ticket->type = $data['type'];
$this->ticket->save();
Notification::make()
Expand All @@ -78,6 +78,6 @@ public function save(): void
]);
$this->updating = false;
$this->emit('ticketSaved');
TicketUpdatedJob::dispatch($this->ticket, __('Type'), $before, (config('system.types.' . $this->ticket->type . '.title') ?? '-'));
TicketUpdatedJob::dispatch($this->ticket, __('Type'), $before, __(config('system.types.' . $this->ticket->type . '.title') ?? '-'));
}
}
6 changes: 6 additions & 0 deletions app/View/Components/MainMenu.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,12 @@ public function __construct()
'always_shown' => false,
'show_notification_indicator' => false
],
'kanban' => [
'title' => 'Kanban Board',
'icon' => 'fa-clipboard-check',
'always_shown' => false,
'show_notification_indicator' => false
],
'administration' => [
'title' => 'Administration',
'icon' => 'fa-cogs',
Expand Down
21 changes: 21 additions & 0 deletions app/helpers.php
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,27 @@ function statuses_list(): array
}
}

if (!function_exists('statuses_list_for_kanban')) {
/**
* Return statuses list as an array for kanban board
*
* @return array
*/
function statuses_list_for_kanban(): array
{
$statuses = [];
foreach (config('system.statuses') as $key => $value) {
$statuses[] = [
'id' => $key,
'title' => __($value['title']),
'text-color' => $value['text-color'],
'bg-color' => $value['bg-color'],
];
}
return $statuses;
}
}

if (!function_exists('priorities_list')) {
/**
* Return priorities list as an array of KEY (priority id) => VALUE (priority title)
Expand Down
5 changes: 3 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
{
"name": "laravel/laravel",
"type": "project",
"description": "The Laravel Framework.",
"keywords": ["framework", "laravel"],
"description": "Help Desk open source project.",
"keywords": ["helpdesk", "laravel", "tailwindcss", "filament-forms", "filament-notifications"],
"license": "MIT",
"require": {
"php": "^8.0.2",
"filament/forms": "^2.15",
"filament/notifications": "^2.15",
"guzzlehttp/guzzle": "^7.2",
"invaders-xx/filament-kanban-board": "^0.2.6",
"laravel/framework": "^9.19",
"laravel/sanctum": "^3.0",
"laravel/tinker": "^2.7",
Expand Down
Loading