added support for legacy tickets
This commit is contained in:
@@ -5,6 +5,7 @@ declare(strict_types=1);
|
||||
return [
|
||||
'route_prefix' => env('HELPDESK_ROUTE_PREFIX', 'admin/helpdesk'),
|
||||
'middleware' => ['web', 'auth'],
|
||||
'legacy_connection' => env('HELPDESK_LEGACY_DB_CONNECTION', config('database.default')),
|
||||
'disk' => env('HELPDESK_ATTACHMENTS_DISK', 'local'),
|
||||
'max_upload_kb' => (int) env('HELPDESK_MAX_UPLOAD_KB', 10240),
|
||||
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Modules\Helpdesk\Console;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Database\ConnectionInterface;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Modules\Helpdesk\Models\LegacyCategory;
|
||||
use Modules\Helpdesk\Models\LegacyComment;
|
||||
use Modules\Helpdesk\Models\LegacyTicket;
|
||||
use Throwable;
|
||||
|
||||
final class ImportLegacyHelpdeskCommand extends Command
|
||||
{
|
||||
protected $signature = 'helpdesk:import-legacy {--connection= : Laravel database connection containing the old tickets tables} {--fresh : Clear previously imported legacy records first}';
|
||||
protected $description = 'Import the old Helpdesk tickets and categories into the read-only Legacy System';
|
||||
|
||||
public function handle(): int
|
||||
{
|
||||
$connectionName = (string) ($this->option('connection') ?: config('helpdesk.legacy_connection', config('database.default')));
|
||||
$source = DB::connection($connectionName);
|
||||
$schema = Schema::connection($connectionName);
|
||||
|
||||
foreach (['tickets', 'tickets_categories'] as $table) {
|
||||
if (! $schema->hasTable($table)) {
|
||||
$this->error("Source table {$table} was not found on connection [{$connectionName}].");
|
||||
return self::FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->option('fresh')) {
|
||||
LegacyComment::query()->delete();
|
||||
LegacyTicket::query()->delete();
|
||||
LegacyCategory::query()->delete();
|
||||
}
|
||||
|
||||
$this->importCategories($source);
|
||||
$this->importTickets($source, $schema);
|
||||
$this->importComments($source, $schema);
|
||||
$this->refreshCategoryCounts();
|
||||
|
||||
$this->info(sprintf('Legacy import complete: %d tickets, %d categories, %d comments.', LegacyTicket::count(), LegacyCategory::count(), LegacyComment::count()));
|
||||
return self::SUCCESS;
|
||||
}
|
||||
|
||||
private function importCategories(ConnectionInterface $source): void
|
||||
{
|
||||
$source->table('tickets_categories')->orderBy('id')->chunk(500, function ($rows): void {
|
||||
foreach ($rows as $row) {
|
||||
LegacyCategory::updateOrCreate(['legacy_id' => $row->id], ['name' => (string) $row->name, 'color' => $row->color ?? null]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private function importTickets(ConnectionInterface $source, $schema): void
|
||||
{
|
||||
$columns = collect($schema->getColumnListing('tickets'));
|
||||
$lookups = [
|
||||
'category' => $this->lookup($source, $schema, 'tickets_categories'),
|
||||
'status' => $this->lookup($source, $schema, 'tickets_statuses'),
|
||||
'priority' => $this->lookup($source, $schema, 'tickets_priorities'),
|
||||
'project' => $this->lookup($source, $schema, 'projects'),
|
||||
'user' => $this->lookup($source, $schema, 'users'),
|
||||
];
|
||||
|
||||
$source->table('tickets')->orderBy('id')->chunk(250, function ($rows) use ($columns, $lookups): void {
|
||||
foreach ($rows as $row) {
|
||||
$data = (array) $row;
|
||||
LegacyTicket::updateOrCreate(['legacy_id' => $row->id], [
|
||||
'reference' => $data['reference'] ?? null,
|
||||
'subject' => (string) ($data['subject'] ?? 'Legacy ticket #'.$row->id),
|
||||
'content' => $data['content'] ?? null,
|
||||
'html_content' => $data['htmlcontent'] ?? null,
|
||||
'legacy_category_id' => $data['category_id'] ?? null,
|
||||
'category_name' => $lookups['category'][$data['category_id'] ?? 0] ?? null,
|
||||
'status_name' => $lookups['status'][$data['status_id'] ?? 0] ?? null,
|
||||
'priority_name' => $lookups['priority'][$data['priority_id'] ?? 0] ?? null,
|
||||
'requester_name' => $lookups['user'][$data['user_id'] ?? 0] ?? null,
|
||||
'agent_name' => $lookups['user'][$data['agent_id'] ?? 0] ?? null,
|
||||
'project_name' => $lookups['project'][$data['project_id'] ?? 0] ?? null,
|
||||
'legacy_created_at' => $data['created_at'] ?? null,
|
||||
'legacy_updated_at' => $data['updated_at'] ?? null,
|
||||
'completed_at' => $data['completed_at'] ?? null,
|
||||
'archived_at' => $data['archived_at'] ?? null,
|
||||
'legacy_data' => $data,
|
||||
]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private function importComments(ConnectionInterface $source, $schema): void
|
||||
{
|
||||
if (! $schema->hasTable('tickets_comments')) return;
|
||||
$users = $this->lookup($source, $schema, 'users');
|
||||
$source->table('tickets_comments')->orderBy('id')->chunk(500, function ($rows) use ($users): void {
|
||||
foreach ($rows as $row) {
|
||||
$data = (array) $row;
|
||||
LegacyComment::updateOrCreate(['legacy_id' => $row->id], [
|
||||
'legacy_ticket_id' => $data['ticket_id'],
|
||||
'author_name' => $users[$data['user_id'] ?? 0] ?? null,
|
||||
'content' => $data['content'] ?? null,
|
||||
'html_content' => $data['htmlcontent'] ?? null,
|
||||
'legacy_created_at' => $data['created_at'] ?? null,
|
||||
'legacy_updated_at' => $data['updated_at'] ?? null,
|
||||
]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private function lookup(ConnectionInterface $source, $schema, string $table): array
|
||||
{
|
||||
if (! $schema->hasTable($table)) return [];
|
||||
$columns = $schema->getColumnListing($table);
|
||||
$nameColumn = in_array('name', $columns, true) ? 'name' : (in_array('username', $columns, true) ? 'username' : null);
|
||||
if ($nameColumn === null) return [];
|
||||
try { return $source->table($table)->pluck($nameColumn, 'id')->map(fn ($v) => (string) $v)->all(); } catch (Throwable) { return []; }
|
||||
}
|
||||
|
||||
private function refreshCategoryCounts(): void
|
||||
{
|
||||
LegacyCategory::query()->each(function (LegacyCategory $category): void {
|
||||
$category->update(['ticket_count' => LegacyTicket::query()->where('legacy_category_id', $category->legacy_id)->count()]);
|
||||
});
|
||||
}
|
||||
}
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration {
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('helpdesk_legacy_categories', function (Blueprint $table): void {
|
||||
$table->id();
|
||||
$table->unsignedBigInteger('legacy_id')->unique();
|
||||
$table->string('name');
|
||||
$table->string('color', 32)->nullable();
|
||||
$table->unsignedInteger('ticket_count')->default(0);
|
||||
$table->timestamps();
|
||||
});
|
||||
|
||||
Schema::create('helpdesk_legacy_tickets', function (Blueprint $table): void {
|
||||
$table->id();
|
||||
$table->unsignedBigInteger('legacy_id')->unique();
|
||||
$table->string('reference')->nullable()->index();
|
||||
$table->string('subject');
|
||||
$table->longText('content')->nullable();
|
||||
$table->longText('html_content')->nullable();
|
||||
$table->unsignedBigInteger('legacy_category_id')->nullable()->index();
|
||||
$table->string('category_name')->nullable();
|
||||
$table->string('status_name')->nullable();
|
||||
$table->string('priority_name')->nullable();
|
||||
$table->string('requester_name')->nullable();
|
||||
$table->string('agent_name')->nullable();
|
||||
$table->string('project_name')->nullable();
|
||||
$table->timestamp('legacy_created_at')->nullable()->index();
|
||||
$table->timestamp('legacy_updated_at')->nullable();
|
||||
$table->timestamp('completed_at')->nullable();
|
||||
$table->timestamp('archived_at')->nullable();
|
||||
$table->json('legacy_data')->nullable();
|
||||
$table->timestamps();
|
||||
});
|
||||
|
||||
Schema::create('helpdesk_legacy_comments', function (Blueprint $table): void {
|
||||
$table->id();
|
||||
$table->unsignedBigInteger('legacy_id')->unique();
|
||||
$table->unsignedBigInteger('legacy_ticket_id')->index();
|
||||
$table->string('author_name')->nullable();
|
||||
$table->longText('content')->nullable();
|
||||
$table->longText('html_content')->nullable();
|
||||
$table->timestamp('legacy_created_at')->nullable();
|
||||
$table->timestamp('legacy_updated_at')->nullable();
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('helpdesk_legacy_comments');
|
||||
Schema::dropIfExists('helpdesk_legacy_tickets');
|
||||
Schema::dropIfExists('helpdesk_legacy_categories');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Modules\Helpdesk\Http\Controllers;
|
||||
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Illuminate\Http\Request;
|
||||
use Modules\Helpdesk\Models\LegacyCategory;
|
||||
use Modules\Helpdesk\Models\LegacyTicket;
|
||||
|
||||
final class LegacySystemController
|
||||
{
|
||||
public function tickets(Request $request): View
|
||||
{
|
||||
$search = trim((string) $request->query('search', ''));
|
||||
$category = $request->integer('category');
|
||||
|
||||
$tickets = LegacyTicket::query()
|
||||
->when($search !== '', fn ($query) => $query->where(function ($q) use ($search): void {
|
||||
$q->where('reference', 'like', "%{$search}%")
|
||||
->orWhere('subject', 'like', "%{$search}%")
|
||||
->orWhere('requester_name', 'like', "%{$search}%");
|
||||
}))
|
||||
->when($category > 0, fn ($query) => $query->where('legacy_category_id', $category))
|
||||
->orderByDesc('legacy_created_at')
|
||||
->orderByDesc('legacy_id')
|
||||
->paginate(30)
|
||||
->withQueryString();
|
||||
|
||||
return view('helpdesk::legacy.tickets', [
|
||||
'tickets' => $tickets,
|
||||
'categories' => LegacyCategory::query()->orderBy('name')->get(),
|
||||
'search' => $search,
|
||||
'selectedCategory' => $category,
|
||||
]);
|
||||
}
|
||||
|
||||
public function show(LegacyTicket $ticket): View
|
||||
{
|
||||
$ticket->load('comments');
|
||||
return view('helpdesk::legacy.show', compact('ticket'));
|
||||
}
|
||||
|
||||
public function categories(): View
|
||||
{
|
||||
return view('helpdesk::legacy.categories', [
|
||||
'categories' => LegacyCategory::query()->orderBy('name')->paginate(50),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Modules\Helpdesk\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
final class LegacyCategory extends Model
|
||||
{
|
||||
protected $table = 'helpdesk_legacy_categories';
|
||||
protected $guarded = [];
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Modules\Helpdesk\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
final class LegacyComment extends Model
|
||||
{
|
||||
protected $table = 'helpdesk_legacy_comments';
|
||||
protected $guarded = [];
|
||||
protected $casts = ['legacy_created_at' => 'datetime', 'legacy_updated_at' => 'datetime'];
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Modules\Helpdesk\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
final class LegacyTicket extends Model
|
||||
{
|
||||
protected $table = 'helpdesk_legacy_tickets';
|
||||
protected $guarded = [];
|
||||
protected $casts = ['legacy_data' => 'array', 'legacy_created_at' => 'datetime', 'legacy_updated_at' => 'datetime', 'completed_at' => 'datetime', 'archived_at' => 'datetime'];
|
||||
|
||||
public function comments(): HasMany
|
||||
{
|
||||
return $this->hasMany(LegacyComment::class, 'legacy_ticket_id', 'legacy_id')->orderBy('legacy_created_at');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
<x-layouts::app :title="__('Legacy Categories')">
|
||||
<div class="hd-helpdesk-surface hd-mobile-ready space-y-6">
|
||||
<div class="overflow-hidden rounded-3xl border border-zinc-200 bg-white shadow-sm dark:border-zinc-700 dark:bg-zinc-900"><div class="bg-gradient-to-br from-slate-950 via-slate-900 to-blue-950 px-6 py-7 text-white"><p class="text-xs font-semibold uppercase tracking-[0.28em] text-blue-200">Legacy System</p><h1 class="mt-2 text-3xl font-semibold tracking-tight">Legacy Categories</h1><p class="mt-2 text-sm text-blue-100/90">Categories retained from the previous Helpdesk.</p></div></div>
|
||||
<div class="overflow-hidden rounded-2xl border border-zinc-200 bg-white shadow-sm dark:border-zinc-700 dark:bg-zinc-900"><table class="w-full text-left text-sm"><thead class="border-b border-zinc-200 bg-zinc-50 text-xs uppercase text-zinc-500 dark:border-zinc-700 dark:bg-zinc-800/60"><tr><th class="px-5 py-3">Category</th><th class="px-5 py-3">Legacy ID</th><th class="px-5 py-3 text-right">Tickets</th></tr></thead><tbody class="divide-y divide-zinc-200 dark:divide-zinc-800">@forelse($categories as $category)<tr><td class="px-5 py-4"><a class="font-semibold text-blue-600 hover:underline" href="{{ route('helpdesk.legacy.tickets', ['category' => $category->legacy_id]) }}" wire:navigate>{{ $category->name }}</a></td><td class="px-5 py-4 text-zinc-500">{{ $category->legacy_id }}</td><td class="px-5 py-4 text-right">{{ $category->ticket_count }}</td></tr>@empty<tr><td colspan="3" class="px-5 py-10 text-center text-zinc-500">No legacy categories have been imported.</td></tr>@endforelse</tbody></table>@if($categories->hasPages())<div class="border-t border-zinc-200 p-4 dark:border-zinc-700">{{ $categories->links() }}</div>@endif</div>
|
||||
</div>
|
||||
</x-layouts::app>
|
||||
@@ -0,0 +1,6 @@
|
||||
<x-layouts::app :title="$ticket->reference ?: 'Legacy Ticket #'.$ticket->legacy_id">
|
||||
<div class="hd-helpdesk-surface hd-mobile-ready space-y-6">
|
||||
<div class="overflow-hidden rounded-3xl border border-zinc-200 bg-white shadow-sm dark:border-zinc-700 dark:bg-zinc-900"><div class="bg-gradient-to-br from-slate-950 via-slate-900 to-blue-950 px-6 py-7 text-white"><div class="flex items-start justify-between gap-4"><div><p class="text-xs font-semibold uppercase tracking-[0.28em] text-blue-200">Legacy System · Read only</p><h1 class="mt-2 text-3xl font-semibold tracking-tight">{{ $ticket->subject }}</h1><p class="mt-2 text-sm text-blue-100/90">{{ $ticket->reference ?: 'Legacy ticket #'.$ticket->legacy_id }}</p></div><a href="{{ route('helpdesk.legacy.tickets') }}" wire:navigate class="hd-btn hd-btn-blue"><i class="fa-solid fa-arrow-left"></i><span>Legacy Tickets</span></a></div></div></div>
|
||||
<div class="grid gap-6 xl:grid-cols-[1fr_320px]"><div class="space-y-6"><section class="rounded-2xl border border-zinc-200 bg-white p-6 shadow-sm dark:border-zinc-700 dark:bg-zinc-900"><h2 class="mb-4 text-lg font-semibold">Original request</h2><div class="prose max-w-none dark:prose-invert">{!! $ticket->html_content ?: nl2br(e($ticket->content ?: 'No content recorded.')) !!}</div></section>@if($ticket->comments->isNotEmpty())<section class="rounded-2xl border border-zinc-200 bg-white p-6 shadow-sm dark:border-zinc-700 dark:bg-zinc-900"><h2 class="mb-4 text-lg font-semibold">Comments</h2><div class="space-y-4">@foreach($ticket->comments as $comment)<article class="rounded-xl border border-zinc-200 p-4 dark:border-zinc-700"><div class="mb-2 flex justify-between text-xs text-zinc-500"><strong>{{ $comment->author_name ?: 'Legacy user' }}</strong><span>{{ $comment->legacy_created_at?->format('d M Y H:i') }}</span></div><div class="prose max-w-none text-sm dark:prose-invert">{!! $comment->html_content ?: nl2br(e($comment->content ?: '')) !!}</div></article>@endforeach</div></section>@endif</div><aside><div class="rounded-2xl border border-zinc-200 bg-white p-5 shadow-sm dark:border-zinc-700 dark:bg-zinc-900"><h2 class="mb-4 font-semibold">Legacy details</h2><dl class="space-y-3 text-sm">@foreach(['Category'=>$ticket->category_name,'Status'=>$ticket->status_name,'Priority'=>$ticket->priority_name,'Requester'=>$ticket->requester_name,'Agent'=>$ticket->agent_name,'Project'=>$ticket->project_name] as $label=>$value)<div><dt class="text-xs uppercase tracking-wide text-zinc-500">{{ $label }}</dt><dd class="mt-1 font-medium">{{ $value ?: '—' }}</dd></div>@endforeach<div><dt class="text-xs uppercase tracking-wide text-zinc-500">Created</dt><dd class="mt-1 font-medium">{{ $ticket->legacy_created_at?->format('d M Y H:i') ?: '—' }}</dd></div></dl></div></aside></div>
|
||||
</div>
|
||||
</x-layouts::app>
|
||||
@@ -0,0 +1,78 @@
|
||||
<x-layouts::app :title="__('Legacy Tickets')">
|
||||
<div class="hd-helpdesk-surface hd-mobile-ready space-y-6">
|
||||
<div class="overflow-hidden rounded-3xl border border-zinc-200 bg-white shadow-sm dark:border-zinc-700 dark:bg-zinc-900">
|
||||
<div class="bg-gradient-to-br from-slate-950 via-slate-900 to-blue-950 px-6 py-7 text-white">
|
||||
<p class="text-xs font-semibold uppercase tracking-[0.28em] text-blue-200">Legacy System</p>
|
||||
<h1 class="mt-2 text-3xl font-semibold tracking-tight">Legacy Tickets</h1>
|
||||
<p class="mt-2 text-sm text-blue-100/90">Historical tickets from the previous Helpdesk. These records are read-only.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="rounded-2xl border border-zinc-200 bg-white p-4 shadow-sm dark:border-zinc-700 dark:bg-zinc-900">
|
||||
<form class="grid gap-3 md:grid-cols-[1fr_260px_auto]" method="GET">
|
||||
<input class="hd-ticket-search" type="search" name="search" value="{{ $search }}" placeholder="Search reference, subject or requester">
|
||||
<select class="hd-select" name="category"><option value="">All legacy categories</option>@foreach($categories as $category)<option value="{{ $category->legacy_id }}" @selected($selectedCategory === $category->legacy_id)>{{ $category->name }}</option>@endforeach</select>
|
||||
<button class="hd-btn hd-btn-blue" type="submit"><i class="fa-solid fa-magnifying-glass"></i><span>Search</span></button>
|
||||
</form>
|
||||
</div>
|
||||
<div class="overflow-hidden rounded-2xl border border-zinc-200 bg-white shadow-sm dark:border-zinc-700 dark:bg-zinc-900">
|
||||
<div class="overflow-x-auto"><table class="w-full text-left text-sm"><thead class="border-b border-zinc-200 bg-zinc-50 text-xs uppercase text-zinc-500 dark:border-zinc-700 dark:bg-zinc-800/60"><tr><th class="px-5 py-3">Reference</th><th class="px-5 py-3">Subject</th><th class="px-5 py-3">Category</th><th class="px-5 py-3">Status</th><th class="px-5 py-3">Requester</th><th class="px-5 py-3">Created</th></tr></thead><tbody class="divide-y divide-zinc-200 dark:divide-zinc-800">@forelse($tickets as $ticket)
|
||||
<tr class="transition-colors dark:hover:bg-zinc-800/30">
|
||||
<td class="px-5 py-4"><a class="font-semibold text-blue-600 hover:underline" href="{{ route('helpdesk.legacy.tickets.show', $ticket) }}" wire:navigate>{{ $ticket->reference ?: '#'.$ticket->legacy_id }}</a></td><td class="px-5 py-4 font-medium">{{ $ticket->subject }}</td><td class="px-5 py-4">{{ $ticket->category_name ?: '—' }}</td><td class="px-5 py-4">{{ $ticket->status_name ?: '—' }}</td><td class="px-5 py-4">{{ $ticket->requester_name ?: '—' }}</td><td class="px-5 py-4 text-zinc-500">{{ $ticket->legacy_created_at?->format('d M Y H:i') ?: '—' }}</td></tr>@empty<tr><td class="px-5 py-10 text-center text-zinc-500" colspan="6">No legacy tickets have been imported.</td></tr>@endforelse</tbody></table></div>
|
||||
@if($tickets->hasPages())
|
||||
<div class="flex items-center justify-between border-t border-zinc-800 px-5 py-4">
|
||||
|
||||
{{-- Previous --}}
|
||||
@if($tickets->onFirstPage())
|
||||
<span
|
||||
class="inline-flex cursor-not-allowed items-center rounded-lg border border-zinc-700 bg-zinc-900 px-4 py-2 text-sm font-medium text-zinc-600"
|
||||
>
|
||||
<i class="fa-solid fa-chevron-left mr-2 text-xs"></i>
|
||||
Previous
|
||||
</span>
|
||||
@else
|
||||
<a
|
||||
href="{{ $tickets->previousPageUrl() }}"
|
||||
class="inline-flex items-center rounded-lg border border-zinc-700 bg-zinc-900 px-4 py-2 text-sm font-medium text-zinc-300 transition hover:border-zinc-600 hover:bg-zinc-800 hover:text-white"
|
||||
wire:navigate
|
||||
>
|
||||
<i class="fa-solid fa-chevron-left mr-2 text-xs"></i>
|
||||
Previous
|
||||
</a>
|
||||
@endif
|
||||
|
||||
{{-- Page information --}}
|
||||
<div class="text-sm text-zinc-500">
|
||||
Page
|
||||
<span class="font-semibold text-zinc-300">
|
||||
{{ $tickets->currentPage() }}
|
||||
</span>
|
||||
of
|
||||
<span class="font-semibold text-zinc-300">
|
||||
{{ $tickets->lastPage() }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{{-- Next --}}
|
||||
@if($tickets->hasMorePages())
|
||||
<a
|
||||
href="{{ $tickets->nextPageUrl() }}"
|
||||
class="inline-flex items-center rounded-lg border border-zinc-700 bg-zinc-900 px-4 py-2 text-sm font-medium text-zinc-300 transition hover:border-zinc-600 hover:bg-zinc-800 hover:text-white"
|
||||
wire:navigate
|
||||
>
|
||||
Next
|
||||
<i class="fa-solid fa-chevron-right ml-2 text-xs"></i>
|
||||
</a>
|
||||
@else
|
||||
<span
|
||||
class="inline-flex cursor-not-allowed items-center rounded-lg border border-zinc-700 bg-zinc-900 px-4 py-2 text-sm font-medium text-zinc-600"
|
||||
>
|
||||
Next
|
||||
<i class="fa-solid fa-chevron-right ml-2 text-xs"></i>
|
||||
</span>
|
||||
@endif
|
||||
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</x-layouts::app>
|
||||
@@ -5,6 +5,7 @@ declare(strict_types=1);
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use Modules\Helpdesk\Http\Controllers\AttachmentDownloadController;
|
||||
use Modules\Helpdesk\Http\Controllers\HelpdeskController;
|
||||
use Modules\Helpdesk\Http\Controllers\LegacySystemController;
|
||||
use Modules\Helpdesk\Http\Controllers\TicketAttachmentStoreController;
|
||||
use Modules\Helpdesk\Http\Controllers\TicketBoardController;
|
||||
use Modules\Helpdesk\Http\Controllers\TicketCommentDeleteController;
|
||||
@@ -28,6 +29,10 @@ Route::get('/', HelpdeskController::class)->name('index');
|
||||
Route::get('/settings', SettingsManager::class)->name('settings');
|
||||
Route::get('/kanban', KanbanBoard::class)->name('kanban');
|
||||
|
||||
Route::get('/legacy/tickets', [LegacySystemController::class, 'tickets'])->name('legacy.tickets');
|
||||
Route::get('/legacy/tickets/{ticket}', [LegacySystemController::class, 'show'])->name('legacy.tickets.show');
|
||||
Route::get('/legacy/categories', [LegacySystemController::class, 'categories'])->name('legacy.categories');
|
||||
|
||||
Route::get('/tickets', TicketIndex::class)->name('tickets.index');
|
||||
Route::get('/tickets/archived', ArchivedTicketIndex::class)->name('tickets.archived');
|
||||
Route::post('/tickets', TicketStoreController::class)->name('tickets.store');
|
||||
|
||||
+14
-1
@@ -83,7 +83,20 @@ return [
|
||||
Mysql::ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
|
||||
]) : [],
|
||||
],
|
||||
|
||||
'helpdesk_legacy' => [
|
||||
'driver' => 'mysql',
|
||||
'host' => env('LEGACY_DB_HOST', '127.0.0.1'),
|
||||
'port' => env('LEGACY_DB_PORT', '3306'),
|
||||
'database' => env('LEGACY_DB_DATABASE'),
|
||||
'username' => env('LEGACY_DB_USERNAME'),
|
||||
'password' => env('LEGACY_DB_PASSWORD'),
|
||||
'unix_socket' => env('LEGACY_DB_SOCKET', ''),
|
||||
'charset' => 'utf8mb4',
|
||||
'collation' => 'utf8mb4_unicode_ci',
|
||||
'prefix' => '',
|
||||
'strict' => true,
|
||||
'engine' => null,
|
||||
],
|
||||
'pgsql' => [
|
||||
'driver' => 'pgsql',
|
||||
'url' => env('DB_URL'),
|
||||
|
||||
@@ -2476,3 +2476,23 @@ html:not(.dark) .hd-appearance-choice.is-active { background: #fff; color: #1118
|
||||
.hd-account-settings-form { max-width: none; }
|
||||
.hd-ios12 .hd-settings-section-card { width: calc(50% - 8px) !important; min-width: 0 !important; }
|
||||
}
|
||||
|
||||
/* Legacy ticket table hover */
|
||||
.legacy-tickets-table tbody tr {
|
||||
transition: background-color 0.15s ease;
|
||||
}
|
||||
|
||||
.legacy-tickets-table tbody tr:hover,
|
||||
.legacy-tickets-table tbody tr:hover > td {
|
||||
background-color: rgba(255, 255, 255, 0.035) !important;
|
||||
color: inherit !important;
|
||||
}
|
||||
|
||||
.legacy-tickets-table tbody tr:hover td,
|
||||
.legacy-tickets-table tbody tr:hover td:not(:first-child) {
|
||||
color: var(--hd-text-primary, #e5e7eb) !important;
|
||||
}
|
||||
|
||||
.legacy-tickets-table tbody tr:hover a {
|
||||
color: #3b82f6 !important;
|
||||
}
|
||||
|
||||
@@ -143,6 +143,12 @@
|
||||
@if(Route::has('helpdesk.kanban'))<a class="hd-nav-link hd-nav-link--kanban {{ request()->routeIs('helpdesk.kanban') ? 'is-active' : '' }}" href="{{ route('helpdesk.kanban') }}" wire:navigate><svg class="hd-svg-icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M3 4h18v16H3V4zm2 2v12h6V6H5zm8 0v12h6V6h-6z"/></svg><span>Kanban</span></a>@endif
|
||||
@if(Route::has('helpdesk.settings'))<a class="hd-nav-link hd-nav-link--settings {{ request()->routeIs('helpdesk.settings*') ? 'is-active' : '' }}" href="{{ route('helpdesk.settings') }}" wire:navigate><svg class="hd-svg-icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M10.7 2h2.6l.6 2.3c.5.2 1 .5 1.5.9l2.3-.7 1.3 2.3-1.7 1.6c.1.5.2 1.1.2 1.6s-.1 1.1-.2 1.6l1.7 1.6-1.3 2.3-2.3-.7c-.5.4-1 .7-1.5.9l-.6 2.3h-2.6l-.6-2.3c-.5-.2-1-.5-1.5-.9l-2.3.7L5 13.2l1.7-1.6A6 6 0 0 1 6.5 10c0-.5.1-1.1.2-1.6L5 6.8l1.3-2.3 2.3.7c.5-.4 1-.7 1.5-.9L10.7 2zM12 7a3 3 0 1 0 0 6 3 3 0 0 0 0-6z"/></svg><span>Settings</span></a>@endif
|
||||
</div>
|
||||
@if(Route::has('helpdesk.legacy.tickets'))
|
||||
<div class="hd-nav-section"><div class="hd-nav-heading">Legacy System</div>
|
||||
<a class="hd-nav-link {{ request()->routeIs('helpdesk.legacy.tickets*') ? 'is-active' : '' }}" style="--hd-nav-accent:#64748b;--hd-nav-accent-rgb:100,116,139" href="{{ route('helpdesk.legacy.tickets') }}" wire:navigate><i class="fa-solid fa-clock-rotate-left" aria-hidden="true"></i><span>Legacy Tickets</span></a>
|
||||
<a class="hd-nav-link {{ request()->routeIs('helpdesk.legacy.categories') ? 'is-active' : '' }}" style="--hd-nav-accent:#64748b;--hd-nav-accent-rgb:100,116,139" href="{{ route('helpdesk.legacy.categories') }}" wire:navigate><i class="fa-solid fa-folder-tree" aria-hidden="true"></i><span>Legacy Categories</span></a>
|
||||
</div>
|
||||
@endif
|
||||
@if(Route::has('documentation.index') || Route::has('documentation.documents.index'))
|
||||
<div class="hd-nav-section"><div class="hd-nav-heading">Documentation</div>
|
||||
<a class="hd-nav-link hd-nav-link--documents {{ request()->routeIs('documentation.documents.*') || request()->routeIs('documentation.index') ? 'is-active' : '' }}" href="{{ Route::has('documentation.documents.index') ? route('documentation.documents.index') : route('documentation.index') }}" wire:navigate><svg class="hd-svg-icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M6 2h8l4 4v16H6V2zm7 1.8V7h3.2L13 3.8zM9 11v1.5h6V11H9zm0 3.5V16h6v-1.5H9z"/></svg><span>Documents</span></a>
|
||||
|
||||
@@ -143,6 +143,12 @@
|
||||
@if(Route::has('helpdesk.kanban'))<a class="hd-nav-link hd-nav-link--kanban {{ request()->routeIs('helpdesk.kanban') ? 'is-active' : '' }}" href="{{ route('helpdesk.kanban') }}" wire:navigate><svg class="hd-svg-icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M3 4h18v16H3V4zm2 2v12h6V6H5zm8 0v12h6V6h-6z"/></svg><span>Kanban</span></a>@endif
|
||||
@if(Route::has('helpdesk.settings'))<a class="hd-nav-link hd-nav-link--settings {{ request()->routeIs('helpdesk.settings*') ? 'is-active' : '' }}" href="{{ route('helpdesk.settings') }}" wire:navigate><svg class="hd-svg-icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M10.7 2h2.6l.6 2.3c.5.2 1 .5 1.5.9l2.3-.7 1.3 2.3-1.7 1.6c.1.5.2 1.1.2 1.6s-.1 1.1-.2 1.6l1.7 1.6-1.3 2.3-2.3-.7c-.5.4-1 .7-1.5.9l-.6 2.3h-2.6l-.6-2.3c-.5-.2-1-.5-1.5-.9l-2.3.7L5 13.2l1.7-1.6A6 6 0 0 1 6.5 10c0-.5.1-1.1.2-1.6L5 6.8l1.3-2.3 2.3.7c.5-.4 1-.7 1.5-.9L10.7 2zM12 7a3 3 0 1 0 0 6 3 3 0 0 0 0-6z"/></svg><span>Settings</span></a>@endif
|
||||
</div>
|
||||
@if(Route::has('helpdesk.legacy.tickets'))
|
||||
<div class="hd-nav-section"><div class="hd-nav-heading">Legacy System</div>
|
||||
<a class="hd-nav-link {{ request()->routeIs('helpdesk.legacy.tickets*') ? 'is-active' : '' }}" style="--hd-nav-accent:#64748b;--hd-nav-accent-rgb:100,116,139" href="{{ route('helpdesk.legacy.tickets') }}" wire:navigate><i class="fa-solid fa-clock-rotate-left" aria-hidden="true"></i><span>Legacy Tickets</span></a>
|
||||
<a class="hd-nav-link {{ request()->routeIs('helpdesk.legacy.categories') ? 'is-active' : '' }}" style="--hd-nav-accent:#64748b;--hd-nav-accent-rgb:100,116,139" href="{{ route('helpdesk.legacy.categories') }}" wire:navigate><i class="fa-solid fa-folder-tree" aria-hidden="true"></i><span>Legacy Categories</span></a>
|
||||
</div>
|
||||
@endif
|
||||
@if(Route::has('documentation.index') || Route::has('documentation.documents.index'))
|
||||
<div class="hd-nav-section"><div class="hd-nav-heading">Documentation</div>
|
||||
<a class="hd-nav-link hd-nav-link--documents {{ request()->routeIs('documentation.documents.*') || request()->routeIs('documentation.index') ? 'is-active' : '' }}" href="{{ Route::has('documentation.documents.index') ? route('documentation.documents.index') : route('documentation.index') }}" wire:navigate><svg class="hd-svg-icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M6 2h8l4 4v16H6V2zm7 1.8V7h3.2L13 3.8zM9 11v1.5h6V11H9zm0 3.5V16h6v-1.5H9z"/></svg><span>Documents</span></a>
|
||||
|
||||
@@ -143,6 +143,12 @@
|
||||
@if(Route::has('helpdesk.kanban'))<a class="hd-nav-link hd-nav-link--kanban {{ request()->routeIs('helpdesk.kanban') ? 'is-active' : '' }}" href="{{ route('helpdesk.kanban') }}" wire:navigate><i class="fa-solid fa-table-columns" aria-hidden="true"></i><span>Kanban</span></a>@endif
|
||||
@if(Route::has('helpdesk.settings'))<a class="hd-nav-link hd-nav-link--settings {{ request()->routeIs('helpdesk.settings*') ? 'is-active' : '' }}" href="{{ route('helpdesk.settings') }}" wire:navigate><i class="fa-solid fa-gear" aria-hidden="true"></i><span>Settings</span></a>@endif
|
||||
</div>
|
||||
@if(Route::has('helpdesk.legacy.tickets'))
|
||||
<div class="hd-nav-section"><div class="hd-nav-heading">Legacy System</div>
|
||||
<a class="hd-nav-link {{ request()->routeIs('helpdesk.legacy.tickets*') ? 'is-active' : '' }}" style="--hd-nav-accent:#64748b;--hd-nav-accent-rgb:100,116,139" href="{{ route('helpdesk.legacy.tickets') }}" wire:navigate><i class="fa-solid fa-clock-rotate-left" aria-hidden="true"></i><span>Legacy Tickets</span></a>
|
||||
<a class="hd-nav-link {{ request()->routeIs('helpdesk.legacy.categories') ? 'is-active' : '' }}" style="--hd-nav-accent:#64748b;--hd-nav-accent-rgb:100,116,139" href="{{ route('helpdesk.legacy.categories') }}" wire:navigate><i class="fa-solid fa-folder-tree" aria-hidden="true"></i><span>Legacy Categories</span></a>
|
||||
</div>
|
||||
@endif
|
||||
@if(Route::has('documentation.index') || Route::has('documentation.documents.index'))
|
||||
<div class="hd-nav-section"><div class="hd-nav-heading">Documentation</div>
|
||||
<a class="hd-nav-link hd-nav-link--documents {{ request()->routeIs('documentation.documents.*') || request()->routeIs('documentation.index') ? 'is-active' : '' }}" href="{{ Route::has('documentation.documents.index') ? route('documentation.documents.index') : route('documentation.index') }}" wire:navigate><i class="fa-solid fa-file-lines" aria-hidden="true"></i><span>Documents</span></a>
|
||||
|
||||
@@ -370,3 +370,11 @@ promiseReactionJob@[native code] {"url":"https://helpdesk.vm/dashboard","user_ag
|
||||
[2026-09-28 08:46:40] local.INFO: Helpdesk module assets loaded {"url":"https://helpdesk.vm/dashboard","user_agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/154.0.0.0 Safari/537.36 Edg/154.0.0.0","timestamp":"2026-09-28T08:46:39.947Z"}
|
||||
[2026-09-28 08:48:45] local.INFO: Helpdesk module assets loaded {"url":"https://helpdesk.vm/dashboard","user_agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/154.0.0.0 Safari/537.36 Edg/154.0.0.0","timestamp":"2026-09-28T08:48:45.326Z"}
|
||||
[2026-09-28 08:50:56] local.INFO: Helpdesk module assets loaded {"url":"https://helpdesk.vm/dashboard","user_agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/154.0.0.0 Safari/537.36 Edg/154.0.0.0","timestamp":"2026-09-28T08:50:56.465Z"}
|
||||
[2026-09-28 09:25:11] local.INFO: Helpdesk module assets loaded {"url":"https://helpdesk.vm/dashboard","user_agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/154.0.0.0 Safari/537.36 Edg/154.0.0.0","timestamp":"2026-09-28T09:25:11.035Z"}
|
||||
[2026-09-28 09:25:23] local.INFO: Helpdesk module assets loaded {"url":"https://helpdesk.vm/admin/helpdesk/legacy/tickets","user_agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/154.0.0.0 Safari/537.36 Edg/154.0.0.0","timestamp":"2026-09-28T09:25:22.956Z"}
|
||||
[2026-09-28 09:38:32] local.INFO: Helpdesk module assets loaded {"url":"https://helpdesk.vm/admin/helpdesk/legacy/tickets","user_agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/154.0.0.0 Safari/537.36 Edg/154.0.0.0","timestamp":"2026-09-28T09:38:32.132Z"}
|
||||
[2026-09-28 09:39:05] local.INFO: Helpdesk module assets loaded {"url":"https://helpdesk.vm/admin/helpdesk/legacy/tickets?page=2","user_agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/154.0.0.0 Safari/537.36 Edg/154.0.0.0","timestamp":"2026-09-28T09:39:05.417Z"}
|
||||
[2026-09-28 09:39:08] local.INFO: Helpdesk module assets loaded {"url":"https://helpdesk.vm/admin/helpdesk/legacy/tickets?page=3","user_agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/154.0.0.0 Safari/537.36 Edg/154.0.0.0","timestamp":"2026-09-28T09:39:08.531Z"}
|
||||
[2026-09-28 09:42:15] local.INFO: Helpdesk module assets loaded {"url":"https://helpdesk.vm/admin/helpdesk/legacy/tickets","user_agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/154.0.0.0 Safari/537.36 Edg/154.0.0.0","timestamp":"2026-09-28T09:42:15.037Z"}
|
||||
[2026-09-28 09:44:20] local.INFO: Helpdesk module assets loaded {"url":"https://helpdesk.vm/admin/helpdesk/legacy/tickets","user_agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/154.0.0.0 Safari/537.36 Edg/154.0.0.0","timestamp":"2026-09-28T09:44:20.199Z"}
|
||||
[2026-09-28 09:45:34] local.INFO: Helpdesk module assets loaded {"url":"https://helpdesk.vm/admin/helpdesk/legacy/tickets?page=18","user_agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/154.0.0.0 Safari/537.36 Edg/154.0.0.0","timestamp":"2026-09-28T09:45:34.462Z"}
|
||||
|
||||
Reference in New Issue
Block a user