updated design
This commit is contained in:
@@ -17,9 +17,9 @@ final class HelpdeskDashboardWidget extends Component
|
||||
$user = Auth::user();
|
||||
$base = Ticket::query();
|
||||
|
||||
if ($user->isHelpdeskAdmin()) {
|
||||
if ($user->helpdesk_admin ?? false) {
|
||||
// no filter
|
||||
} elseif ($user->isHelpdeskAgent()) {
|
||||
} elseif ($user->helpdesk_agent ?? false) {
|
||||
$base->where(function ($q) use ($user) {
|
||||
$q->where('agent_id', $user->id)
|
||||
->orWhere('user_id', $user->id);
|
||||
@@ -34,7 +34,7 @@ final class HelpdeskDashboardWidget extends Component
|
||||
$closedPercent = $totalCount > 0 ? (int) round(($closedCount / $totalCount) * 100) : 0;
|
||||
$openPercent = $totalCount > 0 ? (int) round(($openCount / $totalCount) * 100) : 0;
|
||||
|
||||
return view('helpdesk::livewire.dashboard-widget', [
|
||||
return view('modules.helpdesk.livewire.dashboard-widget', [
|
||||
'totalCount' => $totalCount,
|
||||
'openCount' => $openCount,
|
||||
'closedCount' => $closedCount,
|
||||
|
||||
@@ -185,13 +185,13 @@ final class KanbanBoard extends Component
|
||||
->get();
|
||||
}
|
||||
|
||||
return view('helpdesk::livewire.kanban-board', [
|
||||
return view('modules.helpdesk.livewire.kanban-board', [
|
||||
'boards' => $boards,
|
||||
'board' => $board,
|
||||
'statuses' => $board?->statuses ?? collect(),
|
||||
'ticketsByStatus' => $this->ticketsByStatus($board),
|
||||
'refreshNonce' => $this->refreshNonce,
|
||||
])->layout('helpdesk::layouts.app.sidebar');
|
||||
])->layout('layouts.app', ['title' => __('Helpdesk Kanban')]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -699,7 +699,7 @@ final class SettingsManager extends Component
|
||||
|
||||
public function render(): View
|
||||
{
|
||||
return view('helpdesk::livewire.settings-manager', [
|
||||
return view('modules.helpdesk.livewire.settings-manager', [
|
||||
'statuses' => $this->orderedStatuses(),
|
||||
'priorities' => $this->orderedPriorities(),
|
||||
'categories' => $this->orderedCategories(),
|
||||
@@ -718,7 +718,7 @@ final class SettingsManager extends Component
|
||||
'tags' => Tag::query()->orderBy('name')->get(),
|
||||
'checklistTemplates' => ChecklistTemplate::query()->orderBy('name')->get(),
|
||||
'colourOptions' => Color::options(),
|
||||
])->layout('helpdesk::layouts.app.sidebar');
|
||||
])->layout('layouts.app', ['title' => __('Helpdesk Settings')]);
|
||||
}
|
||||
|
||||
private function orderedStatuses(): mixed
|
||||
|
||||
@@ -138,7 +138,7 @@ final class TicketCreate extends Component
|
||||
|
||||
public function render(): View
|
||||
{
|
||||
return view('helpdesk::livewire.ticket-create', [
|
||||
return view('modules.helpdesk.livewire.ticket-create', [
|
||||
'boards' => $this->boardOptions(),
|
||||
'priorities' => $this->priorityOptions(),
|
||||
'categories' => $this->categoryOptions(),
|
||||
@@ -146,7 +146,7 @@ final class TicketCreate extends Component
|
||||
'projects' => $this->projectOptions(),
|
||||
'products' => Product::query()->where('is_active', true)->orderBy('name')->get(),
|
||||
'tags' => Tag::query()->orderBy('name')->get(),
|
||||
])->layout('helpdesk::layouts.app.sidebar');
|
||||
])->layout('layouts.app', ['title' => __('Create Helpdesk Ticket')]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -188,7 +188,7 @@ final class TicketEdit extends Component
|
||||
|
||||
public function render(): View
|
||||
{
|
||||
return view('helpdesk::livewire.ticket-edit', [
|
||||
return view('modules.helpdesk.livewire.ticket-edit', [
|
||||
'boards' => KanbanBoard::query()->with('owner:id,name,email')->orderByDesc('is_default')->orderBy('sort_order')->orderBy('name')->get(),
|
||||
'statuses' => Status::query()->orderBy('sort_order')->orderBy('name')->get(),
|
||||
'priorities' => Priority::query()->orderBy('sort_order')->orderBy('name')->get(),
|
||||
@@ -198,7 +198,7 @@ final class TicketEdit extends Component
|
||||
'products' => Product::query()->where('is_active', true)->orderBy('name')->get(),
|
||||
'tags' => Tag::query()->orderBy('name')->get(),
|
||||
'agents' => $this->agentOptions(),
|
||||
])->layout('helpdesk::layouts.app.sidebar');
|
||||
])->layout('layouts.app', ['title' => __('Edit Helpdesk Ticket')]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -101,11 +101,11 @@ final class TicketIndex extends Component
|
||||
|
||||
public function render(): View
|
||||
{
|
||||
return view('helpdesk::livewire.ticket-index', [
|
||||
return view('modules.helpdesk.livewire.ticket-index', [
|
||||
'tickets' => $this->tickets(),
|
||||
'statuses' => Status::query()->orderBy('sort_order')->get(),
|
||||
'priorities' => Priority::query()->orderBy('sort_order')->get(),
|
||||
])->layout('helpdesk::layouts.app.sidebar');
|
||||
])->layout('layouts.app', ['title' => __('Helpdesk Tickets')]);
|
||||
}
|
||||
|
||||
private function resetTicketDeletionState(): void
|
||||
|
||||
@@ -320,12 +320,12 @@ final class TicketShow extends Component
|
||||
|
||||
public function render(): View
|
||||
{
|
||||
return view('helpdesk::livewire.ticket-show', [
|
||||
return view('modules.helpdesk.livewire.ticket-show', [
|
||||
'statuses' => Status::query()->orderBy('sort_order')->get(),
|
||||
'priorities' => Priority::query()->orderBy('sort_order')->get(),
|
||||
'agents' => $this->agentOptions(),
|
||||
'cannedResponses' => CannedResponse::query()->where('is_active', true)->orderBy('sort_order')->orderBy('name')->get(),
|
||||
])->layout('helpdesk::layouts.app.sidebar');
|
||||
])->layout('layouts.app', ['title' => __('Helpdesk Ticket')]);
|
||||
}
|
||||
|
||||
private function normaliseNullableInt(mixed $value): ?int
|
||||
@@ -691,7 +691,7 @@ final class TicketShow extends Component
|
||||
{
|
||||
$user = Auth::user();
|
||||
|
||||
return $user !== null && $user->isHelpdeskAdmin();
|
||||
return $user !== null && (method_exists($user, 'isAdmin') ? $user->isAdmin() : (bool) ($user->helpdesk_admin ?? false));
|
||||
}
|
||||
|
||||
private function buildCommentTree(Collection $grouped, ?int $parentId = null): Collection
|
||||
|
||||
@@ -154,7 +154,7 @@ class TicketTimer extends Component
|
||||
*/
|
||||
public function render()
|
||||
{
|
||||
return view('helpdesk::livewire.ticket-timer');
|
||||
return view('modules.helpdesk.livewire.ticket-timer');
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
* Tailwind scanner to discover module Blade classes.
|
||||
*/
|
||||
@source "../../views/**/*.blade.php";
|
||||
@source "../../../../../resources/themes/admin/views/modules/helpdesk/**/*.blade.php";
|
||||
@source "../../../../../resources/themes/backend/views/modules/helpdesk/**/*.blade.php";
|
||||
@source "../../../Livewire/**/*.php";
|
||||
|
||||
.hd-helpdesk-surface .hd-btn,
|
||||
|
||||
@@ -2,6 +2,8 @@ import { defineConfig } from 'vite';
|
||||
import laravel from 'laravel-vite-plugin';
|
||||
import tailwindcss from '@tailwindcss/vite';
|
||||
|
||||
console.log('[Helpdesk] Running Modules/Helpdesk/vite.config.js');
|
||||
|
||||
export default defineConfig({
|
||||
build: {
|
||||
emptyOutDir: true,
|
||||
@@ -14,6 +16,7 @@ export default defineConfig({
|
||||
laravel({
|
||||
publicDirectory: '../../public',
|
||||
buildDirectory: 'build/helpdesk',
|
||||
hotFile: '../../public/hot-helpdesk',
|
||||
|
||||
input: [
|
||||
'Resources/assets/css/app.css',
|
||||
@@ -23,6 +26,7 @@ export default defineConfig({
|
||||
refresh: [
|
||||
'Resources/views/**',
|
||||
'Livewire/**',
|
||||
'../../resources/themes/backend/views/modules/helpdesk/**',
|
||||
],
|
||||
}),
|
||||
],
|
||||
|
||||
+4
-2
@@ -3,8 +3,10 @@
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "vite build",
|
||||
"dev": "vite"
|
||||
"build": "npm run build:backend && npm run build:helpdesk",
|
||||
"build:backend": "vite build --config ./vite.config.js",
|
||||
"build:helpdesk": "npm --prefix Modules/Helpdesk ci && npm --prefix Modules/Helpdesk run build",
|
||||
"dev": "vite --config ./vite.config.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@laravel/passkeys": "^0.2.0",
|
||||
|
||||
@@ -2,13 +2,11 @@
|
||||
@import '../../../../../vendor/livewire/flux/dist/flux.css';
|
||||
@import './helpdesk.css';
|
||||
|
||||
@source '../../views';
|
||||
@source '../../views/modules/helpdesk/**/*.blade.php';
|
||||
@source '../../../../../Modules/Helpdesk/Livewire/**/*.php';
|
||||
@source '../../../**/*.blade.php';
|
||||
@source '../../views/**/*.blade.php';
|
||||
@source '../../../../../Modules/Helpdesk/**/*.php';
|
||||
@source '../../../../../vendor/laravel/framework/src/Illuminate/Pagination/resources/views/*.blade.php';
|
||||
@source '../../../../../vendor/livewire/flux-pro/stubs/**/*.blade.php';
|
||||
@source '../../../../../vendor/livewire/flux/stubs/**/*.blade.php';
|
||||
@source '../../../../../vendor/livewire/flux-pro/stubs/**/*.blade.php';
|
||||
|
||||
@custom-variant dark (&:where(.dark, .dark *));
|
||||
|
||||
@@ -41,7 +39,6 @@
|
||||
}
|
||||
|
||||
@layer base {
|
||||
|
||||
*,
|
||||
::after,
|
||||
::before,
|
||||
@@ -56,7 +53,7 @@
|
||||
}
|
||||
|
||||
[data-flux-label] {
|
||||
@apply !mb-0 !leading-tight;
|
||||
@apply !mb-0 !leading-tight;
|
||||
}
|
||||
|
||||
input:focus[data-flux-control],
|
||||
@@ -64,7 +61,3 @@ textarea:focus[data-flux-control],
|
||||
select:focus[data-flux-control] {
|
||||
@apply outline-hidden ring-2 ring-accent ring-offset-2 ring-offset-accent-foreground;
|
||||
}
|
||||
|
||||
/* \[:where(&)\]:size-4 {
|
||||
@apply size-4;
|
||||
} */
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
@props([
|
||||
'title',
|
||||
'items',
|
||||
'formPrefix',
|
||||
'saveMethod',
|
||||
'editMethod',
|
||||
'deleteMethod',
|
||||
'resetMethod',
|
||||
'nameField' => 'name',
|
||||
])
|
||||
|
||||
<section class="grid gap-6 xl:grid-cols-[22rem_minmax(0,1fr)]">
|
||||
<form wire:submit="{{ $saveMethod }}" class="space-y-4 rounded-2xl border border-zinc-200 bg-white p-5 dark:border-zinc-700 dark:bg-zinc-900">
|
||||
<flux:heading size="lg">{{ __($title) }}</flux:heading>
|
||||
|
||||
<flux:input wire:model="{{ $formPrefix }}.{{ $nameField }}" :label="__('Name')" required />
|
||||
|
||||
@if ($nameField !== 'slug')
|
||||
<flux:input wire:model="{{ $formPrefix }}.slug" :label="__('Slug')" />
|
||||
@endif
|
||||
|
||||
<div class="flex gap-2">
|
||||
<flux:button type="submit" variant="primary">{{ __('Save') }}</flux:button>
|
||||
<flux:button type="button" wire:click="{{ $resetMethod }}">{{ __('Reset') }}</flux:button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="overflow-hidden rounded-2xl border border-zinc-200 bg-white dark:border-zinc-700 dark:bg-zinc-900">
|
||||
<div class="border-b border-zinc-200 px-5 py-4 dark:border-zinc-700">
|
||||
<flux:heading size="sm">{{ __($title) }}</flux:heading>
|
||||
</div>
|
||||
<div class="divide-y divide-zinc-100 dark:divide-zinc-800">
|
||||
@forelse ($items as $item)
|
||||
<div class="flex items-center justify-between gap-4 px-5 py-3">
|
||||
<div class="min-w-0">
|
||||
<div class="truncate font-medium">{{ data_get($item, $nameField) }}</div>
|
||||
@if (data_get($item, 'slug'))<div class="truncate text-xs text-zinc-500">{{ data_get($item, 'slug') }}</div>@endif
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<flux:button size="sm" type="button" wire:click="{{ $editMethod }}({{ $item->id }})">{{ __('Edit') }}</flux:button>
|
||||
<flux:button size="sm" variant="danger" type="button" wire:click="{{ $deleteMethod }}({{ $item->id }})" wire:confirm="{{ __('Delete this item?') }}">{{ __('Delete') }}</flux:button>
|
||||
</div>
|
||||
</div>
|
||||
@empty
|
||||
<div class="px-5 py-10 text-center text-sm text-zinc-500">{{ __('No items yet.') }}</div>
|
||||
@endforelse
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -0,0 +1,7 @@
|
||||
@props([
|
||||
'title' => null,
|
||||
])
|
||||
|
||||
<x-layouts::app :title="$title" {{ $attributes }}>
|
||||
{{ $slot }}
|
||||
</x-layouts::app>
|
||||
@@ -0,0 +1,172 @@
|
||||
<div class="hd-helpdesk-surface space-y-6" wire:key="helpdesk-kanban-{{ $refreshNonce }}">
|
||||
<div class="flex flex-col gap-4 lg:flex-row lg:items-end lg:justify-between">
|
||||
<div>
|
||||
<flux:heading size="xl">{{ __('Helpdesk Kanban') }}</flux:heading>
|
||||
<flux:text class="mt-1">
|
||||
{{ __('Move tickets through the workflow for the selected board.') }}
|
||||
</flux:text>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap items-end gap-3">
|
||||
@if ($boards->isNotEmpty())
|
||||
<div class="min-w-64">
|
||||
<flux:select
|
||||
wire:model.live="board_id"
|
||||
:label="__('Board')"
|
||||
>
|
||||
@foreach ($boards as $boardOption)
|
||||
<option value="{{ $boardOption->id }}">
|
||||
{{ $boardOption->name }}
|
||||
@if ($boardOption->is_default)
|
||||
— {{ __('Default') }}
|
||||
@endif
|
||||
</option>
|
||||
@endforeach
|
||||
</flux:select>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<flux:button
|
||||
:href="route('helpdesk.tickets.index')"
|
||||
wire:navigate
|
||||
>
|
||||
{{ __('Ticket list') }}
|
||||
</flux:button>
|
||||
|
||||
<flux:button
|
||||
variant="primary"
|
||||
:href="route('helpdesk.tickets.create')"
|
||||
wire:navigate
|
||||
>
|
||||
{{ __('New ticket') }}
|
||||
</flux:button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (session('success'))
|
||||
<div class="rounded-xl border border-emerald-300 bg-emerald-50 px-4 py-3 text-sm font-medium text-emerald-800 dark:border-emerald-800 dark:bg-emerald-950/40 dark:text-emerald-200">
|
||||
{{ session('success') }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if ($board === null)
|
||||
<div class="rounded-2xl border border-dashed border-zinc-300 bg-white p-10 text-center dark:border-zinc-700 dark:bg-zinc-900">
|
||||
<flux:heading size="lg">{{ __('No Kanban board configured') }}</flux:heading>
|
||||
<flux:text class="mt-2">
|
||||
{{ __('Create a board in Helpdesk settings before using Kanban.') }}
|
||||
</flux:text>
|
||||
|
||||
<flux:button
|
||||
class="mt-5"
|
||||
variant="primary"
|
||||
:href="route('helpdesk.settings')"
|
||||
wire:navigate
|
||||
>
|
||||
{{ __('Open settings') }}
|
||||
</flux:button>
|
||||
</div>
|
||||
@else
|
||||
<div class="flex gap-4 overflow-x-auto pb-4">
|
||||
@foreach ($statuses as $status)
|
||||
@php
|
||||
$statusTickets = collect($ticketsByStatus[$status->id] ?? []);
|
||||
@endphp
|
||||
|
||||
<section
|
||||
class="w-[22rem] min-w-[22rem] rounded-2xl border border-zinc-200 bg-zinc-100/70 p-3 dark:border-zinc-700 dark:bg-zinc-900"
|
||||
wire:key="kanban-status-{{ $status->id }}-{{ $refreshNonce }}"
|
||||
>
|
||||
<header class="mb-3 flex items-center justify-between gap-3 px-1">
|
||||
<div class="min-w-0">
|
||||
<flux:heading size="sm" class="truncate">
|
||||
{{ $status->name }}
|
||||
</flux:heading>
|
||||
</div>
|
||||
|
||||
<span class="inline-flex min-w-7 items-center justify-center rounded-full bg-white px-2 py-1 text-xs font-semibold text-zinc-600 shadow-sm dark:bg-zinc-800 dark:text-zinc-300">
|
||||
{{ $statusTickets->count() }}
|
||||
</span>
|
||||
</header>
|
||||
|
||||
<div class="space-y-3">
|
||||
@forelse ($statusTickets as $ticket)
|
||||
<article
|
||||
class="rounded-xl border border-zinc-200 bg-white p-4 shadow-sm transition hover:shadow-md dark:border-zinc-700 dark:bg-zinc-800"
|
||||
wire:key="kanban-ticket-{{ $ticket->id }}-{{ $ticket->status_id }}"
|
||||
>
|
||||
<div class="flex items-start justify-between gap-3">
|
||||
<div class="min-w-0">
|
||||
<a
|
||||
href="{{ route('helpdesk.tickets.show', $ticket) }}"
|
||||
wire:navigate
|
||||
class="text-xs font-semibold text-blue-600 hover:underline dark:text-blue-400"
|
||||
>
|
||||
{{ $ticket->reference }}
|
||||
</a>
|
||||
|
||||
<a
|
||||
href="{{ route('helpdesk.tickets.show', $ticket) }}"
|
||||
wire:navigate
|
||||
class="mt-1 block font-semibold leading-snug text-zinc-900 hover:underline dark:text-zinc-100"
|
||||
>
|
||||
{{ $ticket->subject }}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@if ($ticket->priority)
|
||||
<span class="shrink-0 rounded-full bg-zinc-100 px-2 py-1 text-[11px] font-semibold text-zinc-600 dark:bg-zinc-700 dark:text-zinc-200">
|
||||
{{ $ticket->priority->name }}
|
||||
</span>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<div class="mt-3 space-y-1 text-xs text-zinc-500 dark:text-zinc-400">
|
||||
@if ($ticket->agent)
|
||||
<div>{{ __('Agent') }}: {{ $ticket->agent->name }}</div>
|
||||
@endif
|
||||
|
||||
@if ($ticket->category)
|
||||
<div>{{ __('Category') }}: {{ $ticket->category->name }}</div>
|
||||
@endif
|
||||
|
||||
@if ($ticket->project)
|
||||
<div>{{ __('Project') }}: {{ $ticket->project->name ?? $ticket->project->title ?? '—' }}</div>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
@can('update', $ticket)
|
||||
<div class="mt-4">
|
||||
<label class="mb-1 block text-xs font-semibold text-zinc-500 dark:text-zinc-400">
|
||||
{{ __('Move to') }}
|
||||
</label>
|
||||
|
||||
<select
|
||||
class="hd-select !min-h-0 !rounded-lg !px-3 !py-2 text-sm"
|
||||
wire:change="moveTicket({{ $ticket->id }}, $event.target.value)"
|
||||
wire:loading.attr="disabled"
|
||||
wire:target="moveTicket"
|
||||
>
|
||||
<option value="{{ $status->id }}">{{ $status->name }}</option>
|
||||
|
||||
@foreach ($statuses as $targetStatus)
|
||||
@if ($targetStatus->id !== $status->id)
|
||||
<option value="{{ $targetStatus->id }}">
|
||||
{{ $targetStatus->name }}
|
||||
</option>
|
||||
@endif
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
@endcan
|
||||
</article>
|
||||
@empty
|
||||
<div class="rounded-xl border border-dashed border-zinc-300 bg-white/70 px-4 py-8 text-center text-sm text-zinc-500 dark:border-zinc-700 dark:bg-zinc-800/40 dark:text-zinc-400">
|
||||
{{ __('No tickets') }}
|
||||
</div>
|
||||
@endforelse
|
||||
</div>
|
||||
</section>
|
||||
@endforeach
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
@@ -0,0 +1,68 @@
|
||||
<div class="space-y-6">
|
||||
<div class="flex flex-col gap-4 lg:flex-row lg:items-center lg:justify-between">
|
||||
<div>
|
||||
<flux:heading size="xl">{{ __('Helpdesk Settings') }}</flux:heading>
|
||||
<flux:text>{{ __('Manage statuses, priorities, categories, teams, projects and Helpdesk configuration.') }}</flux:text>
|
||||
</div>
|
||||
<flux:button :href="route('helpdesk.tickets.index')" wire:navigate>{{ __('Back to Helpdesk') }}</flux:button>
|
||||
</div>
|
||||
|
||||
@php
|
||||
$sections = [
|
||||
'statuses' => 'Statuses',
|
||||
'priorities' => 'Priorities',
|
||||
'categories' => 'Categories',
|
||||
'teams' => 'Teams',
|
||||
'projects' => 'Projects',
|
||||
'boards' => 'Boards',
|
||||
'repositories' => 'Repositories',
|
||||
'workflows' => 'Workflows',
|
||||
'canned' => 'Canned responses',
|
||||
'sla' => 'SLA policies',
|
||||
'automation' => 'Automation',
|
||||
'products' => 'Products',
|
||||
'tags' => 'Tags',
|
||||
'checklists' => 'Checklists',
|
||||
];
|
||||
@endphp
|
||||
|
||||
<div class="flex flex-wrap gap-2 rounded-xl border border-zinc-200 bg-white p-2 dark:border-zinc-700 dark:bg-zinc-900">
|
||||
@foreach ($sections as $key => $label)
|
||||
<button
|
||||
type="button"
|
||||
wire:click="$set('section', '{{ $key }}')"
|
||||
class="rounded-lg px-3 py-2 text-sm font-medium {{ $section === $key ? 'bg-zinc-900 text-white dark:bg-white dark:text-zinc-900' : 'text-zinc-600 hover:bg-zinc-100 dark:text-zinc-300 dark:hover:bg-zinc-800' }}"
|
||||
>{{ __($label) }}</button>
|
||||
@endforeach
|
||||
</div>
|
||||
|
||||
@if ($section === 'statuses')
|
||||
<x-helpdesk-settings-simple title="Statuses" :items="$statuses" form-prefix="statusForm" save-method="saveStatus" edit-method="editStatus" delete-method="deleteStatus" reset-method="resetStatusForm" />
|
||||
@elseif ($section === 'priorities')
|
||||
<x-helpdesk-settings-simple title="Priorities" :items="$priorities" form-prefix="priorityForm" save-method="savePriority" edit-method="editPriority" delete-method="deletePriority" reset-method="resetPriorityForm" />
|
||||
@elseif ($section === 'categories')
|
||||
<x-helpdesk-settings-simple title="Categories" :items="$categories" form-prefix="categoryForm" save-method="saveCategory" edit-method="editCategory" delete-method="deleteCategory" reset-method="resetCategoryForm" />
|
||||
@elseif ($section === 'teams')
|
||||
<x-helpdesk-settings-simple title="Teams" :items="$teams" form-prefix="teamForm" save-method="saveTeam" edit-method="editTeam" delete-method="deleteTeam" reset-method="resetTeamForm" />
|
||||
@elseif ($section === 'projects')
|
||||
<x-helpdesk-settings-simple title="Projects" :items="$projects" form-prefix="projectForm" save-method="saveProject" edit-method="editProject" delete-method="deleteProject" reset-method="resetProjectForm" name-field="title" />
|
||||
@elseif ($section === 'boards')
|
||||
<x-helpdesk-settings-simple title="Boards" :items="$boards" form-prefix="boardForm" save-method="saveBoard" edit-method="editBoard" delete-method="deleteBoard" reset-method="resetBoardForm" />
|
||||
@elseif ($section === 'repositories')
|
||||
<x-helpdesk-settings-simple title="Repositories" :items="$repositories" form-prefix="repositoryForm" save-method="saveRepository" edit-method="editRepository" delete-method="deleteRepository" reset-method="resetRepositoryForm" />
|
||||
@elseif ($section === 'workflows')
|
||||
<x-helpdesk-settings-simple title="Workflow templates" :items="$workflowTemplates" form-prefix="workflowForm" save-method="saveWorkflowTemplate" edit-method="editWorkflowTemplate" delete-method="deleteWorkflowTemplate" reset-method="resetWorkflowTemplateForm" />
|
||||
@elseif ($section === 'canned')
|
||||
<x-helpdesk-settings-simple title="Canned responses" :items="$cannedResponses" form-prefix="cannedForm" save-method="saveCannedResponse" edit-method="editCannedResponse" delete-method="deleteCannedResponse" reset-method="resetCannedResponseForm" />
|
||||
@elseif ($section === 'sla')
|
||||
<x-helpdesk-settings-simple title="SLA policies" :items="$slaPolicies" form-prefix="slaForm" save-method="saveSlaPolicy" edit-method="editSlaPolicy" delete-method="deleteSlaPolicy" reset-method="resetSlaPolicyForm" />
|
||||
@elseif ($section === 'automation')
|
||||
<x-helpdesk-settings-simple title="Automation rules" :items="$automationRules" form-prefix="automationForm" save-method="saveAutomationRule" edit-method="editAutomationRule" delete-method="deleteAutomationRule" reset-method="resetAutomationRuleForm" />
|
||||
@elseif ($section === 'products')
|
||||
<x-helpdesk-settings-simple title="Products" :items="$products" form-prefix="productForm" save-method="saveProduct" edit-method="editProduct" delete-method="deleteProduct" reset-method="resetProductForm" />
|
||||
@elseif ($section === 'tags')
|
||||
<x-helpdesk-settings-simple title="Tags" :items="$tags" form-prefix="tagForm" save-method="saveTag" edit-method="editTag" delete-method="deleteTag" reset-method="resetTagForm" />
|
||||
@elseif ($section === 'checklists')
|
||||
<x-helpdesk-settings-simple title="Checklist templates" :items="$checklistTemplates" form-prefix="checklistTemplateForm" save-method="saveChecklistTemplate" edit-method="editChecklistTemplate" delete-method="deleteChecklistTemplate" reset-method="resetChecklistTemplateForm" />
|
||||
@endif
|
||||
</div>
|
||||
@@ -0,0 +1,76 @@
|
||||
<div class="space-y-6">
|
||||
<div class="flex items-center justify-between gap-4">
|
||||
<div>
|
||||
<flux:heading size="xl">{{ __('Create ticket') }}</flux:heading>
|
||||
<flux:text>{{ __('Create a new Helpdesk request.') }}</flux:text>
|
||||
</div>
|
||||
<flux:button :href="route('helpdesk.tickets.index')" wire:navigate>{{ __('Back to tickets') }}</flux:button>
|
||||
</div>
|
||||
|
||||
<form wire:submit="save" class="space-y-6">
|
||||
<div class="grid gap-6 rounded-2xl border border-zinc-200 bg-white p-6 dark:border-zinc-700 dark:bg-zinc-900 lg:grid-cols-2">
|
||||
<div class="lg:col-span-2">
|
||||
<flux:input wire:model="subject" :label="__('Subject')" required />
|
||||
@error('subject') <p class="mt-1 text-sm text-red-600">{{ $message }}</p> @enderror
|
||||
</div>
|
||||
|
||||
<div class="lg:col-span-2">
|
||||
<flux:textarea wire:model="body" rows="10" :label="__('Description')" required />
|
||||
@error('body') <p class="mt-1 text-sm text-red-600">{{ $message }}</p> @enderror
|
||||
</div>
|
||||
|
||||
<flux:select wire:model="board_id" :label="__('Board')">
|
||||
<option value="">{{ __('No board') }}</option>
|
||||
@foreach ($boards as $item)<option value="{{ $item->id }}">{{ $item->name }}</option>@endforeach
|
||||
</flux:select>
|
||||
|
||||
<flux:select wire:model="priority_id" :label="__('Priority')" required>
|
||||
<option value="">{{ __('Select priority') }}</option>
|
||||
@foreach ($priorities as $item)<option value="{{ $item->id }}">{{ $item->name }}</option>@endforeach
|
||||
</flux:select>
|
||||
|
||||
<flux:select wire:model="category_id" :label="__('Category')">
|
||||
<option value="">{{ __('No category') }}</option>
|
||||
@foreach ($categories as $item)<option value="{{ $item->id }}">{{ $item->name }}</option>@endforeach
|
||||
</flux:select>
|
||||
|
||||
<flux:select wire:model="team_id" :label="__('Team')">
|
||||
<option value="">{{ __('No team') }}</option>
|
||||
@foreach ($teams as $item)<option value="{{ $item->id }}">{{ $item->name }}</option>@endforeach
|
||||
</flux:select>
|
||||
|
||||
<flux:select wire:model="project_id" :label="__('Project')">
|
||||
<option value="">{{ __('No project') }}</option>
|
||||
@foreach ($projects as $item)<option value="{{ $item->id }}">{{ $item->title }}</option>@endforeach
|
||||
</flux:select>
|
||||
|
||||
<flux:select wire:model="product_id" :label="__('Product')">
|
||||
<option value="">{{ __('No product') }}</option>
|
||||
@foreach ($products as $item)<option value="{{ $item->id }}">{{ $item->name }}</option>@endforeach
|
||||
</flux:select>
|
||||
|
||||
<flux:input wire:model="due_at" type="date" :label="__('Due date')" />
|
||||
|
||||
<div>
|
||||
<label class="mb-2 block text-sm font-medium text-zinc-700 dark:text-zinc-300">{{ __('Tags') }}</label>
|
||||
<select wire:model="tag_ids" multiple class="w-full rounded-lg border-zinc-300 bg-white dark:border-zinc-700 dark:bg-zinc-800">
|
||||
@foreach ($tags as $item)<option value="{{ $item->id }}">{{ $item->name }}</option>@endforeach
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="lg:col-span-2">
|
||||
<label class="mb-2 block text-sm font-medium text-zinc-700 dark:text-zinc-300">{{ __('Attachments') }}</label>
|
||||
<input wire:model="attachments" type="file" multiple class="block w-full text-sm text-zinc-600 dark:text-zinc-300">
|
||||
@error('attachments.*') <p class="mt-1 text-sm text-red-600">{{ $message }}</p> @enderror
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end gap-3">
|
||||
<flux:button type="button" wire:click="clearForm">{{ __('Clear') }}</flux:button>
|
||||
<flux:button type="submit" variant="primary" wire:loading.attr="disabled" wire:target="save">
|
||||
<span wire:loading.remove wire:target="save">{{ __('Create ticket') }}</span>
|
||||
<span wire:loading wire:target="save">{{ __('Saving…') }}</span>
|
||||
</flux:button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@@ -0,0 +1,59 @@
|
||||
<div class="space-y-6">
|
||||
<div class="flex items-center justify-between gap-4">
|
||||
<div>
|
||||
<flux:heading size="xl">{{ __('Edit :reference', ['reference' => $ticket->reference]) }}</flux:heading>
|
||||
<flux:text>{{ __('Update ticket details and assignment.') }}</flux:text>
|
||||
</div>
|
||||
<flux:button :href="route('helpdesk.tickets.show', $ticket)" wire:navigate>{{ __('Cancel') }}</flux:button>
|
||||
</div>
|
||||
|
||||
<form wire:submit="save" class="space-y-6">
|
||||
<div class="grid gap-6 rounded-2xl border border-zinc-200 bg-white p-6 dark:border-zinc-700 dark:bg-zinc-900 lg:grid-cols-2">
|
||||
<div class="lg:col-span-2"><flux:input wire:model="subject" :label="__('Subject')" required /></div>
|
||||
<div class="lg:col-span-2"><flux:textarea wire:model="body" rows="10" :label="__('Description')" required /></div>
|
||||
|
||||
<flux:select wire:model="status_id" :label="__('Status')">@foreach ($statuses as $item)<option value="{{ $item->id }}">{{ $item->name }}</option>@endforeach</flux:select>
|
||||
<flux:select wire:model="priority_id" :label="__('Priority')">@foreach ($priorities as $item)<option value="{{ $item->id }}">{{ $item->name }}</option>@endforeach</flux:select>
|
||||
|
||||
<flux:select wire:model="board_id" :label="__('Board')"><option value="">{{ __('No board') }}</option>@foreach ($boards as $item)<option value="{{ $item->id }}">{{ $item->name }}</option>@endforeach</flux:select>
|
||||
<flux:select wire:model="agent_id" :label="__('Agent')"><option value="">{{ __('Unassigned') }}</option>@foreach ($agents as $item)<option value="{{ $item->id }}">{{ $item->name }}</option>@endforeach</flux:select>
|
||||
|
||||
<flux:select wire:model="category_id" :label="__('Category')"><option value="">{{ __('No category') }}</option>@foreach ($categories as $item)<option value="{{ $item->id }}">{{ $item->name }}</option>@endforeach</flux:select>
|
||||
<flux:select wire:model="team_id" :label="__('Team')"><option value="">{{ __('No team') }}</option>@foreach ($teams as $item)<option value="{{ $item->id }}">{{ $item->name }}</option>@endforeach</flux:select>
|
||||
|
||||
<flux:select wire:model="project_id" :label="__('Project')"><option value="">{{ __('No project') }}</option>@foreach ($projects as $item)<option value="{{ $item->id }}">{{ $item->title }}</option>@endforeach</flux:select>
|
||||
<flux:select wire:model="product_id" :label="__('Product')"><option value="">{{ __('No product') }}</option>@foreach ($products as $item)<option value="{{ $item->id }}">{{ $item->name }}</option>@endforeach</flux:select>
|
||||
|
||||
<flux:input wire:model="due_at" type="date" :label="__('Due date')" />
|
||||
|
||||
<div>
|
||||
<label class="mb-2 block text-sm font-medium text-zinc-700 dark:text-zinc-300">{{ __('Tags') }}</label>
|
||||
<select wire:model="tag_ids" multiple class="w-full rounded-lg border-zinc-300 bg-white dark:border-zinc-700 dark:bg-zinc-800">
|
||||
@foreach ($tags as $item)<option value="{{ $item->id }}">{{ $item->name }}</option>@endforeach
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="lg:col-span-2">
|
||||
<label class="mb-2 block text-sm font-medium text-zinc-700 dark:text-zinc-300">{{ __('New attachments') }}</label>
|
||||
<input wire:model="attachments" type="file" multiple class="block w-full text-sm">
|
||||
</div>
|
||||
|
||||
@if ($ticket->attachments->isNotEmpty())
|
||||
<div class="lg:col-span-2 space-y-2">
|
||||
<flux:heading size="sm">{{ __('Existing attachments') }}</flux:heading>
|
||||
@foreach ($ticket->attachments as $attachment)
|
||||
<div class="flex items-center justify-between rounded-lg border border-zinc-200 px-3 py-2 dark:border-zinc-700">
|
||||
<span class="text-sm">{{ $attachment->original_name ?? $attachment->filename ?? __('Attachment') }}</span>
|
||||
<flux:button size="sm" variant="danger" type="button" wire:click="removeAttachment({{ $attachment->id }})">{{ __('Remove') }}</flux:button>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end gap-3">
|
||||
<flux:button type="button" wire:click="clearForm">{{ __('Clear') }}</flux:button>
|
||||
<flux:button type="submit" variant="primary">{{ __('Save changes') }}</flux:button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@@ -0,0 +1,204 @@
|
||||
<div class="space-y-6">
|
||||
<div class="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div>
|
||||
<flux:heading size="xl">{{ __('Tickets') }}</flux:heading>
|
||||
<flux:text>{{ __('Manage Helpdesk tickets, assignments and status.') }}</flux:text>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-2">
|
||||
<flux:button :href="route('helpdesk.kanban')" wire:navigate>
|
||||
{{ __('Kanban') }}
|
||||
</flux:button>
|
||||
|
||||
<flux:button variant="primary" :href="route('helpdesk.tickets.create')" wire:navigate>
|
||||
{{ __('New ticket') }}
|
||||
</flux:button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid gap-3 md:grid-cols-4">
|
||||
<flux:input
|
||||
wire:model.live.debounce.300ms="search"
|
||||
:label="__('Search')"
|
||||
:placeholder="__('Reference, subject or text')"
|
||||
/>
|
||||
|
||||
<flux:select wire:model.live="status" :label="__('Status')">
|
||||
<option value="">{{ __('All statuses') }}</option>
|
||||
@foreach ($statuses as $statusOption)
|
||||
<option value="{{ $statusOption->id }}">{{ $statusOption->name }}</option>
|
||||
@endforeach
|
||||
</flux:select>
|
||||
|
||||
<flux:select wire:model.live="priority" :label="__('Priority')">
|
||||
<option value="">{{ __('All priorities') }}</option>
|
||||
@foreach ($priorities as $priorityOption)
|
||||
<option value="{{ $priorityOption->id }}">{{ $priorityOption->name }}</option>
|
||||
@endforeach
|
||||
</flux:select>
|
||||
|
||||
<flux:select wire:model.live="scope" :label="__('Scope')">
|
||||
<option value="open">{{ __('Open') }}</option>
|
||||
<option value="closed">{{ __('Closed') }}</option>
|
||||
<option value="all">{{ __('All') }}</option>
|
||||
</flux:select>
|
||||
</div>
|
||||
|
||||
<div class="overflow-hidden rounded-xl border border-zinc-200 bg-white dark:border-zinc-700 dark:bg-zinc-900">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="min-w-full divide-y divide-zinc-200 dark:divide-zinc-700">
|
||||
<thead class="bg-zinc-50 dark:bg-zinc-800/80">
|
||||
<tr>
|
||||
<th class="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wide text-zinc-500">
|
||||
{{ __('Reference') }}
|
||||
</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wide text-zinc-500">
|
||||
{{ __('Subject') }}
|
||||
</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wide text-zinc-500">
|
||||
{{ __('Status') }}
|
||||
</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wide text-zinc-500">
|
||||
{{ __('Priority') }}
|
||||
</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wide text-zinc-500">
|
||||
{{ __('Agent') }}
|
||||
</th>
|
||||
<th class="px-4 py-3 text-right text-xs font-semibold uppercase tracking-wide text-zinc-500">
|
||||
{{ __('Actions') }}
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody class="divide-y divide-zinc-100 dark:divide-zinc-800">
|
||||
@forelse ($tickets as $ticket)
|
||||
<tr wire:key="ticket-{{ $ticket->id }}" class="hover:bg-zinc-50/70 dark:hover:bg-zinc-800/50">
|
||||
<td class="whitespace-nowrap px-4 py-3 text-sm font-medium text-zinc-900 dark:text-zinc-100">
|
||||
<a
|
||||
href="{{ route('helpdesk.tickets.show', $ticket) }}"
|
||||
wire:navigate
|
||||
class="hover:underline"
|
||||
>
|
||||
{{ $ticket->reference }}
|
||||
</a>
|
||||
</td>
|
||||
|
||||
<td class="px-4 py-3">
|
||||
<div class="max-w-xl">
|
||||
<a
|
||||
href="{{ route('helpdesk.tickets.show', $ticket) }}"
|
||||
wire:navigate
|
||||
class="font-medium text-zinc-900 hover:underline dark:text-zinc-100"
|
||||
>
|
||||
{{ $ticket->subject }}
|
||||
</a>
|
||||
|
||||
<div class="mt-1 flex flex-wrap gap-x-3 gap-y-1 text-xs text-zinc-500">
|
||||
@if ($ticket->category)
|
||||
<span>{{ $ticket->category->name }}</span>
|
||||
@endif
|
||||
|
||||
@if ($ticket->project)
|
||||
<span>{{ $ticket->project->title }}</span>
|
||||
@endif
|
||||
|
||||
@if ($ticket->attachments_count)
|
||||
<span>{{ trans_choice(':count attachment|:count attachments', $ticket->attachments_count, ['count' => $ticket->attachments_count]) }}</span>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
<td class="whitespace-nowrap px-4 py-3 text-sm text-zinc-700 dark:text-zinc-300">
|
||||
{{ $ticket->status?->name ?? '—' }}
|
||||
</td>
|
||||
|
||||
<td class="whitespace-nowrap px-4 py-3 text-sm text-zinc-700 dark:text-zinc-300">
|
||||
{{ $ticket->priority?->name ?? '—' }}
|
||||
</td>
|
||||
|
||||
<td class="whitespace-nowrap px-4 py-3 text-sm text-zinc-700 dark:text-zinc-300">
|
||||
{{ $ticket->agent_name ?? '—' }}
|
||||
</td>
|
||||
|
||||
<td class="whitespace-nowrap px-4 py-3 text-right">
|
||||
<div class="flex justify-end gap-2">
|
||||
<flux:button
|
||||
size="sm"
|
||||
:href="route('helpdesk.tickets.show', $ticket)"
|
||||
wire:navigate
|
||||
>
|
||||
{{ __('View') }}
|
||||
</flux:button>
|
||||
|
||||
@can('update', $ticket)
|
||||
<flux:button
|
||||
size="sm"
|
||||
:href="route('helpdesk.tickets.edit', $ticket)"
|
||||
wire:navigate
|
||||
>
|
||||
{{ __('Edit') }}
|
||||
</flux:button>
|
||||
@endcan
|
||||
|
||||
@can('delete', $ticket)
|
||||
<flux:button
|
||||
size="sm"
|
||||
variant="danger"
|
||||
wire:click="askToDeleteTicket({{ $ticket->id }})"
|
||||
>
|
||||
{{ __('Delete') }}
|
||||
</flux:button>
|
||||
@endcan
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr>
|
||||
<td colspan="6" class="px-6 py-12 text-center">
|
||||
<flux:heading size="lg">{{ __('No tickets found') }}</flux:heading>
|
||||
<flux:text class="mt-1">
|
||||
{{ __('Try changing the filters or create a new ticket.') }}
|
||||
</flux:text>
|
||||
</td>
|
||||
</tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
@if ($tickets->hasPages())
|
||||
<div class="border-t border-zinc-200 px-4 py-3 dark:border-zinc-700">
|
||||
{{ $tickets->links() }}
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
@if ($deleteModalOpen)
|
||||
<div
|
||||
class="fixed inset-0 z-50 flex items-center justify-center bg-black/50 p-4"
|
||||
wire:key="delete-ticket-modal"
|
||||
>
|
||||
<div class="w-full max-w-md rounded-xl bg-white p-6 shadow-xl dark:bg-zinc-900">
|
||||
<flux:heading size="lg">{{ __('Delete ticket?') }}</flux:heading>
|
||||
|
||||
<flux:text class="mt-2">
|
||||
{{ __('This will delete :reference — :subject.', [
|
||||
'reference' => $pendingDeleteTicketReference,
|
||||
'subject' => $pendingDeleteTicketSubject,
|
||||
]) }}
|
||||
</flux:text>
|
||||
|
||||
<div class="mt-6 flex justify-end gap-2">
|
||||
<flux:button wire:click="cancelTicketDelete">
|
||||
{{ __('Cancel') }}
|
||||
</flux:button>
|
||||
|
||||
<flux:button variant="danger" wire:click="confirmTicketDelete">
|
||||
{{ __('Delete') }}
|
||||
</flux:button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
@@ -0,0 +1,62 @@
|
||||
<div class="space-y-6">
|
||||
<div class="flex flex-col gap-4 lg:flex-row lg:items-start lg:justify-between">
|
||||
<div>
|
||||
<div class="text-sm font-semibold text-zinc-500">{{ $ticket->reference }}</div>
|
||||
<flux:heading size="xl">{{ $ticket->subject }}</flux:heading>
|
||||
<flux:text class="mt-1">{{ __('Opened by :name', ['name' => $ticket->user?->name ?? __('Unknown')]) }}</flux:text>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<flux:button :href="route('helpdesk.tickets.index')" wire:navigate>{{ __('Tickets') }}</flux:button>
|
||||
@can('update', $ticket)
|
||||
<flux:button variant="primary" :href="route('helpdesk.tickets.edit', $ticket)" wire:navigate>{{ __('Edit') }}</flux:button>
|
||||
@endcan
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid gap-6 xl:grid-cols-[minmax(0,1fr)_20rem]">
|
||||
<div class="space-y-6">
|
||||
<section class="rounded-2xl border border-zinc-200 bg-white p-6 dark:border-zinc-700 dark:bg-zinc-900">
|
||||
<div class="whitespace-pre-wrap text-sm leading-6 text-zinc-700 dark:text-zinc-300">{{ $ticket->body }}</div>
|
||||
</section>
|
||||
|
||||
<section class="rounded-2xl border border-zinc-200 bg-white p-6 dark:border-zinc-700 dark:bg-zinc-900">
|
||||
<flux:heading size="lg">{{ __('Comments') }}</flux:heading>
|
||||
<div class="mt-4 space-y-4">
|
||||
@forelse ($ticket->comments as $comment)
|
||||
<article class="rounded-xl bg-zinc-50 p-4 dark:bg-zinc-800">
|
||||
<div class="flex justify-between gap-3 text-xs text-zinc-500">
|
||||
<span>{{ $comment->user?->name ?? __('System') }}</span>
|
||||
<span>{{ $comment->created_at?->diffForHumans() }}</span>
|
||||
</div>
|
||||
@if ($comment->subject)<div class="mt-2 font-semibold">{{ $comment->subject }}</div>@endif
|
||||
<div class="mt-2 whitespace-pre-wrap text-sm">{{ $comment->body }}</div>
|
||||
</article>
|
||||
@empty
|
||||
<flux:text>{{ __('No comments yet.') }}</flux:text>
|
||||
@endforelse
|
||||
</div>
|
||||
|
||||
<div class="mt-6 space-y-3">
|
||||
<flux:textarea wire:model="commentBody" rows="5" :label="__('Add comment')" />
|
||||
<label class="flex items-center gap-2 text-sm"><input type="checkbox" wire:model="internal"> {{ __('Internal note') }}</label>
|
||||
<flux:button wire:click="addComment" variant="primary">{{ __('Add comment') }}</flux:button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<livewire:helpdesk.ticket-timer :ticket="$ticket" :key="'timer-'.$ticket->id" />
|
||||
</div>
|
||||
|
||||
<aside class="space-y-4">
|
||||
<section class="rounded-2xl border border-zinc-200 bg-white p-5 dark:border-zinc-700 dark:bg-zinc-900">
|
||||
<flux:heading size="sm">{{ __('Ticket details') }}</flux:heading>
|
||||
<dl class="mt-4 space-y-3 text-sm">
|
||||
<div><dt class="text-zinc-500">{{ __('Status') }}</dt><dd class="font-medium">{{ $ticket->status?->name ?? '—' }}</dd></div>
|
||||
<div><dt class="text-zinc-500">{{ __('Priority') }}</dt><dd class="font-medium">{{ $ticket->priority?->name ?? '—' }}</dd></div>
|
||||
<div><dt class="text-zinc-500">{{ __('Agent') }}</dt><dd class="font-medium">{{ $ticket->agent?->name ?? __('Unassigned') }}</dd></div>
|
||||
<div><dt class="text-zinc-500">{{ __('Category') }}</dt><dd class="font-medium">{{ $ticket->category?->name ?? '—' }}</dd></div>
|
||||
<div><dt class="text-zinc-500">{{ __('Project') }}</dt><dd class="font-medium">{{ $ticket->project?->title ?? '—' }}</dd></div>
|
||||
</dl>
|
||||
</section>
|
||||
</aside>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,22 @@
|
||||
<section class="rounded-2xl border border-zinc-200 bg-white p-5 dark:border-zinc-700 dark:bg-zinc-900">
|
||||
<div class="flex items-center justify-between gap-4">
|
||||
<div>
|
||||
<flux:heading size="sm">{{ __('Time tracking') }}</flux:heading>
|
||||
<flux:text>{{ $this->elapsedTime }}</flux:text>
|
||||
</div>
|
||||
|
||||
@if ($this->isRunning)
|
||||
<flux:button variant="danger" wire:click="endTimer">{{ __('Stop timer') }}</flux:button>
|
||||
@else
|
||||
<flux:button variant="primary" wire:click="startTimer">{{ __('Start timer') }}</flux:button>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<div class="mt-4">
|
||||
<flux:input wire:model="description" :label="__('Description')" :placeholder="__('What are you working on?')" />
|
||||
</div>
|
||||
|
||||
@if ($message)
|
||||
<p class="mt-3 text-sm text-zinc-600 dark:text-zinc-300">{{ $message }}</p>
|
||||
@endif
|
||||
</section>
|
||||
@@ -12,4 +12,14 @@
|
||||
@fonts
|
||||
|
||||
@vite('resources/themes/backend/assets/css/app.css')
|
||||
|
||||
|
||||
{{-- Helpdesk owns additional Select2/Flatpickr/Tailwind assets in its module Vite build. --}}
|
||||
@if (file_exists(public_path('build/helpdesk/manifest.json')))
|
||||
@vite([
|
||||
'Resources/assets/css/app.css',
|
||||
'Resources/assets/js/app.js',
|
||||
], 'build/helpdesk')
|
||||
@endif
|
||||
|
||||
@fluxAppearance
|
||||
|
||||
+11
-6
@@ -1,9 +1,7 @@
|
||||
import {
|
||||
defineConfig
|
||||
} from 'vite';
|
||||
import { defineConfig } from 'vite';
|
||||
import laravel from 'laravel-vite-plugin';
|
||||
import { bunny } from 'laravel-vite-plugin/fonts';
|
||||
import tailwindcss from "@tailwindcss/vite";
|
||||
import tailwindcss from '@tailwindcss/vite';
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
@@ -12,7 +10,11 @@ export default defineConfig({
|
||||
'resources/themes/backend/assets/css/app.css',
|
||||
'resources/themes/backend/assets/js/passkeys.js',
|
||||
],
|
||||
refresh: true,
|
||||
refresh: [
|
||||
'resources/themes/backend/views/**/*.blade.php',
|
||||
'resources/themes/backend/assets/**/*',
|
||||
'Modules/Helpdesk/**/*.php',
|
||||
],
|
||||
fonts: [
|
||||
bunny('Instrument Sans', {
|
||||
weights: [400, 500, 600],
|
||||
@@ -21,10 +23,13 @@ export default defineConfig({
|
||||
}),
|
||||
tailwindcss(),
|
||||
],
|
||||
|
||||
server: {
|
||||
cors: true,
|
||||
watch: {
|
||||
ignored: ['**/storage/framework/views/**'],
|
||||
ignored: [
|
||||
'**/storage/framework/views/**',
|
||||
],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user