now with git support

This commit is contained in:
Mark Gregory
2026-06-24 12:29:48 +01:00
parent 5c3b692618
commit 69e4e3ccca
11 changed files with 214 additions and 61 deletions
+22 -1
View File
@@ -134,10 +134,31 @@ final class TicketCreate extends Component
$this->subject = '';
$this->body = '';
$this->due_at = null;
$this->tag_ids = [];
$this->attachments = [];
$this->dispatch('helpdesk-form-cleared');
$this->company_id = null;
$this->board_id = null;
$this->priority_id = null;
$this->category_id = null;
$this->project_id = null;
$this->product_id = null;
$this->team_id = null;
$this->applyDefaults();
$this->dispatch('helpdesk-form-cleared', values: [
'company_id' => $this->company_id,
'board_id' => $this->board_id,
'priority_id' => $this->priority_id,
'category_id' => $this->category_id,
'project_id' => $this->project_id,
'product_id' => $this->product_id,
'team_id' => $this->team_id,
'due_at' => $this->due_at,
'tag_ids' => $this->tag_ids,
]);
$this->dispatch('helpdesk-editor-reset', model: 'body');
}
+28 -15
View File
@@ -173,23 +173,36 @@ final class TicketEdit extends Component
$this->resetErrorBag();
$this->resetValidation();
$this->subject = '';
$this->body = '';
$this->company_id = null;
$this->status_id = null;
$this->board_id = null;
$this->priority_id = null;
$this->category_id = null;
$this->project_id = null;
$this->product_id = null;
$this->team_id = null;
$this->agent_id = null;
$this->due_at = null;
$this->tag_ids = [];
$this->ticket = $this->freshTicket($this->ticket);
$this->company_id = $this->ticket->company_id;
$this->subject = $this->ticket->subject;
$this->body = $this->ticket->body;
$this->status_id = $this->ticket->status_id;
$this->board_id = $this->ticket->board_id;
$this->priority_id = $this->ticket->priority_id;
$this->category_id = $this->ticket->category_id;
$this->project_id = $this->ticket->project_id;
$this->product_id = $this->ticket->product_id;
$this->team_id = $this->ticket->team_id;
$this->agent_id = $this->ticket->agent_id;
$this->due_at = $this->ticket->due_at?->format('Y-m-d');
$this->tag_ids = $this->ticket->tags->pluck('id')->map(fn ($id): int => (int) $id)->all();
$this->attachments = [];
$this->dispatch('helpdesk-form-cleared');
$this->dispatch('helpdesk-editor-reset', model: 'body');
$this->dispatch('helpdesk-form-cleared', values: [
'company_id' => $this->company_id,
'status_id' => $this->status_id,
'board_id' => $this->board_id,
'priority_id' => $this->priority_id,
'category_id' => $this->category_id,
'project_id' => $this->project_id,
'product_id' => $this->product_id,
'team_id' => $this->team_id,
'agent_id' => $this->agent_id,
'due_at' => $this->due_at,
'tag_ids' => $this->tag_ids,
]);
$this->dispatch('helpdesk-editor-reset', model: 'body', value: $this->body);
}
public function render(): View
@@ -13,7 +13,7 @@
<div {{ $attributes->whereDoesntStartWith('wire:model')->merge(['class' => 'space-y-2']) }}
@if ($model) data-helpdesk-editor-model="{{ $model }}" @endif
x-on:helpdesk-editor-reset.window="if ($event.detail.model === model) clear()"
x-on:helpdesk-editor-reset.window="if ($event.detail.model === model) clear($event.detail.value || '')"
x-on:helpdesk-editor-sync.window="if ($event.detail.model === model) syncToLivewire()"
x-data="{
model: @js($model),
@@ -54,12 +54,12 @@
this.$nextTick(() => this.syncToHiddenInput());
},
clear() {
this.value = '';
this.$refs.editor.innerHTML = '';
clear(value = '') {
this.value = value || '';
this.$refs.editor.innerHTML = this.value;
if (this.model) {
this.$wire.set(this.model, '', false);
this.$wire.set(this.model, this.value, false);
}
},
init() {
@@ -103,7 +103,7 @@
class="rounded-lg px-2 py-1 text-sm hover:bg-zinc-200 dark:hover:bg-zinc-800">Clear</button>
</div>
<div x-ref="editor" contenteditable="true" spellcheck="true" lang="en-GB" autocapitalize="sentences" autocorrect="on" autocomplete="off" inputmode="text"
<div x-ref="editor" contenteditable="true" spellcheck="true" lang="en-GB" autocapitalize="sentences" autocorrect="on" inputmode="text" aria-multiline="true" role="textbox"
@if ($editorDusk) data-dusk="{{ $editorDusk }}" dusk="{{ $editorDusk }}" @endif
x-on:paste="handlePaste($event)" x-on:blur="syncToHiddenInput()"
class="helpdesk-rich-editor prose prose-sm min-h-[{{ (int) $rows * 28 }}px] max-w-none bg-white p-4 text-sm text-zinc-900 outline-none dark:prose-invert dark:bg-zinc-950 dark:text-zinc-100"
@@ -330,28 +330,33 @@
@enderror
</div>
</div>
<div class="flex items-center justify-end gap-3 border-t border-zinc-700 bg-zinc-900 px-6 py-4">
<button type="button" wire:click="clearForm" wire:loading.attr="disabled" wire:target="clearForm"
class="inline-flex items-center gap-2 rounded-xl bg-red-600 px-4 py-2 text-sm font-semibold text-white shadow-sm hover:bg-red-700 disabled:cursor-not-allowed disabled:opacity-50"
data-dusk="ticket-create-clear" dusk="ticket-create-clear">
<i class="fa-solid fa-eraser" aria-hidden="true"></i>
<span wire:loading.remove wire:target="clearForm">Clear</span>
<span wire:loading wire:target="clearForm">Clearing...</span>
</button>
</div>
<a href="{{ route('helpdesk.tickets.index') }}" wire:navigate
class="rounded-xl bg-blue-600 px-4 py-2 text-sm font-semibold text-white shadow-sm hover:bg-blue-700">
Cancel
</a>
<div class="border-t border-zinc-700 bg-zinc-900/95 px-6 py-4">
<div class="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-end">
<a href="{{ route('helpdesk.tickets.index') }}" wire:navigate
class="inline-flex items-center justify-center gap-2 rounded-xl bg-blue-600 px-4 py-2.5 text-sm font-semibold text-white shadow-sm transition hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500/70 focus:ring-offset-2 focus:ring-offset-zinc-950">
<i class="fa-solid fa-xmark" aria-hidden="true"></i>
<span>Cancel</span>
</a>
<button type="submit"
class="inline-flex items-center gap-2 rounded-xl bg-green-600 px-5 py-2 text-sm font-semibold text-white shadow-sm hover:bg-green-700 disabled:cursor-not-allowed disabled:opacity-50"
data-dusk="ticket-create-submit" dusk="ticket-create-submit" wire:loading.attr="disabled" wire:target="save,attachments" x-bind:disabled="helpdeskAttachmentsUploading" wire:dirty.class="ring-2 ring-amber-400/40">
<i class="fa-regular fa-floppy-disk" aria-hidden="true"></i>
<span wire:loading.remove wire:target="save,attachments">Create Ticket</span>
<span wire:loading wire:target="attachments">Uploading...</span>
<span wire:loading wire:target="save">Creating...</span>
</button>
<button type="button" wire:click="clearForm" wire:loading.attr="disabled" wire:target="clearForm"
class="inline-flex items-center justify-center gap-2 rounded-xl bg-amber-600 px-4 py-2.5 text-sm font-semibold text-white shadow-sm transition hover:bg-amber-700 disabled:cursor-not-allowed disabled:opacity-50 focus:outline-none focus:ring-2 focus:ring-amber-500/70 focus:ring-offset-2 focus:ring-offset-zinc-950"
data-dusk="ticket-create-clear" dusk="ticket-create-clear">
<i class="fa-solid fa-rotate-left" aria-hidden="true"></i>
<span wire:loading.remove wire:target="clearForm">Reset form</span>
<span wire:loading wire:target="clearForm">Resetting...</span>
</button>
<button type="submit"
class="inline-flex items-center justify-center gap-2 rounded-xl bg-green-600 px-5 py-2.5 text-sm font-semibold text-white shadow-sm transition hover:bg-green-700 disabled:cursor-not-allowed disabled:opacity-50 focus:outline-none focus:ring-2 focus:ring-green-500/70 focus:ring-offset-2 focus:ring-offset-zinc-950"
data-dusk="ticket-create-submit" dusk="ticket-create-submit" wire:loading.attr="disabled" wire:target="save,attachments" x-bind:disabled="helpdeskAttachmentsUploading" wire:dirty.class="ring-2 ring-amber-400/40">
<i class="fa-regular fa-floppy-disk" aria-hidden="true"></i>
<span wire:loading.remove wire:target="save,attachments">Create Ticket</span>
<span wire:loading wire:target="attachments">Uploading...</span>
<span wire:loading wire:target="save">Creating...</span>
</button>
</div>
</div>
</div>
</form>
@@ -392,11 +397,36 @@
};
};
const resetTicketCreateFiles = () => {
const resetTicketCreateFiles = (event) => {
document.querySelectorAll('.helpdesk-ticket-form #attachments').forEach((input) => {
input.value = '';
input.dispatchEvent(new Event('change', { bubbles: true }));
});
const values = event?.detail?.values || event?.values || {};
document.querySelectorAll('.helpdesk-ticket-form [data-helpdesk-select2][data-wire-model]').forEach((select) => {
const model = select.dataset.wireModel;
const value = values[model] ?? '';
select.value = value === null ? '' : String(value);
select.dispatchEvent(new Event('change', { bubbles: true }));
if (window.jQuery && window.jQuery.fn?.select2) {
window.jQuery(select).val(select.value || null).trigger('change.select2');
}
});
document.querySelectorAll('.helpdesk-ticket-form [data-helpdesk-flatpickr]').forEach((input) => {
const model = input.getAttribute('wire:model') || input.dataset.wireModel || input.id;
const value = values[model] ?? '';
input.value = value || '';
input.dispatchEvent(new Event('change', { bubbles: true }));
if (input._flatpickr) {
input._flatpickr.setDate(value || null, false);
}
});
};
const appendPastedFiles = (event) => {
@@ -400,25 +400,67 @@
</div>
</div>
<div class="flex items-center justify-end gap-3 border-t border-zinc-700 bg-zinc-900 px-6 py-4">
<button type="button" wire:click="clearForm"
class="rounded-xl bg-red-600 px-4 py-2 text-sm font-semibold text-white shadow-sm hover:bg-red-700">
Clear
</button>
<div class="border-t border-zinc-700 bg-zinc-900/95 px-6 py-4">
<div class="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-end">
<a href="{{ route('helpdesk.tickets.show', $ticket) }}" wire:navigate
class="inline-flex items-center justify-center gap-2 rounded-xl bg-blue-600 px-4 py-2.5 text-sm font-semibold text-white shadow-sm transition hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500/70 focus:ring-offset-2 focus:ring-offset-zinc-950">
<i class="fa-solid fa-xmark" aria-hidden="true"></i>
<span>Cancel</span>
</a>
<a href="{{ route('helpdesk.tickets.show', $ticket) }}" wire:navigate
class="rounded-xl bg-blue-600 px-4 py-2 text-sm font-semibold text-white shadow-sm hover:bg-blue-700">
Cancel
</a>
<button type="button" wire:click="clearForm" wire:loading.attr="disabled" wire:target="clearForm"
class="inline-flex items-center justify-center gap-2 rounded-xl bg-amber-600 px-4 py-2.5 text-sm font-semibold text-white shadow-sm transition hover:bg-amber-700 disabled:cursor-not-allowed disabled:opacity-50 focus:outline-none focus:ring-2 focus:ring-amber-500/70 focus:ring-offset-2 focus:ring-offset-zinc-950">
<i class="fa-solid fa-rotate-left" aria-hidden="true"></i>
<span wire:loading.remove wire:target="clearForm">Reset changes</span>
<span wire:loading wire:target="clearForm">Resetting...</span>
</button>
<button type="submit"
class="inline-flex items-center gap-2 rounded-xl bg-green-600 px-5 py-2 text-sm font-semibold text-white shadow-sm hover:bg-green-700 disabled:cursor-not-allowed disabled:opacity-50"
data-dusk="ticket-edit-submit" wire:loading.attr="disabled">
<i class="fa-regular fa-floppy-disk" aria-hidden="true"></i>
<span wire:loading.remove>Save Ticket</span>
<span wire:loading>Saving...</span>
</button>
<button type="submit"
class="inline-flex items-center justify-center gap-2 rounded-xl bg-green-600 px-5 py-2.5 text-sm font-semibold text-white shadow-sm transition hover:bg-green-700 disabled:cursor-not-allowed disabled:opacity-50 focus:outline-none focus:ring-2 focus:ring-green-500/70 focus:ring-offset-2 focus:ring-offset-zinc-950"
data-dusk="ticket-edit-submit" wire:loading.attr="disabled">
<i class="fa-regular fa-floppy-disk" aria-hidden="true"></i>
<span wire:loading.remove>Save Ticket</span>
<span wire:loading>Saving...</span>
</button>
</div>
</div>
</div>
</form>
<script data-helpdesk-edit-form-cleared-reset>
(() => {
const resetTicketEditControls = (event) => {
const values = event?.detail?.values || event?.values || {};
document.querySelectorAll('.helpdesk-ticket-form [data-helpdesk-select2][data-wire-model]').forEach((select) => {
const model = select.dataset.wireModel;
const value = values[model] ?? '';
select.value = value === null ? '' : String(value);
select.dispatchEvent(new Event('change', { bubbles: true }));
if (window.jQuery && window.jQuery.fn?.select2) {
window.jQuery(select).val(select.value || null).trigger('change.select2');
}
});
document.querySelectorAll('.helpdesk-ticket-form [data-helpdesk-flatpickr]').forEach((input) => {
const model = input.getAttribute('wire:model') || input.dataset.wireModel || input.id;
const value = values[model] ?? '';
input.value = value || '';
input.dispatchEvent(new Event('change', { bubbles: true }));
if (input._flatpickr) {
input._flatpickr.setDate(value || null, false);
}
});
};
document.addEventListener('helpdesk-form-cleared', resetTicketEditControls);
if (window.Livewire?.on) {
window.Livewire.on('helpdesk-form-cleared', resetTicketEditControls);
}
})();
</script>
</div>
+12
View File
@@ -387,3 +387,15 @@
- Replaced the broken artwork area with inline Helpdesk workflow artwork using Blade and Tailwind only.
- Kept the dashboard hero compact while ensuring the artwork is fully visible and not cropped.
- Preserved append-only changelog history.
- version: 1.10.12
date: 2026-06-24
type: bugfix
summary: Fix Helpdesk ticket create and edit form actions and reset behaviour.
changes:
- Restored the missing grid closing wrapper so create form actions render in the footer rather than inside the form grid.
- Repositioned Cancel, Reset and Create/Save actions to the right-hand footer on create and edit screens.
- Changed create reset to clear user-entered values and then reapply configured default dropdown values.
- Changed edit reset to restore the saved ticket values rather than blanking the ticket.
- Kept rich editor spellcheck enabled and preserved submit-time Livewire synchronisation without keypress syncing.
- Preserved append-only changelog history.
+1 -1
View File
@@ -22,5 +22,5 @@
"Modules\\Helpdesk\\Providers\\HelpdeskServiceProvider"
],
"files": [],
"version": "1.10.10"
"version": "1.10.12"
}
@@ -0,0 +1,25 @@
<?php
declare(strict_types=1);
namespace App\Http\Controllers\Git;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Routing\Controller;
final class GiteaWebhookController extends Controller
{
/**
* Handle an incoming Gitea webhook delivery.
*/
public function __invoke(Request $request): JsonResponse
{
return response()->json([
'message' => 'Gitea webhook received.',
'event' => $request->header('X-Gitea-Event'),
'delivery' => $request->header('X-Gitea-Delivery'),
'payload' => $request->json()->all(),
]);
}
}
+1
View File
@@ -9,6 +9,7 @@ use Modules\Platform\app\Http\Middleware\EnsureLicensedTenant;
return Application::configure(basePath: dirname(__DIR__))
->withRouting(
web: __DIR__ . '/../routes/web.php',
api: __DIR__ . '/../routes/api.php',
commands: __DIR__ . '/../routes/console.php',
health: '/up',
)
+4 -1
View File
@@ -27,7 +27,10 @@ return [
'secret' => env('AWS_SECRET_ACCESS_KEY'),
'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
],
'gitea' => [
'webhook_secret' => env('GITEA_WEBHOOK_SECRET'),
'webhook_authorization' => env('GITEA_WEBHOOK_AUTHORIZATION'),
],
'slack' => [
'notifications' => [
'bot_user_oauth_token' => env('SLACK_BOT_USER_OAUTH_TOKEN'),
+6
View File
@@ -0,0 +1,6 @@
<?php
use App\Http\Controllers\Git\GiteaWebhookController;
Route::post('/git/gitea/webhook', GiteaWebhookController::class)
->name('git.gitea.webhook');