structure updates
This commit is contained in:
@@ -11,6 +11,7 @@ use Illuminate\Database\QueryException;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use Livewire\Attributes\Layout;
|
||||
use Livewire\Component;
|
||||
use Livewire\WithPagination;
|
||||
use Modules\Helpdesk\Models\AutomationRule;
|
||||
@@ -1034,9 +1035,12 @@ final class SettingsManager extends Component
|
||||
$this->checklistTemplateForm = ['id' => null, 'category_id' => null, 'project_id' => null, 'name' => '', 'slug' => '', 'items_text' => '', 'is_active' => true];
|
||||
}
|
||||
|
||||
#[Layout('layouts.app')]
|
||||
public function render(): View
|
||||
{
|
||||
return view('modules.helpdesk.livewire.settings-manager', [
|
||||
return view()->file(
|
||||
resource_path('themes/backend/views/modules/helpdesk/livewire/settings-manager.blade.php'),
|
||||
[
|
||||
'statuses' => $this->orderedStatuses(),
|
||||
'priorities' => $this->orderedPriorities(),
|
||||
'categories' => $this->orderedCategories(),
|
||||
@@ -1088,7 +1092,8 @@ final class SettingsManager extends Component
|
||||
->orderBy('name')
|
||||
->paginate(10, ['*'], 'checksPage'),
|
||||
'colourOptions' => Color::options(),
|
||||
])->layout('layouts.app');
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
private function orderedStatuses(): mixed
|
||||
|
||||
@@ -1017,3 +1017,378 @@ span.flatpickr-weekday {
|
||||
padding-inline: 0.25rem;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Professional coloured headers for Helpdesk settings modals */
|
||||
.hd-settings-modal-header {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
margin-bottom: 1.35rem;
|
||||
padding: 1.05rem 1.2rem;
|
||||
border: 1px solid rgba(255, 255, 255, 0.10);
|
||||
border-radius: 0.9rem;
|
||||
box-shadow:
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.08),
|
||||
0 12px 30px rgba(0, 0, 0, 0.14);
|
||||
}
|
||||
|
||||
.hd-settings-modal-header::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: -3.5rem;
|
||||
right: -2rem;
|
||||
width: 11rem;
|
||||
height: 11rem;
|
||||
border-radius: 9999px;
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
filter: blur(1px);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.hd-settings-modal-header [data-flux-heading],
|
||||
.hd-settings-modal-header h1,
|
||||
.hd-settings-modal-header h2,
|
||||
.hd-settings-modal-header h3 {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
color: #fff !important;
|
||||
font-weight: 700;
|
||||
letter-spacing: -0.015em;
|
||||
}
|
||||
|
||||
.hd-settings-modal-header [data-flux-subheading],
|
||||
.hd-settings-modal-header p {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
color: rgba(255, 255, 255, 0.74) !important;
|
||||
}
|
||||
|
||||
.hd-settings-modal-header--status {
|
||||
background: linear-gradient(135deg, #075985 0%, #0369a1 48%, #0e7490 100%);
|
||||
}
|
||||
|
||||
.hd-settings-modal-header--priority {
|
||||
background: linear-gradient(135deg, #7f1d1d 0%, #b91c1c 48%, #c2410c 100%);
|
||||
}
|
||||
|
||||
.hd-settings-modal-header--category {
|
||||
background: linear-gradient(135deg, #3730a3 0%, #4f46e5 48%, #7c3aed 100%);
|
||||
}
|
||||
|
||||
.hd-settings-modal-header--team {
|
||||
background: linear-gradient(135deg, #1e3a8a 0%, #1d4ed8 48%, #0369a1 100%);
|
||||
}
|
||||
|
||||
.hd-settings-modal-header--project {
|
||||
background: linear-gradient(135deg, #155e75 0%, #0e7490 48%, #0f766e 100%);
|
||||
}
|
||||
|
||||
.hd-settings-modal-header--board {
|
||||
background: linear-gradient(135deg, #78350f 0%, #b45309 48%, #d97706 100%);
|
||||
}
|
||||
|
||||
.hd-settings-modal-header--git {
|
||||
background: linear-gradient(135deg, #1f2937 0%, #374151 52%, #475569 100%);
|
||||
}
|
||||
|
||||
.hd-settings-modal-header--workflow {
|
||||
background: linear-gradient(135deg, #115e59 0%, #0f766e 48%, #047857 100%);
|
||||
}
|
||||
|
||||
.hd-settings-modal-header--canned {
|
||||
background: linear-gradient(135deg, #164e63 0%, #0891b2 48%, #0284c7 100%);
|
||||
}
|
||||
|
||||
.hd-settings-modal-header--sla {
|
||||
background: linear-gradient(135deg, #7c2d12 0%, #c2410c 48%, #ea580c 100%);
|
||||
}
|
||||
|
||||
.hd-settings-modal-header--automation {
|
||||
background: linear-gradient(135deg, #581c87 0%, #7e22ce 48%, #9333ea 100%);
|
||||
}
|
||||
|
||||
.hd-settings-modal-header--checklist {
|
||||
background: linear-gradient(135deg, #365314 0%, #4d7c0f 48%, #65a30d 100%);
|
||||
}
|
||||
|
||||
.hd-settings-modal-header--tag {
|
||||
background: linear-gradient(135deg, #831843 0%, #be185d 48%, #db2777 100%);
|
||||
}
|
||||
|
||||
.hd-settings-modal-header--product {
|
||||
background: linear-gradient(135deg, #312e81 0%, #4338ca 48%, #6366f1 100%);
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.hd-settings-modal-header {
|
||||
padding: 0.95rem 1rem;
|
||||
border-radius: 0.8rem;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Settings modal shell alignment + typography */
|
||||
.hd-settings-modal,
|
||||
.hd-settings-modal * {
|
||||
font-family: "Instrument Sans", ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif !important;
|
||||
}
|
||||
|
||||
.hd-settings-modal {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
/*
|
||||
* Flux supplies the modal body padding. Pull the coloured banner back over
|
||||
* that padding so it visually becomes the modal's own top header rather than
|
||||
* looking like a card floating inside the dialog.
|
||||
*/
|
||||
.hd-settings-modal .hd-settings-modal-header {
|
||||
margin-top: -1.5rem;
|
||||
margin-right: -1.5rem;
|
||||
margin-bottom: 1.5rem;
|
||||
margin-left: -1.5rem;
|
||||
padding: 1.35rem 1.5rem 1.3rem;
|
||||
border-top: 0;
|
||||
border-right: 0;
|
||||
border-left: 0;
|
||||
border-radius: 1rem 1rem 0 0;
|
||||
box-shadow:
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.08),
|
||||
0 10px 26px rgba(0, 0, 0, 0.16);
|
||||
}
|
||||
|
||||
.hd-settings-modal .hd-settings-modal-header [data-flux-heading],
|
||||
.hd-settings-modal .hd-settings-modal-header h1,
|
||||
.hd-settings-modal .hd-settings-modal-header h2,
|
||||
.hd-settings-modal .hd-settings-modal-header h3 {
|
||||
margin: 0;
|
||||
font-family: inherit !important;
|
||||
font-size: 1.25rem !important;
|
||||
font-weight: 700 !important;
|
||||
line-height: 1.35 !important;
|
||||
letter-spacing: -0.02em !important;
|
||||
}
|
||||
|
||||
.hd-settings-modal .hd-settings-modal-header [data-flux-subheading],
|
||||
.hd-settings-modal .hd-settings-modal-header p {
|
||||
margin-top: 0.45rem !important;
|
||||
margin-bottom: 0 !important;
|
||||
font-family: inherit !important;
|
||||
font-size: 0.95rem !important;
|
||||
font-weight: 400 !important;
|
||||
line-height: 1.5 !important;
|
||||
letter-spacing: 0 !important;
|
||||
}
|
||||
|
||||
.hd-settings-modal input,
|
||||
.hd-settings-modal select,
|
||||
.hd-settings-modal textarea,
|
||||
.hd-settings-modal button {
|
||||
font-family: inherit !important;
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.hd-settings-modal .hd-settings-modal-header {
|
||||
margin-top: -1rem;
|
||||
margin-right: -1rem;
|
||||
margin-left: -1rem;
|
||||
padding: 1.1rem 1rem;
|
||||
border-radius: 0.85rem 0.85rem 0 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Kanban-style settings form rhythm */
|
||||
.hd-settings-modal {
|
||||
color: rgb(244 244 245);
|
||||
}
|
||||
|
||||
.hd-settings-modal :is(label, [data-flux-label]) {
|
||||
font-family: "Instrument Sans", ui-sans-serif, system-ui, sans-serif !important;
|
||||
font-size: 0.95rem !important;
|
||||
font-weight: 600 !important;
|
||||
line-height: 1.35 !important;
|
||||
letter-spacing: -0.01em !important;
|
||||
text-transform: none !important;
|
||||
}
|
||||
|
||||
.hd-settings-modal :is(input:not([type="checkbox"]):not([type="radio"]):not([type="color"]), select, textarea) {
|
||||
min-height: 3rem;
|
||||
font-family: "Instrument Sans", ui-sans-serif, system-ui, sans-serif !important;
|
||||
font-size: 1rem !important;
|
||||
font-weight: 500 !important;
|
||||
line-height: 1.4 !important;
|
||||
letter-spacing: -0.01em !important;
|
||||
color: rgb(244 244 245) !important;
|
||||
background: rgb(39 39 42) !important;
|
||||
border-color: rgb(82 82 91) !important;
|
||||
border-radius: 0.75rem !important;
|
||||
}
|
||||
|
||||
.hd-settings-modal :is(input, select, textarea):focus {
|
||||
border-color: rgb(59 130 246) !important;
|
||||
box-shadow: 0 0 0 3px rgb(59 130 246 / 0.16) !important;
|
||||
}
|
||||
|
||||
.hd-settings-modal textarea {
|
||||
min-height: 7.5rem;
|
||||
padding-top: 0.85rem !important;
|
||||
padding-bottom: 0.85rem !important;
|
||||
}
|
||||
|
||||
.hd-settings-modal button,
|
||||
.hd-settings-modal [role="button"] {
|
||||
font-family: "Instrument Sans", ui-sans-serif, system-ui, sans-serif !important;
|
||||
font-weight: 650 !important;
|
||||
letter-spacing: -0.01em !important;
|
||||
}
|
||||
|
||||
/* Shared horizontal colour control for Status, Priority, Category, Team, Project and Tag editors. */
|
||||
.hd-colour-control {
|
||||
width: 100%;
|
||||
padding: 1rem 1.1rem;
|
||||
border: 1px solid rgb(63 63 70);
|
||||
border-radius: 0.9rem;
|
||||
background: rgb(24 24 27 / 0.78);
|
||||
box-shadow: inset 0 1px 0 rgb(255 255 255 / 0.025);
|
||||
}
|
||||
|
||||
.hd-colour-control__label {
|
||||
margin-bottom: 0.65rem;
|
||||
font-family: "Instrument Sans", ui-sans-serif, system-ui, sans-serif;
|
||||
font-size: 0.95rem;
|
||||
font-weight: 650;
|
||||
line-height: 1.35;
|
||||
letter-spacing: -0.01em;
|
||||
color: rgb(228 228 231);
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
.hd-colour-control__row {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(18rem, 1fr) minmax(7.5rem, auto) auto 3.25rem minmax(8rem, 10rem);
|
||||
align-items: center;
|
||||
gap: 0.8rem;
|
||||
}
|
||||
|
||||
.hd-colour-control__select,
|
||||
.hd-colour-control__value {
|
||||
height: 3rem;
|
||||
min-width: 0;
|
||||
border: 1px solid rgb(82 82 91);
|
||||
border-radius: 0.75rem;
|
||||
background: rgb(39 39 42);
|
||||
color: rgb(244 244 245);
|
||||
font-family: "Instrument Sans", ui-sans-serif, system-ui, sans-serif;
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
outline: none;
|
||||
transition: border-color 150ms ease, box-shadow 150ms ease;
|
||||
}
|
||||
|
||||
.hd-colour-control__select {
|
||||
width: 100%;
|
||||
padding: 0 0.95rem;
|
||||
}
|
||||
|
||||
.hd-colour-control__value {
|
||||
width: 100%;
|
||||
padding: 0 0.9rem;
|
||||
}
|
||||
|
||||
.hd-colour-control__select:focus,
|
||||
.hd-colour-control__value:focus {
|
||||
border-color: rgb(59 130 246);
|
||||
box-shadow: 0 0 0 3px rgb(59 130 246 / 0.16);
|
||||
}
|
||||
|
||||
.hd-colour-control__badge {
|
||||
display: inline-flex;
|
||||
height: 3rem;
|
||||
min-width: 7.5rem;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0 1rem;
|
||||
border-radius: 0.75rem;
|
||||
font-family: "Instrument Sans", ui-sans-serif, system-ui, sans-serif;
|
||||
font-size: 0.95rem;
|
||||
font-weight: 700;
|
||||
box-shadow: inset 0 0 0 1px rgb(255 255 255 / 0.12);
|
||||
}
|
||||
|
||||
.hd-colour-control__swatches {
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
align-items: center;
|
||||
gap: 0.45rem;
|
||||
}
|
||||
|
||||
.hd-colour-control__swatch {
|
||||
width: 1.75rem;
|
||||
height: 1.75rem;
|
||||
flex: 0 0 auto;
|
||||
border: 2px solid rgb(228 228 231 / 0.86);
|
||||
border-radius: 9999px;
|
||||
box-shadow: 0 0 0 1px rgb(0 0 0 / 0.28);
|
||||
transition: transform 120ms ease, box-shadow 120ms ease;
|
||||
}
|
||||
|
||||
.hd-colour-control__swatch:hover {
|
||||
transform: translateY(-1px) scale(1.06);
|
||||
box-shadow: 0 0 0 3px rgb(59 130 246 / 0.14);
|
||||
}
|
||||
|
||||
.hd-colour-control__picker {
|
||||
width: 3.25rem;
|
||||
height: 3rem;
|
||||
padding: 0.35rem;
|
||||
cursor: pointer;
|
||||
border: 1px solid rgb(82 82 91);
|
||||
border-radius: 0.75rem;
|
||||
background: rgb(39 39 42);
|
||||
}
|
||||
|
||||
@media (max-width: 1180px) {
|
||||
.hd-colour-control__row {
|
||||
grid-template-columns: minmax(16rem, 1fr) minmax(7rem, auto) auto;
|
||||
}
|
||||
|
||||
.hd-colour-control__picker,
|
||||
.hd-colour-control__value {
|
||||
grid-row: 2;
|
||||
}
|
||||
|
||||
.hd-colour-control__value {
|
||||
grid-column: 2 / 4;
|
||||
max-width: 13rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 720px) {
|
||||
.hd-colour-control {
|
||||
padding: 0.9rem;
|
||||
}
|
||||
|
||||
.hd-colour-control__row {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.hd-colour-control__select {
|
||||
flex: 1 1 100%;
|
||||
}
|
||||
|
||||
.hd-colour-control__badge {
|
||||
flex: 1 1 8rem;
|
||||
}
|
||||
|
||||
.hd-colour-control__swatches {
|
||||
order: 5;
|
||||
width: 100%;
|
||||
overflow-x: auto;
|
||||
padding-top: 0.15rem;
|
||||
}
|
||||
|
||||
.hd-colour-control__value {
|
||||
flex: 1 1 8rem;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,6 +51,17 @@ class User extends Authenticatable implements PasskeyUser
|
||||
}
|
||||
|
||||
|
||||
|
||||
public function gravatarUrl(int $size = 160): string
|
||||
{
|
||||
$email = strtolower(trim((string) $this->email));
|
||||
$hash = hash('sha256', $email);
|
||||
|
||||
return 'https://www.gravatar.com/avatar/'.$hash
|
||||
.'?s='.max(1, min($size, 2048))
|
||||
.'&d=404&r=g';
|
||||
}
|
||||
|
||||
public function roles(): BelongsToMany
|
||||
{
|
||||
return $this->belongsToMany(
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
@import 'tailwindcss';
|
||||
@import '@fortawesome/fontawesome-free/css/all.min.css';
|
||||
@import '../../../../../vendor/livewire/flux/dist/flux.css';
|
||||
@import './helpdesk.css';
|
||||
|
||||
@source '../../views/**/*.blade.php';
|
||||
@source '../../../../../Modules/Helpdesk/**/*.php';
|
||||
@source '../../../../../vendor/laravel/framework/src/Illuminate/Pagination/resources/views/*.blade.php';
|
||||
@source '../../../../../vendor/livewire/flux/stubs/**/*.blade.php';
|
||||
@source '../../../../../vendor/livewire/flux-pro/stubs/**/*.blade.php';
|
||||
|
||||
@custom-variant dark (&:where(.dark, .dark *));
|
||||
|
||||
@theme {
|
||||
--font-sans: 'Instrument Sans', ui-sans-serif, system-ui, sans-serif,
|
||||
'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
|
||||
}
|
||||
@@ -1337,3 +1337,213 @@ span.flatpickr-weekday {
|
||||
@media(max-width:1100px){.hd-metric-grid{grid-template-columns:repeat(2,minmax(0,1fr))}.hd-dashboard-grid{grid-template-columns:1fr}.hd-priority-body{min-height:240px}.hd-top-search{display:none}}
|
||||
@media(max-width:1023px){:root{--hd-sidebar-width:300px}.hd-topbar{display:flex}.hd-topbar-brand{width:auto;border-right:0}.hd-topbar-main{flex:1;padding-left:8px}.hd-mobile-menu-button{display:inline-flex}.hd-shell-sidebar{transform:translateX(-102%);width:min(84vw,320px);box-shadow:15px 0 40px rgba(0,0,0,.35)}.hd-sidebar-open .hd-shell-sidebar{transform:translateX(0)}.hd-sidebar-resizer{display:none}.hd-shell-main{margin-left:0;padding:calc(var(--hd-topbar-height) + 20px) 18px 25px}.hd-sidebar-backdrop{position:fixed;z-index:55;inset:var(--hd-topbar-height) 0 0 0;background:rgba(0,0,0,.52);border:0}.hd-sidebar-open .hd-sidebar-backdrop{display:block}}
|
||||
@media(max-width:700px){.hd-account-copy{display:none}.hd-topbar-main{padding-right:10px}.hd-topbar-brand{padding-left:10px;padding-right:8px}.hd-brand-name{font-size:.95rem}.hd-shell-main{padding-left:10px;padding-right:10px}.hd-dashboard-heading{align-items:flex-start;flex-direction:column}.hd-range-select{width:100%}.hd-metric-grid{grid-template-columns:1fr}.hd-dashboard-card{border-radius:10px}.hd-priority-body{grid-template-columns:1fr}.hd-latest-list>a{grid-template-columns:65px minmax(0,1fr);gap:10px}.hd-latest-list time,.hd-latest-list .hd-status-mini{display:none}.hd-page-hero{align-items:flex-start;flex-direction:column;padding:18px}.hd-page-hero .hd-action{width:100%}.hd-toolbar{flex-direction:column}.hd-form-grid,.hd-role-options{grid-template-columns:1fr}.hd-action{min-height:44px}}
|
||||
|
||||
|
||||
/* Dark account menu + Gravatar */
|
||||
.hd-account-menu {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
color: #f8fafc;
|
||||
}
|
||||
|
||||
.hd-account-menu > summary {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.hd-account-menu > summary::-webkit-details-marker {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.hd-account-menu[open] .hd-account-button {
|
||||
background: #202732;
|
||||
border-color: #3a4554;
|
||||
}
|
||||
|
||||
.hd-account-menu[open] .hd-account-chevron {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
.hd-account-chevron {
|
||||
font-size: .75rem;
|
||||
transition: transform .16s ease;
|
||||
}
|
||||
|
||||
.hd-user-avatar--gravatar {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.hd-user-avatar--gravatar img {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
z-index: 2;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: block;
|
||||
border-radius: inherit;
|
||||
object-fit: cover;
|
||||
background: #252c36;
|
||||
}
|
||||
|
||||
.hd-user-avatar-fallback {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
display: inline-flex;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.hd-user-avatar--large {
|
||||
width: 46px;
|
||||
height: 46px;
|
||||
flex-basis: 46px;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.hd-account-dropdown {
|
||||
position: absolute;
|
||||
top: calc(100% + 10px);
|
||||
right: 0;
|
||||
z-index: 120;
|
||||
width: min(310px, calc(100vw - 24px));
|
||||
overflow: hidden;
|
||||
border: 1px solid #333d4a;
|
||||
border-radius: 14px;
|
||||
background:
|
||||
linear-gradient(180deg, rgba(28, 34, 44, .98), rgba(18, 23, 30, .99));
|
||||
color: #e5e7eb;
|
||||
box-shadow:
|
||||
0 24px 60px rgba(0, 0, 0, .48),
|
||||
0 8px 22px rgba(0, 0, 0, .28),
|
||||
inset 0 1px 0 rgba(255, 255, 255, .035);
|
||||
}
|
||||
|
||||
.hd-account-dropdown-user {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 16px;
|
||||
background: rgba(255, 255, 255, .018);
|
||||
}
|
||||
|
||||
.hd-account-dropdown-copy {
|
||||
min-width: 0;
|
||||
display: grid;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.hd-account-dropdown-copy strong {
|
||||
overflow: hidden;
|
||||
color: #f8fafc;
|
||||
font-size: .95rem;
|
||||
font-weight: 700;
|
||||
line-height: 1.25;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.hd-account-dropdown-copy span {
|
||||
overflow: hidden;
|
||||
color: #9ca3af;
|
||||
font-size: .78rem;
|
||||
line-height: 1.25;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.hd-account-dropdown-copy small {
|
||||
margin-top: 2px;
|
||||
color: #60a5fa;
|
||||
font-size: .7rem;
|
||||
font-weight: 700;
|
||||
letter-spacing: .055em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.hd-account-dropdown-divider {
|
||||
height: 1px;
|
||||
background: #303846;
|
||||
}
|
||||
|
||||
.hd-account-dropdown-nav {
|
||||
display: grid;
|
||||
gap: 4px;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.hd-account-dropdown-nav a,
|
||||
.hd-account-logout {
|
||||
width: 100%;
|
||||
min-height: 42px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 11px;
|
||||
padding: 9px 11px;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 9px;
|
||||
background: transparent;
|
||||
color: #d7dce4;
|
||||
font: inherit;
|
||||
font-size: .86rem;
|
||||
font-weight: 600;
|
||||
line-height: 1.25;
|
||||
text-align: left;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
transition:
|
||||
background .14s ease,
|
||||
border-color .14s ease,
|
||||
color .14s ease;
|
||||
}
|
||||
|
||||
.hd-account-dropdown-nav a i,
|
||||
.hd-account-logout i {
|
||||
width: 17px;
|
||||
color: #8fa0b5;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.hd-account-dropdown-nav a:hover,
|
||||
.hd-account-dropdown-nav a:focus-visible {
|
||||
border-color: #35465c;
|
||||
background: #263244;
|
||||
color: #fff;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.hd-account-dropdown-nav a:hover i {
|
||||
color: #60a5fa;
|
||||
}
|
||||
|
||||
.hd-account-dropdown form {
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.hd-account-logout:hover,
|
||||
.hd-account-logout:focus-visible {
|
||||
border-color: rgba(239, 68, 68, .32);
|
||||
background: rgba(127, 29, 29, .24);
|
||||
color: #fecaca;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.hd-account-logout:hover i,
|
||||
.hd-account-logout:focus-visible i {
|
||||
color: #f87171;
|
||||
}
|
||||
|
||||
.hd-account-menu--legacy .hd-account-dropdown {
|
||||
left: 0;
|
||||
right: auto;
|
||||
}
|
||||
|
||||
@media (max-width: 700px) {
|
||||
.hd-account-dropdown {
|
||||
position: fixed;
|
||||
top: calc(var(--hd-topbar-height) - 2px);
|
||||
right: 10px;
|
||||
width: min(310px, calc(100vw - 20px));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,39 +1,66 @@
|
||||
<flux:dropdown position="bottom" align="start">
|
||||
<flux:sidebar.profile
|
||||
:name="auth()->user()->name"
|
||||
:initials="auth()->user()->initials()"
|
||||
icon:trailing="chevrons-up-down"
|
||||
data-test="sidebar-menu-button"
|
||||
/>
|
||||
@php
|
||||
$user = auth()->user();
|
||||
@endphp
|
||||
|
||||
<flux:menu>
|
||||
<div class="flex items-center gap-2 px-1 py-1.5 text-start text-sm">
|
||||
<flux:avatar
|
||||
:name="auth()->user()->name"
|
||||
:initials="auth()->user()->initials()"
|
||||
/>
|
||||
<div class="grid flex-1 text-start text-sm leading-tight">
|
||||
<flux:heading class="truncate">{{ auth()->user()->name }}</flux:heading>
|
||||
<flux:text class="truncate">{{ auth()->user()->email }}</flux:text>
|
||||
<details class="hd-account-menu hd-account-menu--legacy">
|
||||
<summary class="hd-account-button" data-test="sidebar-menu-button">
|
||||
<span class="hd-user-avatar hd-user-avatar--gravatar">
|
||||
<span class="hd-user-avatar-fallback">{{ $user->initials() }}</span>
|
||||
<img
|
||||
src="{{ $user->gravatarUrl(160) }}"
|
||||
alt="{{ $user->name }}"
|
||||
loading="lazy"
|
||||
referrerpolicy="no-referrer"
|
||||
onerror="this.style.display='none'"
|
||||
>
|
||||
</span>
|
||||
|
||||
<span class="hd-account-copy">
|
||||
<strong>{{ $user->name }}</strong>
|
||||
<small>{{ $user->email }}</small>
|
||||
</span>
|
||||
|
||||
<i class="fa-solid fa-chevron-down hd-account-chevron"></i>
|
||||
</summary>
|
||||
|
||||
<div class="hd-account-dropdown" role="menu">
|
||||
<div class="hd-account-dropdown-user">
|
||||
<span class="hd-user-avatar hd-user-avatar--gravatar hd-user-avatar--large">
|
||||
<span class="hd-user-avatar-fallback">{{ $user->initials() }}</span>
|
||||
<img
|
||||
src="{{ $user->gravatarUrl(160) }}"
|
||||
alt="{{ $user->name }}"
|
||||
loading="lazy"
|
||||
referrerpolicy="no-referrer"
|
||||
onerror="this.style.display='none'"
|
||||
>
|
||||
</span>
|
||||
|
||||
<div class="hd-account-dropdown-copy">
|
||||
<strong>{{ $user->name }}</strong>
|
||||
<span>{{ $user->email }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<flux:menu.separator />
|
||||
<flux:menu.radio.group>
|
||||
<flux:menu.item :href="route('profile.edit')" icon="cog" wire:navigate>
|
||||
{{ __('Settings') }}
|
||||
</flux:menu.item>
|
||||
<form method="POST" action="{{ route('logout') }}" class="w-full">
|
||||
@csrf
|
||||
<flux:menu.item
|
||||
as="button"
|
||||
type="submit"
|
||||
icon="arrow-right-start-on-rectangle"
|
||||
class="w-full cursor-pointer"
|
||||
data-test="logout-button"
|
||||
>
|
||||
{{ __('Log out') }}
|
||||
</flux:menu.item>
|
||||
</form>
|
||||
</flux:menu.radio.group>
|
||||
</flux:menu>
|
||||
</flux:dropdown>
|
||||
|
||||
<div class="hd-account-dropdown-divider"></div>
|
||||
|
||||
<nav class="hd-account-dropdown-nav" aria-label="Account">
|
||||
@if(Route::has('profile.edit'))
|
||||
<a href="{{ route('profile.edit') }}" wire:navigate role="menuitem">
|
||||
<i class="fa-solid fa-gear"></i>
|
||||
<span>Settings</span>
|
||||
</a>
|
||||
@endif
|
||||
</nav>
|
||||
|
||||
<div class="hd-account-dropdown-divider"></div>
|
||||
|
||||
<form method="POST" action="{{ route('logout') }}">
|
||||
@csrf
|
||||
<button class="hd-account-logout" type="submit" data-test="logout-button" role="menuitem">
|
||||
<i class="fa-solid fa-arrow-right-from-bracket"></i>
|
||||
<span>Log out</span>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
@@ -11,64 +11,57 @@
|
||||
$currentLabel = \Modules\Helpdesk\Support\Color::label($currentValue);
|
||||
@endphp
|
||||
|
||||
<div {{ $attributes->class('rounded-xl border border-zinc-200 bg-white p-3 shadow-sm dark:border-zinc-700 dark:bg-zinc-950/50') }}>
|
||||
<div class="grid gap-3 lg:grid-cols-[minmax(0,1fr)_auto] lg:items-end">
|
||||
<div class="min-w-0 space-y-2">
|
||||
<label class="block text-xs font-bold uppercase tracking-wide text-zinc-600 dark:text-zinc-300">
|
||||
{{ $label }}
|
||||
</label>
|
||||
<div {{ $attributes->class('hd-colour-control') }}>
|
||||
<div class="hd-colour-control__label">{{ $label }}</div>
|
||||
|
||||
<div class="grid gap-2 sm:grid-cols-[minmax(0,1fr)_auto] sm:items-center">
|
||||
<select
|
||||
wire:model.live="{{ $model }}"
|
||||
class="h-9 w-full rounded-lg border border-zinc-300 bg-white px-3 text-sm font-semibold text-zinc-900 shadow-sm outline-none transition-colors duration-150 focus:border-blue-500 focus:ring-2 focus:ring-blue-500/30 dark:border-zinc-700 dark:bg-zinc-900 dark:text-zinc-100"
|
||||
<div class="hd-colour-control__row">
|
||||
<select
|
||||
wire:model.live="{{ $model }}"
|
||||
class="hd-colour-control__select"
|
||||
aria-label="{{ $label }}"
|
||||
>
|
||||
@foreach ($options as $key => $option)
|
||||
<option value="{{ $key }}" style="color: {{ $option['hex'] }};">
|
||||
● {{ $option['name'] }} — {{ $option['hex'] }}
|
||||
</option>
|
||||
@endforeach
|
||||
</select>
|
||||
|
||||
<span
|
||||
class="hd-colour-control__badge"
|
||||
style="background-color: {{ $currentHex }}; color: {{ $currentText }};"
|
||||
>
|
||||
{{ $currentLabel }}
|
||||
</span>
|
||||
|
||||
<div class="hd-colour-control__swatches" aria-label="Available Helpdesk colours">
|
||||
@foreach ($options as $key => $option)
|
||||
<button
|
||||
type="button"
|
||||
wire:click="$set('{{ $model }}', '{{ $key }}')"
|
||||
title="{{ $option['name'] }} {{ $option['hex'] }}"
|
||||
class="hd-colour-control__swatch"
|
||||
style="background-color: {{ $option['hex'] }}"
|
||||
>
|
||||
@foreach ($options as $key => $option)
|
||||
<option value="{{ $key }}" style="color: {{ $option['hex'] }};">
|
||||
● {{ $option['name'] }} — {{ $option['hex'] }}
|
||||
</option>
|
||||
@endforeach
|
||||
</select>
|
||||
|
||||
<span
|
||||
class="inline-flex h-9 min-w-28 items-center justify-center rounded-lg px-3 text-xs font-bold shadow-sm ring-1 ring-zinc-900/10 dark:ring-white/10"
|
||||
style="background-color: {{ $currentHex }}; color: {{ $currentText }};"
|
||||
>
|
||||
{{ $currentLabel }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap items-center gap-1.5" aria-label="Available Helpdesk colours">
|
||||
@foreach ($options as $key => $option)
|
||||
<button
|
||||
type="button"
|
||||
wire:click="$set('{{ $model }}', '{{ $key }}')"
|
||||
title="{{ $option['name'] }} {{ $option['hex'] }}"
|
||||
class="inline-flex h-6 w-6 items-center justify-center rounded-full border border-white/80 shadow-sm ring-1 ring-zinc-900/10 transition hover:scale-105 focus:outline-none focus:ring-2 focus:ring-blue-500/40 dark:ring-white/15"
|
||||
style="background-color: {{ $option['hex'] }}"
|
||||
>
|
||||
<span class="sr-only">Use {{ $option['name'] }}</span>
|
||||
</button>
|
||||
@endforeach
|
||||
</div>
|
||||
<span class="sr-only">Use {{ $option['name'] }}</span>
|
||||
</button>
|
||||
@endforeach
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-[2.75rem_minmax(0,11rem)] items-center gap-2 lg:justify-end">
|
||||
<input
|
||||
type="color"
|
||||
value="{{ $currentHex }}"
|
||||
wire:change="$set('{{ $model }}', $event.target.value)"
|
||||
class="h-9 w-11 cursor-pointer rounded-lg border border-zinc-300 bg-white p-1 shadow-sm dark:border-zinc-700 dark:bg-zinc-900"
|
||||
aria-label="Choose a custom hex colour"
|
||||
/>
|
||||
<input
|
||||
type="color"
|
||||
value="{{ $currentHex }}"
|
||||
wire:change="$set('{{ $model }}', $event.target.value)"
|
||||
class="hd-colour-control__picker"
|
||||
aria-label="Choose a custom hex colour"
|
||||
/>
|
||||
|
||||
<input
|
||||
type="text"
|
||||
wire:model.live.debounce.500ms="{{ $model }}"
|
||||
class="h-9 w-full rounded-lg border border-zinc-300 bg-white px-3 text-xs font-semibold text-zinc-900 placeholder:text-zinc-400 shadow-sm outline-none transition-colors duration-150 focus:border-blue-500 focus:ring-2 focus:ring-blue-500/30 dark:border-zinc-700 dark:bg-zinc-900 dark:text-zinc-100 dark:placeholder:text-zinc-500"
|
||||
placeholder="#ffffff"
|
||||
aria-label="Custom hex colour"
|
||||
/>
|
||||
</div>
|
||||
<input
|
||||
type="text"
|
||||
wire:model.live.debounce.500ms="{{ $model }}"
|
||||
class="hd-colour-control__value"
|
||||
placeholder="#ffffff"
|
||||
aria-label="Custom hex colour"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<div class="flex items-start max-md:flex-col">
|
||||
<div class="hd-account-settings flex items-start max-md:flex-col">
|
||||
<div class="me-10 w-full pb-4 md:w-[220px]">
|
||||
<flux:navlist aria-label="{{ __('Settings') }}">
|
||||
<flux:navlist.item :href="route('profile.edit')" wire:navigate>{{ __('Profile') }}</flux:navlist.item>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
<x-layouts::app.sidebar :title="$title ?? null">
|
||||
{{ $slot }}
|
||||
</x-layouts::app.sidebar>
|
||||
@include('layouts.app.sidebar', [
|
||||
'slot' => $slot,
|
||||
'title' => $title ?? null,
|
||||
])
|
||||
|
||||
@@ -1,86 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}" class="dark">
|
||||
<head>@include('partials.head')</head>
|
||||
<body class="hd-app-body">
|
||||
@php
|
||||
$user = auth()->user();
|
||||
$isAdmin = $user !== null && (
|
||||
(bool) ($user->helpdesk_admin ?? false)
|
||||
|| (method_exists($user, 'isHelpdeskAdmin') && $user->isHelpdeskAdmin())
|
||||
);
|
||||
$roleLabel = $isAdmin ? 'Administrator' : ((method_exists($user, 'isHelpdeskAgent') && $user->isHelpdeskAgent()) || (bool) ($user->helpdesk_agent ?? false) ? 'Agent' : 'User');
|
||||
@endphp
|
||||
<div class="hd-app-shell" data-hd-shell>
|
||||
<header class="hd-topbar">
|
||||
<div class="hd-topbar-brand">
|
||||
<button class="hd-icon-button hd-mobile-menu-button" type="button" data-hd-sidebar-toggle aria-label="Toggle navigation"><i class="fa-solid fa-bars"></i></button>
|
||||
<a href="{{ route('dashboard') }}" wire:navigate class="hd-brand-name">{{ config('app.name', 'Laravel') }}</a>
|
||||
</div>
|
||||
<div class="hd-topbar-main">
|
||||
@if(Route::has('helpdesk.tickets.index'))
|
||||
<form method="GET" action="{{ route('helpdesk.tickets.index') }}" class="hd-top-search">
|
||||
<i class="fa-solid fa-magnifying-glass"></i><input name="search" placeholder="Search tickets" aria-label="Search tickets">
|
||||
</form>
|
||||
@endif
|
||||
<div class="hd-topbar-spacer"></div>
|
||||
<flux:dropdown position="bottom" align="end">
|
||||
<button class="hd-account-button" type="button">
|
||||
<span class="hd-user-avatar">{{ $user->initials() }}</span>
|
||||
<span class="hd-account-copy"><strong>{{ $user->name }}</strong><small>{{ $roleLabel }}</small></span>
|
||||
<i class="fa-solid fa-chevron-down text-xs"></i>
|
||||
</button>
|
||||
<flux:menu>
|
||||
@if(Route::has('profile.edit'))<flux:menu.item :href="route('profile.edit')" icon="user-circle" wire:navigate>Profile</flux:menu.item>@endif
|
||||
@if(Route::has('helpdesk.settings'))<flux:menu.item :href="route('helpdesk.settings')" icon="cog" wire:navigate>Helpdesk Settings</flux:menu.item>@endif
|
||||
@if($isAdmin)<flux:menu.item :href="url('/admin/users')" icon="user-circle" wire:navigate>User Management</flux:menu.item>@endif
|
||||
<flux:menu.separator />
|
||||
<form method="POST" action="{{ route('logout') }}">@csrf<flux:menu.item as="button" type="submit" icon="arrow-right-start-on-rectangle">Log out</flux:menu.item></form>
|
||||
</flux:menu>
|
||||
</flux:dropdown>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<aside class="hd-shell-sidebar" data-hd-sidebar>
|
||||
<nav class="hd-shell-nav" aria-label="Primary navigation">
|
||||
<div class="hd-nav-section">
|
||||
<div class="hd-nav-heading">Platform</div>
|
||||
|
||||
<a
|
||||
class="hd-nav-link {{ request()->routeIs('dashboard') ? 'is-active' : '' }}"
|
||||
href="{{ route('dashboard') }}"
|
||||
wire:navigate
|
||||
>
|
||||
<i class="fa-solid fa-house"></i>
|
||||
<span>Dashboard</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="hd-nav-section"><div class="hd-nav-heading">Helpdesk</div>
|
||||
@if(Route::has('helpdesk.tickets.index'))<a class="hd-nav-link {{ request()->routeIs('helpdesk.tickets.*') ? 'is-active' : '' }}" href="{{ route('helpdesk.tickets.index') }}" wire:navigate><i class="fa-solid fa-ticket"></i><span>Tickets</span></a>@endif
|
||||
@if(Route::has('helpdesk.kanban'))<a class="hd-nav-link {{ request()->routeIs('helpdesk.kanban') ? 'is-active' : '' }}" href="{{ route('helpdesk.kanban') }}" wire:navigate><i class="fa-solid fa-table-columns"></i><span>Kanban</span></a>@endif
|
||||
@if(Route::has('helpdesk.settings'))<a class="hd-nav-link {{ request()->routeIs('helpdesk.settings*') ? 'is-active' : '' }}" href="{{ route('helpdesk.settings') }}" wire:navigate><i class="fa-solid fa-gear"></i><span>Settings</span></a>@endif
|
||||
</div>
|
||||
@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 {{ 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-regular fa-file-lines"></i><span>Documents</span></a>
|
||||
@if(Route::has('documentation.categories.index'))<a class="hd-nav-link {{ request()->routeIs('documentation.categories.*') ? 'is-active' : '' }}" href="{{ route('documentation.categories.index') }}" wire:navigate><i class="fa-regular fa-folder"></i><span>Categories</span></a>@endif
|
||||
</div>
|
||||
@endif
|
||||
@if($isAdmin && Route::has('user-management.index'))
|
||||
<div class="hd-nav-section"><div class="hd-nav-heading">Users</div>
|
||||
<a class="hd-nav-link {{ request()->routeIs('user-management.index') || request()->routeIs('user-management.create') || request()->routeIs('user-management.edit') ? 'is-active' : '' }}" href="{{ route('user-management.index') }}" wire:navigate><i class="fa-solid fa-users"></i><span>Users</span></a>
|
||||
<a class="hd-nav-link {{ request()->routeIs('user-management.roles') ? 'is-active' : '' }}" href="{{ route('user-management.roles') }}" wire:navigate><i class="fa-solid fa-shield-halved"></i><span>Roles & Permissions</span></a>
|
||||
</div>
|
||||
@endif
|
||||
</nav>
|
||||
<div class="hd-sidebar-resizer" data-hd-sidebar-resizer title="Drag to resize sidebar"><span></span></div>
|
||||
</aside>
|
||||
<button type="button" class="hd-sidebar-backdrop" data-hd-sidebar-toggle aria-label="Close navigation"></button>
|
||||
|
||||
<main class="hd-shell-main">{{ $slot }}</main>
|
||||
</div>
|
||||
|
||||
@persist('toast')<flux:toast.group><flux:toast /></flux:toast.group>@endpersist
|
||||
@fluxScripts
|
||||
</body>
|
||||
</html>
|
||||
@@ -3,7 +3,7 @@
|
||||
<head>
|
||||
@include('partials.head')
|
||||
</head>
|
||||
<body class="min-h-screen bg-white antialiased dark:bg-linear-to-b dark:from-neutral-950 dark:to-neutral-900">
|
||||
<body class="hd-auth-surface min-h-screen bg-white antialiased dark:bg-linear-to-b dark:from-neutral-950 dark:to-neutral-900">
|
||||
<div class="bg-background flex min-h-svh flex-col items-center justify-center gap-6 p-6 md:p-10">
|
||||
<div class="flex w-full max-w-sm flex-col gap-2">
|
||||
<a href="{{ route('home') }}" class="flex flex-col items-center gap-2 font-medium" wire:navigate>
|
||||
|
||||
@@ -11,7 +11,8 @@
|
||||
columns are configured in Helpdesk Settings.</p>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<div class="flex w-full flex-col gap-3 lg:w-auto lg:min-w-[420px]">
|
||||
<div class="flex flex-wrap justify-start gap-2 lg:justify-end">
|
||||
<a href="{{ route('helpdesk.index') }}" wire:navigate class="hd-btn hd-btn-blue">
|
||||
<i class="fa-solid fa-gauge-high" aria-hidden="true"></i>
|
||||
<span>Dashboard</span>
|
||||
@@ -31,6 +32,39 @@
|
||||
<i class="fa-solid fa-list" aria-hidden="true"></i>
|
||||
<span>List view</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@if ($boards->isNotEmpty())
|
||||
<div class="hd-kanban-active-board-card">
|
||||
<div class="min-w-0">
|
||||
<p class="hd-kanban-active-board-label">Active board</p>
|
||||
<h2 class="hd-kanban-active-board-title">{{ $board?->name ?? 'None' }}</h2>
|
||||
<p class="hd-kanban-active-board-description">
|
||||
{{ $board?->description ?: 'Select the workflow board to display.' }}
|
||||
</p>
|
||||
@if ($board?->owner)
|
||||
<p class="hd-kanban-active-board-owner">
|
||||
<i class="fa-solid fa-user-shield" aria-hidden="true"></i>
|
||||
{{ $board->owner->name }}
|
||||
</p>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<div class="hd-select-wrap hd-kanban-board-selector">
|
||||
<select
|
||||
class="hd-select"
|
||||
aria-label="Select Kanban board"
|
||||
onchange="window.location.href = '{{ route('helpdesk.kanban') }}?board_id=' + encodeURIComponent(this.value)"
|
||||
>
|
||||
@foreach ($boards as $availableBoard)
|
||||
<option value="{{ $availableBoard->id }}" @selected((int) $board_id === (int) $availableBoard->id)>
|
||||
{{ $availableBoard->name }}{{ $availableBoard->owner ? ' · ' . $availableBoard->owner->name : '' }}
|
||||
</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -41,34 +75,6 @@
|
||||
No Kanban boards exist yet. Open Helpdesk Settings and create a board with at least one status column.
|
||||
</div>
|
||||
@else
|
||||
<div
|
||||
class="flex flex-col gap-4 rounded-2xl border border-zinc-200 bg-white p-4 shadow-sm dark:border-zinc-700 dark:bg-zinc-900 md:flex-row md:items-center md:justify-between">
|
||||
<div class="min-w-0">
|
||||
<h2 class="text-sm font-semibold text-zinc-900 dark:text-zinc-100">Active board: {{ $board?->name ?? 'None' }}</h2>
|
||||
<p class="text-xs text-zinc-500 dark:text-zinc-400">
|
||||
{{ $board?->description ?: 'Use settings to configure which statuses appear as draggable columns.' }}
|
||||
</p>
|
||||
@if ($board?->owner)
|
||||
<p class="mt-1 text-xs font-semibold text-emerald-600 dark:text-emerald-300">Owner:
|
||||
{{ $board->owner->name }}</p>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<div class="hd-select-wrap shrink-0 md:w-72">
|
||||
<select
|
||||
class="hd-select"
|
||||
aria-label="Select Kanban board"
|
||||
onchange="window.location.href = '{{ route('helpdesk.kanban') }}?board_id=' + encodeURIComponent(this.value)"
|
||||
>
|
||||
@foreach ($boards as $availableBoard)
|
||||
<option value="{{ $availableBoard->id }}" @selected((int) $board_id === (int) $availableBoard->id)>
|
||||
{{ $availableBoard->name }}{{ $availableBoard->owner ? ' · ' . $availableBoard->owner->name : '' }}
|
||||
</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if ($statuses->isEmpty())
|
||||
<div
|
||||
class="rounded-2xl border border-amber-500/40 bg-amber-950/70 px-5 py-4 text-sm font-semibold text-amber-100">
|
||||
@@ -111,12 +117,7 @@
|
||||
draggable="true" data-helpdesk-kanban-ticket data-ticket-id="{{ $ticket->id }}"
|
||||
wire:key="kanban-ticket-{{ $ticket->id }}-{{ $ticket->status_id }}-{{ $ticket->board_id }}-{{ optional($ticket->updated_at)->timestamp }}"
|
||||
dusk="kanban-ticket-{{ $ticket->id }}">
|
||||
<div class="mb-2 flex justify-end">
|
||||
<span class="hd-kanban-drag-handle" draggable="true" data-kanban-drag-handle title="Drag ticket" aria-label="Drag {{ $ticket->reference }}">
|
||||
<i class="fa-solid fa-grip-vertical"></i>
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex items-start justify-between gap-3">
|
||||
<div class="flex items-start justify-between gap-3">
|
||||
<div class="min-w-0">
|
||||
<div
|
||||
class="font-mono text-xs font-semibold text-blue-700 dark:text-blue-300">
|
||||
|
||||
+14
-14
@@ -122,7 +122,7 @@
|
||||
class="hd-settings-modal w-full max-w-4xl"
|
||||
@close="closeStatusModal"
|
||||
>
|
||||
<div class="mb-5 border-b border-zinc-200 pb-4 dark:border-zinc-700">
|
||||
<div class="hd-settings-modal-header hd-settings-modal-header--status">
|
||||
<flux:heading size="lg">{{ filled($statusForm['id'] ?? null) ? 'Edit status' : 'Create status' }}</flux:heading>
|
||||
<flux:subheading class="mt-1">Create or edit this status without leaving the settings list.</flux:subheading>
|
||||
</div>
|
||||
@@ -183,7 +183,7 @@
|
||||
class="hd-settings-modal w-full max-w-4xl"
|
||||
@close="closePriorityModal"
|
||||
>
|
||||
<div class="mb-5 border-b border-zinc-200 pb-4 dark:border-zinc-700">
|
||||
<div class="hd-settings-modal-header hd-settings-modal-header--priority">
|
||||
<flux:heading size="lg">{{ filled($priorityForm['id'] ?? null) ? 'Edit priority' : 'Create priority' }}</flux:heading>
|
||||
<flux:subheading class="mt-1">Create or edit this priority without leaving the settings list.</flux:subheading>
|
||||
</div>
|
||||
@@ -240,7 +240,7 @@
|
||||
class="hd-settings-modal w-full max-w-4xl"
|
||||
@close="closeCategoryModal"
|
||||
>
|
||||
<div class="mb-5 border-b border-zinc-200 pb-4 dark:border-zinc-700">
|
||||
<div class="hd-settings-modal-header hd-settings-modal-header--category">
|
||||
<flux:heading size="lg">{{ filled($categoryForm['id'] ?? null) ? 'Edit category' : 'Create category' }}</flux:heading>
|
||||
<flux:subheading class="mt-1">Create or edit this category without leaving the settings list.</flux:subheading>
|
||||
</div>
|
||||
@@ -300,7 +300,7 @@
|
||||
@close="closeTeamModal"
|
||||
>
|
||||
<form wire:submit="saveTeam" class="space-y-5">
|
||||
<div class="flex items-start justify-between gap-4 border-b border-zinc-200 pb-4 dark:border-zinc-700">
|
||||
<div class="hd-settings-modal-header hd-settings-modal-header--team flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<flux:heading size="lg">
|
||||
{{ filled($teamForm['id'] ?? null) ? 'Edit team' : 'Create team' }}
|
||||
@@ -399,7 +399,7 @@
|
||||
class="hd-settings-modal w-full max-w-5xl"
|
||||
@close="closeProjectModal"
|
||||
>
|
||||
<div class="mb-5 border-b border-zinc-200 pb-4 dark:border-zinc-700">
|
||||
<div class="hd-settings-modal-header hd-settings-modal-header--project">
|
||||
<flux:heading size="lg">{{ filled($projectForm['id'] ?? null) ? 'Edit project' : 'Create project' }}</flux:heading>
|
||||
<flux:subheading class="mt-1">Create or edit this project without leaving the settings list.</flux:subheading>
|
||||
</div>
|
||||
@@ -467,7 +467,7 @@
|
||||
class="hd-settings-modal w-full max-w-6xl"
|
||||
@close="closeBoardModal"
|
||||
>
|
||||
<div class="mb-5 border-b border-zinc-200 pb-4 dark:border-zinc-700">
|
||||
<div class="hd-settings-modal-header hd-settings-modal-header--board">
|
||||
<flux:heading size="lg">{{ filled($boardForm['id'] ?? null) ? 'Edit board' : 'Create board' }}</flux:heading>
|
||||
<flux:subheading class="mt-1">Create or edit this board without leaving the settings list.</flux:subheading>
|
||||
</div>
|
||||
@@ -599,7 +599,7 @@
|
||||
class="hd-settings-modal w-full max-w-5xl"
|
||||
@close="closeRepositoryModal"
|
||||
>
|
||||
<div class="mb-5 border-b border-zinc-200 pb-4 dark:border-zinc-700">
|
||||
<div class="hd-settings-modal-header hd-settings-modal-header--git">
|
||||
<flux:heading size="lg">{{ filled($repositoryForm['id'] ?? null) ? 'Edit repository' : 'Create repository' }}</flux:heading>
|
||||
<flux:subheading class="mt-1">Create or edit this repository without leaving the settings list.</flux:subheading>
|
||||
</div>
|
||||
@@ -657,7 +657,7 @@
|
||||
class="hd-settings-modal w-full max-w-6xl"
|
||||
@close="closeWorkflowTemplateModal"
|
||||
>
|
||||
<div class="mb-5 border-b border-zinc-200 pb-4 dark:border-zinc-700">
|
||||
<div class="hd-settings-modal-header hd-settings-modal-header--workflow">
|
||||
<flux:heading size="lg">{{ filled($workflowForm['id'] ?? null) ? 'Edit workflow message' : 'Create workflow message' }}</flux:heading>
|
||||
<flux:subheading class="mt-1">Create or edit this workflow message without leaving the settings list.</flux:subheading>
|
||||
</div>
|
||||
@@ -777,7 +777,7 @@
|
||||
class="hd-settings-modal w-full max-w-6xl"
|
||||
@close="closeCannedResponseModal"
|
||||
>
|
||||
<div class="mb-5 border-b border-zinc-200 pb-4 dark:border-zinc-700">
|
||||
<div class="hd-settings-modal-header hd-settings-modal-header--canned">
|
||||
<flux:heading size="lg">{{ filled($cannedForm['id'] ?? null) ? 'Edit canned response' : 'Create canned response' }}</flux:heading>
|
||||
<flux:subheading class="mt-1">Create or edit this canned response without leaving the settings list.</flux:subheading>
|
||||
</div>
|
||||
@@ -875,7 +875,7 @@
|
||||
class="hd-settings-modal w-full max-w-5xl"
|
||||
@close="closeSlaPolicyModal"
|
||||
>
|
||||
<div class="mb-5 border-b border-zinc-200 pb-4 dark:border-zinc-700">
|
||||
<div class="hd-settings-modal-header hd-settings-modal-header--sla">
|
||||
<flux:heading size="lg">{{ filled($slaForm['id'] ?? null) ? 'Edit SLA policy' : 'Create SLA policy' }}</flux:heading>
|
||||
<flux:subheading class="mt-1">Create or edit this SLA policy without leaving the settings list.</flux:subheading>
|
||||
</div>
|
||||
@@ -984,7 +984,7 @@
|
||||
class="hd-settings-modal w-full max-w-6xl"
|
||||
@close="closeAutomationRuleModal"
|
||||
>
|
||||
<div class="mb-5 border-b border-zinc-200 pb-4 dark:border-zinc-700">
|
||||
<div class="hd-settings-modal-header hd-settings-modal-header--automation">
|
||||
<flux:heading size="lg">{{ filled($automationForm['id'] ?? null) ? 'Edit automation rule' : 'Create automation rule' }}</flux:heading>
|
||||
<flux:subheading class="mt-1">Create or edit this automation rule without leaving the settings list.</flux:subheading>
|
||||
</div>
|
||||
@@ -1087,7 +1087,7 @@
|
||||
class="hd-settings-modal w-full max-w-5xl"
|
||||
@close="closeChecklistTemplateModal"
|
||||
>
|
||||
<div class="mb-5 border-b border-zinc-200 pb-4 dark:border-zinc-700">
|
||||
<div class="hd-settings-modal-header hd-settings-modal-header--checklist">
|
||||
<flux:heading size="lg">{{ filled($checklistTemplateForm['id'] ?? null) ? 'Edit checklist template' : 'Create checklist' }}</flux:heading>
|
||||
<flux:subheading class="mt-1">Create or edit this checklist template without leaving the settings list.</flux:subheading>
|
||||
</div>
|
||||
@@ -1170,7 +1170,7 @@
|
||||
class="hd-settings-modal w-full max-w-4xl"
|
||||
@close="closeTagModal"
|
||||
>
|
||||
<div class="mb-5 border-b border-zinc-200 pb-4 dark:border-zinc-700">
|
||||
<div class="hd-settings-modal-header hd-settings-modal-header--tag">
|
||||
<flux:heading size="lg">{{ filled($tagForm['id'] ?? null) ? 'Edit tag' : 'Create tag' }}</flux:heading>
|
||||
<flux:subheading class="mt-1">Create or edit this tag without leaving the settings list.</flux:subheading>
|
||||
</div>
|
||||
@@ -1238,7 +1238,7 @@
|
||||
class="hd-settings-modal w-full max-w-5xl"
|
||||
@close="closeProductModal"
|
||||
>
|
||||
<div class="mb-5 border-b border-zinc-200 pb-4 dark:border-zinc-700">
|
||||
<div class="hd-settings-modal-header hd-settings-modal-header--product">
|
||||
<flux:heading size="lg">{{ filled($productForm['id'] ?? null) ? 'Edit product/service' : 'Create product' }}</flux:heading>
|
||||
<flux:subheading class="mt-1">Create or edit this product/service without leaving the settings list.</flux:subheading>
|
||||
</div>
|
||||
|
||||
@@ -25,28 +25,42 @@
|
||||
|
||||
<div class="rounded-2xl border border-zinc-200 bg-white p-4 shadow-sm dark:border-zinc-700 dark:bg-zinc-900">
|
||||
<div class="grid gap-4 md:grid-cols-4">
|
||||
<flux:input wire:model.live.debounce.350ms="search" icon="magnifying-glass"
|
||||
placeholder="Search reference, subject or body" />
|
||||
<div class="hd-ticket-search-wrap">
|
||||
<i class="fa-solid fa-magnifying-glass hd-ticket-search-icon" aria-hidden="true"></i>
|
||||
<input
|
||||
type="search"
|
||||
class="hd-ticket-search"
|
||||
wire:model.live.debounce.350ms="search"
|
||||
placeholder="Search reference, subject or body"
|
||||
aria-label="Search tickets"
|
||||
>
|
||||
</div>
|
||||
|
||||
<flux:select wire:model.live="scope">
|
||||
<option value="open">Open tickets</option>
|
||||
<option value="closed">Closed tickets</option>
|
||||
<option value="all">All tickets</option>
|
||||
</flux:select>
|
||||
<div class="hd-select-wrap">
|
||||
<select class="hd-select" wire:model.live="scope" aria-label="Ticket scope">
|
||||
<option value="open">Open tickets</option>
|
||||
<option value="closed">Closed tickets</option>
|
||||
<option value="all">All tickets</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<flux:select wire:model.live="status">
|
||||
<option value="">All statuses</option>
|
||||
@foreach ($statuses as $status)
|
||||
<option value="{{ $status->id }}">{{ $status->name }}</option>
|
||||
@endforeach
|
||||
</flux:select>
|
||||
<div class="hd-select-wrap">
|
||||
<select class="hd-select" wire:model.live="status" aria-label="Ticket status">
|
||||
<option value="">All statuses</option>
|
||||
@foreach ($statuses as $status)
|
||||
<option value="{{ $status->id }}">{{ $status->name }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<flux:select wire:model.live="priority">
|
||||
<option value="">All priorities</option>
|
||||
@foreach ($priorities as $priority)
|
||||
<option value="{{ $priority->id }}">{{ $priority->name }}</option>
|
||||
@endforeach
|
||||
</flux:select>
|
||||
<div class="hd-select-wrap">
|
||||
<select class="hd-select" wire:model.live="priority" aria-label="Ticket priority">
|
||||
<option value="">All priorities</option>
|
||||
@foreach ($priorities as $priority)
|
||||
<option value="{{ $priority->id }}">{{ $priority->name }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -150,8 +164,23 @@
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="border-t border-zinc-200 p-4 dark:border-zinc-700">
|
||||
{{ $tickets->links() }}
|
||||
<div class="flex flex-col gap-3 border-t border-zinc-200 bg-zinc-50/70 px-4 py-4 dark:border-zinc-700 dark:bg-zinc-950/35 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div class="text-sm font-medium text-zinc-500 dark:text-zinc-400">
|
||||
Showing
|
||||
<span class="font-semibold text-zinc-800 dark:text-zinc-200">{{ $tickets->firstItem() ?? 0 }}</span>
|
||||
–
|
||||
<span class="font-semibold text-zinc-800 dark:text-zinc-200">{{ $tickets->lastItem() ?? 0 }}</span>
|
||||
of
|
||||
<span class="font-semibold text-zinc-800 dark:text-zinc-200">{{ $tickets->total() }}</span> tickets
|
||||
</div>
|
||||
|
||||
<div class="min-w-0">
|
||||
@if ($tickets->hasPages())
|
||||
{{ $tickets->onEachSide(1)->links() }}
|
||||
@else
|
||||
<span class="inline-flex rounded-lg border border-zinc-200 bg-white px-3 py-1.5 text-xs font-semibold text-zinc-500 dark:border-zinc-700 dark:bg-zinc-900">Page 1 of 1</span>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user