@escalated-dev/escalated 0.6.0 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -21
- package/README.md +125 -2
- package/package.json +40 -5
- package/src/components/ActiveChatsPanel.vue +246 -0
- package/src/components/ActivityTimeline.stories.js +55 -0
- package/src/components/ActivityTimeline.vue +83 -66
- package/src/components/AdminDashboard.stories.js +1423 -0
- package/src/components/AgentLoadIndicator.stories.js +37 -0
- package/src/components/AgentLoadIndicator.vue +49 -0
- package/src/components/AgentPerformanceCard.vue +44 -0
- package/src/components/ArticleEditor.vue +82 -0
- package/src/components/AssigneeSelect.vue +37 -23
- package/src/components/AttachmentList.vue +65 -37
- package/src/components/AuditLogEntry.vue +95 -0
- package/src/components/BulkActionBar.vue +166 -97
- package/src/components/CategoryTree.vue +89 -0
- package/src/components/ChartWidget.vue +46 -0
- package/src/components/ChatActionBar.vue +226 -0
- package/src/components/ChatBubble.vue +162 -0
- package/src/components/ChatComposer.vue +308 -0
- package/src/components/ChatQueue.vue +222 -0
- package/src/components/ChatThread.vue +197 -0
- package/src/components/CollisionWarning.stories.js +30 -0
- package/src/components/CollisionWarning.vue +68 -0
- package/src/components/ConditionalFieldRules.vue +135 -0
- package/src/components/ContextPanel.vue +66 -0
- package/src/components/ContextPanelSection.vue +65 -0
- package/src/components/CustomFieldRenderer.vue +159 -0
- package/src/components/EscalatedLayout.vue +424 -70
- package/src/components/FileDropzone.vue +57 -41
- package/src/components/FollowButton.stories.js +30 -0
- package/src/components/FollowButton.vue +88 -63
- package/src/components/KeyboardShortcutHelp.vue +213 -123
- package/src/components/KnowledgePanel.vue +122 -0
- package/src/components/KpiCard.stories.js +32 -0
- package/src/components/KpiCard.vue +88 -0
- package/src/components/MacroDropdown.vue +150 -90
- package/src/components/MentionDropdown.vue +154 -0
- package/src/components/MetricComparison.vue +89 -0
- package/src/components/PercentileChart.vue +67 -0
- package/src/components/PeriodSelector.vue +98 -0
- package/src/components/PermissionMatrix.vue +107 -0
- package/src/components/PinnedNotes.vue +129 -90
- package/src/components/PluginSlot.vue +68 -62
- package/src/components/PresenceIndicator.vue +138 -28
- package/src/components/PriorityBadge.stories.js +33 -0
- package/src/components/PriorityBadge.vue +46 -39
- package/src/components/QuickFilters.vue +65 -49
- package/src/components/ReplyComposer.vue +207 -30
- package/src/components/ReplyThread.vue +111 -21
- package/src/components/ReportChart.vue +395 -0
- package/src/components/ReportExportButton.vue +108 -0
- package/src/components/SatisfactionRating.vue +180 -128
- package/src/components/SaveViewDialog.vue +172 -0
- package/src/components/SavedViewSidebar.vue +208 -0
- package/src/components/ScheduleEditor.vue +104 -0
- package/src/components/SideConversation.vue +165 -0
- package/src/components/SideConversationList.vue +147 -0
- package/src/components/SkillTagManager.vue +79 -0
- package/src/components/SlaComplianceChart.vue +46 -0
- package/src/components/SlaTimer.stories.js +35 -0
- package/src/components/SlaTimer.vue +62 -49
- package/src/components/SnoozeButton.vue +247 -0
- package/src/components/StatsCard.stories.js +24 -0
- package/src/components/StatsCard.vue +18 -6
- package/src/components/StatusBadge.stories.js +53 -0
- package/src/components/StatusBadge.vue +78 -46
- package/src/components/TagSelect.vue +84 -53
- package/src/components/TicketFilters.vue +247 -46
- package/src/components/TicketLinkPanel.vue +193 -0
- package/src/components/TicketList.vue +536 -74
- package/src/components/TicketMergeDialog.vue +157 -0
- package/src/components/TicketSidebar.vue +167 -99
- package/src/components/TicketSplitDialog.vue +98 -0
- package/src/components/TicketTypeSelector.vue +21 -0
- package/src/components/TotpInput.stories.js +9 -0
- package/src/components/TotpInput.vue +81 -0
- package/src/components/TwoFactorSetup.vue +113 -0
- package/src/components/VariableTokenInput.vue +111 -0
- package/src/components/WorkflowActionConfig.vue +287 -0
- package/src/components/WorkflowActionList.vue +350 -0
- package/src/components/WorkflowConditionBuilder.vue +265 -0
- package/src/components/WorkflowTriggerSelector.vue +125 -0
- package/src/composables/useChat.js +187 -0
- package/src/composables/useDebouncedSearch.js +38 -0
- package/src/composables/useI18n.js +80 -80
- package/src/composables/useKeyboardShortcuts.js +46 -46
- package/src/composables/useMentions.js +134 -0
- package/src/composables/usePluginExtensions.js +159 -67
- package/src/composables/usePluginHooks.js +65 -0
- package/src/composables/useRealtime.js +182 -0
- package/src/defineEscalatedPlugin.js +76 -50
- package/src/index.js +77 -28
- package/src/locales/ar.json +727 -0
- package/src/locales/de.json +678 -430
- package/src/locales/en.json +717 -430
- package/src/locales/es.json +679 -430
- package/src/locales/fr.json +680 -431
- package/src/locales/index.js +12 -2
- package/src/locales/it.json +727 -0
- package/src/locales/ja.json +727 -0
- package/src/locales/ko.json +727 -0
- package/src/locales/nl.json +727 -0
- package/src/locales/pl.json +727 -0
- package/src/locales/pt-BR.json +727 -0
- package/src/locales/ru.json +727 -0
- package/src/locales/tr.json +727 -0
- package/src/locales/zh-CN.json +727 -0
- package/src/pages/Admin/ApiTokens/Index.vue +381 -217
- package/src/pages/Admin/AuditLog/Index.vue +154 -0
- package/src/pages/Admin/BusinessHours/Form.vue +172 -0
- package/src/pages/Admin/BusinessHours/Index.vue +113 -0
- package/src/pages/Admin/CannedResponses/Index.vue +82 -25
- package/src/pages/Admin/Capacity/Index.vue +134 -0
- package/src/pages/Admin/CustomFields/Form.vue +195 -0
- package/src/pages/Admin/CustomFields/Index.vue +134 -0
- package/src/pages/Admin/CustomObjects/Form.vue +202 -0
- package/src/pages/Admin/CustomObjects/Index.vue +127 -0
- package/src/pages/Admin/CustomObjects/Records.vue +228 -0
- package/src/pages/Admin/Departments/Form.vue +35 -11
- package/src/pages/Admin/Departments/Index.vue +74 -21
- package/src/pages/Admin/EscalationRules/Form.vue +86 -25
- package/src/pages/Admin/EscalationRules/Index.vue +158 -37
- package/src/pages/Admin/Import/Connect.vue +95 -0
- package/src/pages/Admin/Import/Index.vue +102 -0
- package/src/pages/Admin/Import/Mapping.vue +110 -0
- package/src/pages/Admin/Import/Progress.vue +153 -0
- package/src/pages/Admin/Import/Review.vue +76 -0
- package/src/pages/Admin/KnowledgeBase/Articles/Form.vue +142 -0
- package/src/pages/Admin/KnowledgeBase/Articles/Index.vue +195 -0
- package/src/pages/Admin/KnowledgeBase/Categories/Index.vue +193 -0
- package/src/pages/Admin/Macros/Index.vue +448 -287
- package/src/pages/Admin/Plugins/Index.vue +327 -197
- package/src/pages/Admin/Reports/AgentDetail.vue +132 -0
- package/src/pages/Admin/Reports/AgentMetrics.vue +115 -0
- package/src/pages/Admin/Reports/AgentRanking.vue +246 -0
- package/src/pages/Admin/Reports/Cohorts.vue +180 -0
- package/src/pages/Admin/Reports/Comparison.vue +141 -0
- package/src/pages/Admin/Reports/CsatReport.vue +127 -0
- package/src/pages/Admin/Reports/Dashboard.vue +226 -0
- package/src/pages/Admin/Reports/ResolutionTimes.vue +164 -0
- package/src/pages/Admin/Reports/ResponseTimes.vue +164 -0
- package/src/pages/Admin/Reports/SlaReport.vue +162 -0
- package/src/pages/Admin/Reports/SlaTrends.vue +183 -0
- package/src/pages/Admin/Reports.vue +206 -101
- package/src/pages/Admin/Roles/Form.vue +71 -0
- package/src/pages/Admin/Roles/Index.vue +122 -0
- package/src/pages/Admin/Settings/CsatSettings.vue +165 -0
- package/src/pages/Admin/Settings/DataRetention.vue +182 -0
- package/src/pages/Admin/Settings/EmailSettings.vue +243 -0
- package/src/pages/Admin/Settings/Sandbox.vue +135 -0
- package/src/pages/Admin/Settings/SsoSettings.vue +218 -0
- package/src/pages/Admin/Settings/TwoFactor.vue +110 -0
- package/src/pages/Admin/Settings.vue +847 -99
- package/src/pages/Admin/Skills/Form.vue +53 -0
- package/src/pages/Admin/Skills/Index.vue +94 -0
- package/src/pages/Admin/SlaPolicies/Form.vue +62 -19
- package/src/pages/Admin/SlaPolicies/Index.vue +85 -23
- package/src/pages/Admin/Statuses/Form.vue +114 -0
- package/src/pages/Admin/Statuses/Index.vue +153 -0
- package/src/pages/Admin/Tags/Index.vue +121 -35
- package/src/pages/Admin/Tickets/Index.vue +96 -9
- package/src/pages/Admin/Tickets/Show.vue +130 -37
- package/src/pages/Admin/Webhooks/DeliveryLog.vue +120 -0
- package/src/pages/Admin/Webhooks/Form.vue +110 -0
- package/src/pages/Admin/Webhooks/Index.vue +136 -0
- package/src/pages/Admin/Workflows/Builder.vue +207 -0
- package/src/pages/Admin/Workflows/Index.vue +235 -0
- package/src/pages/Admin/Workflows/Logs.vue +286 -0
- package/src/pages/Agent/Dashboard.vue +311 -186
- package/src/pages/Agent/TicketIndex.vue +80 -6
- package/src/pages/Agent/TicketShow.vue +399 -60
- package/src/pages/Customer/Create.vue +37 -10
- package/src/pages/Customer/Index.vue +9 -2
- package/src/pages/Customer/KnowledgeBase/Article.vue +129 -0
- package/src/pages/Customer/KnowledgeBase/Index.vue +151 -0
- package/src/pages/Customer/Show.vue +69 -63
- package/src/pages/Guest/Create.vue +40 -15
- package/src/pages/Guest/Show.vue +124 -93
- package/src/plugin.js +111 -1
- package/src/utils/constants.js +30 -0
- package/src/utils/formatting.js +35 -0
- package/src/utils/mentionHighlight.js +24 -0
- package/src/utils/sanitizeHtml.js +78 -64
- package/src/widget/EscalatedWidget.vue +804 -0
- package/src/widget/index.js +254 -0
- package/src/pages/Admin/Plugins.vue +0 -129
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import EscalatedLayout from '../../../components/EscalatedLayout.vue';
|
|
3
|
+
import { useForm } from '@inertiajs/vue3';
|
|
4
|
+
|
|
5
|
+
const props = defineProps({
|
|
6
|
+
object: { type: Object, default: null },
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
const isEdit = !!props.object;
|
|
10
|
+
|
|
11
|
+
const form = useForm({
|
|
12
|
+
name: props.object?.name || '',
|
|
13
|
+
slug: props.object?.slug || '',
|
|
14
|
+
fields_schema: props.object?.fields_schema || [],
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
const fieldTypes = [
|
|
18
|
+
{ value: 'text', label: 'Text' },
|
|
19
|
+
{ value: 'number', label: 'Number' },
|
|
20
|
+
{ value: 'select', label: 'Select' },
|
|
21
|
+
{ value: 'date', label: 'Date' },
|
|
22
|
+
{ value: 'lookup', label: 'Lookup' },
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
function addField() {
|
|
26
|
+
form.fields_schema.push({
|
|
27
|
+
name: '',
|
|
28
|
+
type: 'text',
|
|
29
|
+
required: false,
|
|
30
|
+
options: [],
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function removeField(index) {
|
|
35
|
+
form.fields_schema.splice(index, 1);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function autoSlug() {
|
|
39
|
+
if (!isEdit) {
|
|
40
|
+
form.slug = form.name
|
|
41
|
+
.toLowerCase()
|
|
42
|
+
.replace(/[^a-z0-9]+/g, '_')
|
|
43
|
+
.replace(/(^_|_$)/g, '');
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function submit() {
|
|
48
|
+
if (isEdit) {
|
|
49
|
+
form.put(route('escalated.admin.custom-objects.update', props.object.id));
|
|
50
|
+
} else {
|
|
51
|
+
form.post(route('escalated.admin.custom-objects.store'));
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
</script>
|
|
55
|
+
|
|
56
|
+
<template>
|
|
57
|
+
<EscalatedLayout :title="isEdit ? 'Edit Custom Object' : 'New Custom Object'">
|
|
58
|
+
<form class="mx-auto max-w-2xl space-y-6" @submit.prevent="submit">
|
|
59
|
+
<!-- Basic Info -->
|
|
60
|
+
<div class="rounded-xl border border-[var(--esc-panel-border)] bg-[var(--esc-panel-surface)] p-6">
|
|
61
|
+
<h3 class="mb-5 text-sm font-semibold text-[var(--esc-panel-text)]">Object Details</h3>
|
|
62
|
+
<div class="space-y-4">
|
|
63
|
+
<div>
|
|
64
|
+
<label class="block text-sm font-medium text-[var(--esc-panel-text-secondary)]">Name</label>
|
|
65
|
+
<input
|
|
66
|
+
v-model="form.name"
|
|
67
|
+
type="text"
|
|
68
|
+
placeholder="e.g. Product, Contract"
|
|
69
|
+
class="mt-1 w-full rounded-lg border border-[var(--esc-panel-border-input)] bg-[var(--esc-panel-surface-alt)] px-3 py-2 text-sm text-[var(--esc-panel-text-secondary)] placeholder-[var(--esc-panel-text-muted)] focus:border-[var(--esc-panel-border-input)] focus:outline-none focus:ring-1 focus:ring-[var(--esc-panel-border-input)]"
|
|
70
|
+
@input="autoSlug"
|
|
71
|
+
/>
|
|
72
|
+
<div v-if="form.errors.name" class="mt-1 text-sm text-rose-400">{{ form.errors.name }}</div>
|
|
73
|
+
</div>
|
|
74
|
+
<div>
|
|
75
|
+
<label class="block text-sm font-medium text-[var(--esc-panel-text-secondary)]">Slug</label>
|
|
76
|
+
<input
|
|
77
|
+
v-model="form.slug"
|
|
78
|
+
type="text"
|
|
79
|
+
:readonly="isEdit"
|
|
80
|
+
class="mt-1 w-full rounded-lg border border-[var(--esc-panel-border-input)] bg-[var(--esc-panel-surface-alt)] px-3 py-2 text-sm font-mono text-[var(--esc-panel-text-secondary)] placeholder-[var(--esc-panel-text-muted)] focus:border-[var(--esc-panel-border-input)] focus:outline-none focus:ring-1 focus:ring-[var(--esc-panel-border-input)]"
|
|
81
|
+
:class="{ 'bg-[var(--esc-panel-surface)] text-[var(--esc-panel-text-muted)]': isEdit }"
|
|
82
|
+
/>
|
|
83
|
+
<div v-if="form.errors.slug" class="mt-1 text-sm text-rose-400">{{ form.errors.slug }}</div>
|
|
84
|
+
</div>
|
|
85
|
+
</div>
|
|
86
|
+
</div>
|
|
87
|
+
|
|
88
|
+
<!-- Fields Schema -->
|
|
89
|
+
<div class="rounded-xl border border-[var(--esc-panel-border)] bg-[var(--esc-panel-surface)] p-6">
|
|
90
|
+
<div class="mb-5 flex items-center justify-between">
|
|
91
|
+
<h3 class="text-sm font-semibold text-[var(--esc-panel-text)]">Fields</h3>
|
|
92
|
+
<button
|
|
93
|
+
type="button"
|
|
94
|
+
class="rounded-lg border border-[var(--esc-panel-border-input)] px-3 py-1.5 text-sm text-[var(--esc-panel-text-tertiary)] hover:border-[var(--esc-panel-border-input)] hover:text-[var(--esc-panel-text-secondary)]"
|
|
95
|
+
@click="addField"
|
|
96
|
+
>
|
|
97
|
+
+ Add Field
|
|
98
|
+
</button>
|
|
99
|
+
</div>
|
|
100
|
+
|
|
101
|
+
<div v-if="form.fields_schema.length" class="space-y-3">
|
|
102
|
+
<div
|
|
103
|
+
v-for="(field, idx) in form.fields_schema"
|
|
104
|
+
:key="idx"
|
|
105
|
+
class="rounded-lg border border-[var(--esc-panel-border)] bg-[var(--esc-panel-surface-alt)] p-4"
|
|
106
|
+
>
|
|
107
|
+
<div class="grid grid-cols-1 gap-3 sm:grid-cols-4">
|
|
108
|
+
<div class="sm:col-span-2">
|
|
109
|
+
<label class="block text-xs font-medium text-[var(--esc-panel-text-tertiary)]"
|
|
110
|
+
>Field Name</label
|
|
111
|
+
>
|
|
112
|
+
<input
|
|
113
|
+
v-model="field.name"
|
|
114
|
+
type="text"
|
|
115
|
+
placeholder="e.g. serial_number"
|
|
116
|
+
class="mt-1 w-full rounded-lg border border-[var(--esc-panel-border-input)] bg-[var(--esc-panel-surface)] px-3 py-2 text-sm text-[var(--esc-panel-text-secondary)] placeholder-[var(--esc-panel-text-muted)] focus:border-[var(--esc-panel-border-input)] focus:outline-none focus:ring-1 focus:ring-[var(--esc-panel-border-input)]"
|
|
117
|
+
/>
|
|
118
|
+
</div>
|
|
119
|
+
<div>
|
|
120
|
+
<label class="block text-xs font-medium text-[var(--esc-panel-text-tertiary)]"
|
|
121
|
+
>Type</label
|
|
122
|
+
>
|
|
123
|
+
<select
|
|
124
|
+
v-model="field.type"
|
|
125
|
+
class="mt-1 w-full rounded-lg border border-[var(--esc-panel-border-input)] bg-[var(--esc-panel-surface)] px-3 py-2 text-sm text-[var(--esc-panel-text-secondary)] focus:border-[var(--esc-panel-border-input)] focus:outline-none focus:ring-1 focus:ring-[var(--esc-panel-border-input)]"
|
|
126
|
+
>
|
|
127
|
+
<option v-for="ft in fieldTypes" :key="ft.value" :value="ft.value">
|
|
128
|
+
{{ ft.label }}
|
|
129
|
+
</option>
|
|
130
|
+
</select>
|
|
131
|
+
</div>
|
|
132
|
+
<div class="flex items-end gap-2">
|
|
133
|
+
<label class="flex items-center gap-2 py-2">
|
|
134
|
+
<input
|
|
135
|
+
v-model="field.required"
|
|
136
|
+
type="checkbox"
|
|
137
|
+
class="rounded border-[var(--esc-panel-border-input)] bg-[var(--esc-panel-surface)] text-cyan-500 focus:ring-[var(--esc-panel-border-input)]"
|
|
138
|
+
/>
|
|
139
|
+
<span class="text-xs text-[var(--esc-panel-text-tertiary)]">Required</span>
|
|
140
|
+
</label>
|
|
141
|
+
<button
|
|
142
|
+
type="button"
|
|
143
|
+
class="ml-auto rounded p-1 text-[var(--esc-panel-text-muted)] hover:bg-red-500/10 hover:text-red-400"
|
|
144
|
+
@click="removeField(idx)"
|
|
145
|
+
>
|
|
146
|
+
<svg
|
|
147
|
+
class="h-4 w-4"
|
|
148
|
+
fill="none"
|
|
149
|
+
stroke="currentColor"
|
|
150
|
+
stroke-width="1.5"
|
|
151
|
+
viewBox="0 0 24 24"
|
|
152
|
+
>
|
|
153
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
|
|
154
|
+
</svg>
|
|
155
|
+
</button>
|
|
156
|
+
</div>
|
|
157
|
+
</div>
|
|
158
|
+
<!-- Options for select type -->
|
|
159
|
+
<div v-if="field.type === 'select'" class="mt-3">
|
|
160
|
+
<label class="block text-xs font-medium text-[var(--esc-panel-text-tertiary)]"
|
|
161
|
+
>Options (comma-separated)</label
|
|
162
|
+
>
|
|
163
|
+
<input
|
|
164
|
+
:value="(field.options || []).join(', ')"
|
|
165
|
+
type="text"
|
|
166
|
+
placeholder="Option1, Option2, Option3"
|
|
167
|
+
class="mt-1 w-full rounded-lg border border-[var(--esc-panel-border-input)] bg-[var(--esc-panel-surface)] px-3 py-2 text-sm text-[var(--esc-panel-text-secondary)] placeholder-[var(--esc-panel-text-muted)] focus:border-[var(--esc-panel-border-input)] focus:outline-none focus:ring-1 focus:ring-[var(--esc-panel-border-input)]"
|
|
168
|
+
@input="
|
|
169
|
+
field.options = $event.target.value
|
|
170
|
+
.split(',')
|
|
171
|
+
.map((s) => s.trim())
|
|
172
|
+
.filter(Boolean)
|
|
173
|
+
"
|
|
174
|
+
/>
|
|
175
|
+
</div>
|
|
176
|
+
</div>
|
|
177
|
+
</div>
|
|
178
|
+
|
|
179
|
+
<div
|
|
180
|
+
v-else
|
|
181
|
+
class="rounded-lg border border-dashed border-[var(--esc-panel-border)] px-4 py-6 text-center"
|
|
182
|
+
>
|
|
183
|
+
<p class="text-sm text-[var(--esc-panel-text-muted)]">
|
|
184
|
+
No fields defined yet. Click "Add Field" to start.
|
|
185
|
+
</p>
|
|
186
|
+
</div>
|
|
187
|
+
</div>
|
|
188
|
+
|
|
189
|
+
<!-- Submit -->
|
|
190
|
+
<div class="flex items-center justify-end gap-3">
|
|
191
|
+
<span v-if="form.recentlySuccessful" class="text-sm text-emerald-400">Saved.</span>
|
|
192
|
+
<button
|
|
193
|
+
type="submit"
|
|
194
|
+
:disabled="form.processing"
|
|
195
|
+
class="rounded-lg bg-gradient-to-r from-[var(--esc-panel-accent)] to-[var(--esc-panel-accent-secondary)] px-5 py-2 text-sm font-medium text-white shadow-lg shadow-[var(--esc-panel-bg)]/20 transition-all hover:from-[var(--esc-panel-accent-hover)] hover:to-[var(--esc-panel-accent-secondary-hover)] disabled:opacity-50"
|
|
196
|
+
>
|
|
197
|
+
{{ form.processing ? 'Saving...' : isEdit ? 'Update Object' : 'Create Object' }}
|
|
198
|
+
</button>
|
|
199
|
+
</div>
|
|
200
|
+
</form>
|
|
201
|
+
</EscalatedLayout>
|
|
202
|
+
</template>
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import EscalatedLayout from '../../../components/EscalatedLayout.vue';
|
|
3
|
+
import { router } from '@inertiajs/vue3';
|
|
4
|
+
import { Link } from '@inertiajs/vue3';
|
|
5
|
+
|
|
6
|
+
defineProps({
|
|
7
|
+
objects: { type: Array, default: () => [] },
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
function deleteObject(obj) {
|
|
11
|
+
if (confirm(`Delete custom object "${obj.name}"? This will also delete all its records.`)) {
|
|
12
|
+
router.delete(route('escalated.admin.custom-objects.destroy', obj.id));
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
</script>
|
|
16
|
+
|
|
17
|
+
<template>
|
|
18
|
+
<EscalatedLayout title="Custom Objects">
|
|
19
|
+
<div class="mb-6 flex items-center justify-between">
|
|
20
|
+
<div>
|
|
21
|
+
<h2 class="text-lg font-semibold text-[var(--esc-panel-text-secondary)]">Custom Objects</h2>
|
|
22
|
+
<p class="mt-1 text-sm text-[var(--esc-panel-text-muted)]">
|
|
23
|
+
Define custom data structures with fields and relationships
|
|
24
|
+
</p>
|
|
25
|
+
</div>
|
|
26
|
+
<Link
|
|
27
|
+
:href="route('escalated.admin.custom-objects.create')"
|
|
28
|
+
class="rounded-lg bg-gradient-to-r from-[var(--esc-panel-accent)] to-[var(--esc-panel-accent-secondary)] px-5 py-2 text-sm font-medium text-white shadow-lg shadow-[var(--esc-panel-bg)]/20 transition-all hover:from-[var(--esc-panel-accent-hover)] hover:to-[var(--esc-panel-accent-secondary-hover)]"
|
|
29
|
+
>
|
|
30
|
+
New Object
|
|
31
|
+
</Link>
|
|
32
|
+
</div>
|
|
33
|
+
|
|
34
|
+
<div v-if="objects.length" class="overflow-hidden rounded-xl border border-[var(--esc-panel-border)]">
|
|
35
|
+
<table class="w-full">
|
|
36
|
+
<thead>
|
|
37
|
+
<tr class="border-b border-[var(--esc-panel-border)] bg-[var(--esc-panel-hover)]">
|
|
38
|
+
<th
|
|
39
|
+
class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-[var(--esc-panel-text-muted)]"
|
|
40
|
+
>
|
|
41
|
+
Name
|
|
42
|
+
</th>
|
|
43
|
+
<th
|
|
44
|
+
class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-[var(--esc-panel-text-muted)]"
|
|
45
|
+
>
|
|
46
|
+
Slug
|
|
47
|
+
</th>
|
|
48
|
+
<th
|
|
49
|
+
class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-[var(--esc-panel-text-muted)]"
|
|
50
|
+
>
|
|
51
|
+
Fields
|
|
52
|
+
</th>
|
|
53
|
+
<th
|
|
54
|
+
class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-[var(--esc-panel-text-muted)]"
|
|
55
|
+
>
|
|
56
|
+
Records
|
|
57
|
+
</th>
|
|
58
|
+
<th
|
|
59
|
+
class="px-4 py-3 text-right text-xs font-medium uppercase tracking-wider text-[var(--esc-panel-text-muted)]"
|
|
60
|
+
>
|
|
61
|
+
Actions
|
|
62
|
+
</th>
|
|
63
|
+
</tr>
|
|
64
|
+
</thead>
|
|
65
|
+
<tbody class="divide-y divide-[var(--esc-panel-border)]">
|
|
66
|
+
<tr v-for="obj in objects" :key="obj.id" class="hover:bg-[var(--esc-panel-hover)]">
|
|
67
|
+
<td class="px-4 py-3">
|
|
68
|
+
<Link
|
|
69
|
+
:href="route('escalated.admin.custom-objects.edit', obj.id)"
|
|
70
|
+
class="text-sm font-medium text-[var(--esc-panel-text-secondary)] hover:text-[var(--esc-panel-text)]"
|
|
71
|
+
>
|
|
72
|
+
{{ obj.name }}
|
|
73
|
+
</Link>
|
|
74
|
+
</td>
|
|
75
|
+
<td class="px-4 py-3 text-sm font-mono text-[var(--esc-panel-text-muted)]">{{ obj.slug }}</td>
|
|
76
|
+
<td class="px-4 py-3 text-sm text-[var(--esc-panel-text-tertiary)]">
|
|
77
|
+
{{ obj.fields_schema?.length || 0 }}
|
|
78
|
+
</td>
|
|
79
|
+
<td class="px-4 py-3 text-sm text-[var(--esc-panel-text-tertiary)]">
|
|
80
|
+
{{ obj.records_count ?? 0 }}
|
|
81
|
+
</td>
|
|
82
|
+
<td class="px-4 py-3 text-right">
|
|
83
|
+
<Link
|
|
84
|
+
:href="route('escalated.admin.custom-objects.records', obj.id)"
|
|
85
|
+
class="mr-3 text-sm text-[var(--esc-panel-text-muted)] hover:text-[var(--esc-panel-text-secondary)]"
|
|
86
|
+
>
|
|
87
|
+
Records
|
|
88
|
+
</Link>
|
|
89
|
+
<Link
|
|
90
|
+
:href="route('escalated.admin.custom-objects.edit', obj.id)"
|
|
91
|
+
class="mr-3 text-sm text-[var(--esc-panel-text-muted)] hover:text-[var(--esc-panel-text-secondary)]"
|
|
92
|
+
>
|
|
93
|
+
Edit
|
|
94
|
+
</Link>
|
|
95
|
+
<button class="text-sm text-red-500/60 hover:text-red-400" @click="deleteObject(obj)">
|
|
96
|
+
Delete
|
|
97
|
+
</button>
|
|
98
|
+
</td>
|
|
99
|
+
</tr>
|
|
100
|
+
</tbody>
|
|
101
|
+
</table>
|
|
102
|
+
</div>
|
|
103
|
+
|
|
104
|
+
<div v-else class="rounded-xl border border-dashed border-[var(--esc-panel-border)] px-6 py-12 text-center">
|
|
105
|
+
<svg
|
|
106
|
+
class="mx-auto mb-3 h-10 w-10 text-[var(--esc-panel-text-muted)]"
|
|
107
|
+
fill="none"
|
|
108
|
+
stroke="currentColor"
|
|
109
|
+
stroke-width="1.5"
|
|
110
|
+
viewBox="0 0 24 24"
|
|
111
|
+
>
|
|
112
|
+
<path
|
|
113
|
+
stroke-linecap="round"
|
|
114
|
+
stroke-linejoin="round"
|
|
115
|
+
d="M20.25 6.375c0 2.278-3.694 4.125-8.25 4.125S3.75 8.653 3.75 6.375m16.5 0c0-2.278-3.694-4.125-8.25-4.125S3.75 4.097 3.75 6.375m16.5 0v11.25c0 2.278-3.694 4.125-8.25 4.125s-8.25-1.847-8.25-4.125V6.375m16.5 0v3.75m-16.5-3.75v3.75m16.5 0v3.75C20.25 16.153 16.556 18 12 18s-8.25-1.847-8.25-4.125v-3.75m16.5 0c0 2.278-3.694 4.125-8.25 4.125s-8.25-1.847-8.25-4.125"
|
|
116
|
+
/>
|
|
117
|
+
</svg>
|
|
118
|
+
<p class="text-sm text-[var(--esc-panel-text-muted)]">No custom objects defined yet.</p>
|
|
119
|
+
<Link
|
|
120
|
+
:href="route('escalated.admin.custom-objects.create')"
|
|
121
|
+
class="mt-3 inline-block text-sm text-[var(--esc-panel-accent)] hover:text-[var(--esc-panel-accent)]"
|
|
122
|
+
>
|
|
123
|
+
Create your first custom object
|
|
124
|
+
</Link>
|
|
125
|
+
</div>
|
|
126
|
+
</EscalatedLayout>
|
|
127
|
+
</template>
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import EscalatedLayout from '../../../components/EscalatedLayout.vue';
|
|
3
|
+
import { useForm, router } from '@inertiajs/vue3';
|
|
4
|
+
import { ref, computed } from 'vue';
|
|
5
|
+
|
|
6
|
+
const props = defineProps({
|
|
7
|
+
object: Object,
|
|
8
|
+
records: { type: Array, default: () => [] },
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
const fields = computed(() => props.object.fields_schema || []);
|
|
12
|
+
const editingId = ref(null);
|
|
13
|
+
const showAddForm = ref(false);
|
|
14
|
+
|
|
15
|
+
const newRecordForm = useForm({
|
|
16
|
+
data: {},
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
const editForm = useForm({
|
|
20
|
+
data: {},
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
function startAdd() {
|
|
24
|
+
const data = {};
|
|
25
|
+
fields.value.forEach((f) => {
|
|
26
|
+
data[f.name] = '';
|
|
27
|
+
});
|
|
28
|
+
newRecordForm.data = data;
|
|
29
|
+
showAddForm.value = true;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function addRecord() {
|
|
33
|
+
newRecordForm.post(route('escalated.admin.custom-objects.records.store', props.object.id), {
|
|
34
|
+
preserveScroll: true,
|
|
35
|
+
onSuccess: () => {
|
|
36
|
+
showAddForm.value = false;
|
|
37
|
+
newRecordForm.reset();
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function startEdit(record) {
|
|
43
|
+
editingId.value = record.id;
|
|
44
|
+
editForm.data = { ...(record.data || {}) };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function saveEdit(record) {
|
|
48
|
+
editForm.put(route('escalated.admin.custom-objects.records.update', [props.object.id, record.id]), {
|
|
49
|
+
preserveScroll: true,
|
|
50
|
+
onSuccess: () => {
|
|
51
|
+
editingId.value = null;
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function cancelEdit() {
|
|
57
|
+
editingId.value = null;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function deleteRecord(record) {
|
|
61
|
+
if (confirm('Delete this record?')) {
|
|
62
|
+
router.delete(route('escalated.admin.custom-objects.records.destroy', [props.object.id, record.id]), {
|
|
63
|
+
preserveScroll: true,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
</script>
|
|
68
|
+
|
|
69
|
+
<template>
|
|
70
|
+
<EscalatedLayout :title="`${object.name} Records`">
|
|
71
|
+
<div class="mb-6 flex items-center justify-between">
|
|
72
|
+
<div>
|
|
73
|
+
<h2 class="text-lg font-semibold text-[var(--esc-panel-text-secondary)]">{{ object.name }} Records</h2>
|
|
74
|
+
<p class="mt-1 text-sm text-[var(--esc-panel-text-muted)]">{{ records.length }} records</p>
|
|
75
|
+
</div>
|
|
76
|
+
<button
|
|
77
|
+
type="button"
|
|
78
|
+
class="rounded-lg bg-gradient-to-r from-[var(--esc-panel-accent)] to-[var(--esc-panel-accent-secondary)] px-5 py-2 text-sm font-medium text-white shadow-lg shadow-[var(--esc-panel-bg)]/20 transition-all hover:from-[var(--esc-panel-accent-hover)] hover:to-[var(--esc-panel-accent-secondary-hover)]"
|
|
79
|
+
@click="startAdd"
|
|
80
|
+
>
|
|
81
|
+
Add Record
|
|
82
|
+
</button>
|
|
83
|
+
</div>
|
|
84
|
+
|
|
85
|
+
<!-- Add Record Form -->
|
|
86
|
+
<div
|
|
87
|
+
v-if="showAddForm"
|
|
88
|
+
class="mb-6 rounded-xl border border-[var(--esc-panel-border)] bg-[var(--esc-panel-surface)] p-6"
|
|
89
|
+
>
|
|
90
|
+
<h3 class="mb-4 text-sm font-semibold text-[var(--esc-panel-text)]">New Record</h3>
|
|
91
|
+
<form class="space-y-3" @submit.prevent="addRecord">
|
|
92
|
+
<div v-for="field in fields" :key="field.name">
|
|
93
|
+
<label class="block text-xs font-medium text-[var(--esc-panel-text-tertiary)]">
|
|
94
|
+
{{ field.name }}
|
|
95
|
+
<span v-if="field.required" class="text-rose-400">*</span>
|
|
96
|
+
</label>
|
|
97
|
+
<select
|
|
98
|
+
v-if="field.type === 'select'"
|
|
99
|
+
v-model="newRecordForm.data[field.name]"
|
|
100
|
+
class="mt-1 w-full rounded-lg border border-[var(--esc-panel-border-input)] bg-[var(--esc-panel-surface-alt)] px-3 py-2 text-sm text-[var(--esc-panel-text-secondary)] focus:border-[var(--esc-panel-border-input)] focus:outline-none focus:ring-1 focus:ring-[var(--esc-panel-border-input)]"
|
|
101
|
+
>
|
|
102
|
+
<option value="">Select...</option>
|
|
103
|
+
<option v-for="opt in field.options" :key="opt" :value="opt">{{ opt }}</option>
|
|
104
|
+
</select>
|
|
105
|
+
<input
|
|
106
|
+
v-else-if="field.type === 'date'"
|
|
107
|
+
v-model="newRecordForm.data[field.name]"
|
|
108
|
+
type="date"
|
|
109
|
+
class="mt-1 w-full rounded-lg border border-[var(--esc-panel-border-input)] bg-[var(--esc-panel-surface-alt)] px-3 py-2 text-sm text-[var(--esc-panel-text-secondary)] focus:border-[var(--esc-panel-border-input)] focus:outline-none focus:ring-1 focus:ring-[var(--esc-panel-border-input)]"
|
|
110
|
+
/>
|
|
111
|
+
<input
|
|
112
|
+
v-else-if="field.type === 'number'"
|
|
113
|
+
v-model="newRecordForm.data[field.name]"
|
|
114
|
+
type="number"
|
|
115
|
+
class="mt-1 w-full rounded-lg border border-[var(--esc-panel-border-input)] bg-[var(--esc-panel-surface-alt)] px-3 py-2 text-sm text-[var(--esc-panel-text-secondary)] focus:border-[var(--esc-panel-border-input)] focus:outline-none focus:ring-1 focus:ring-[var(--esc-panel-border-input)]"
|
|
116
|
+
/>
|
|
117
|
+
<input
|
|
118
|
+
v-else
|
|
119
|
+
v-model="newRecordForm.data[field.name]"
|
|
120
|
+
type="text"
|
|
121
|
+
class="mt-1 w-full rounded-lg border border-[var(--esc-panel-border-input)] bg-[var(--esc-panel-surface-alt)] px-3 py-2 text-sm text-[var(--esc-panel-text-secondary)] focus:border-[var(--esc-panel-border-input)] focus:outline-none focus:ring-1 focus:ring-[var(--esc-panel-border-input)]"
|
|
122
|
+
/>
|
|
123
|
+
</div>
|
|
124
|
+
<div class="flex gap-2">
|
|
125
|
+
<button
|
|
126
|
+
type="submit"
|
|
127
|
+
:disabled="newRecordForm.processing"
|
|
128
|
+
class="rounded-lg bg-gradient-to-r from-[var(--esc-panel-accent)] to-[var(--esc-panel-accent-secondary)] px-4 py-1.5 text-sm font-medium text-white"
|
|
129
|
+
>
|
|
130
|
+
Save
|
|
131
|
+
</button>
|
|
132
|
+
<button
|
|
133
|
+
type="button"
|
|
134
|
+
class="rounded-lg border border-[var(--esc-panel-border-input)] px-4 py-1.5 text-sm text-[var(--esc-panel-text-tertiary)]"
|
|
135
|
+
@click="showAddForm = false"
|
|
136
|
+
>
|
|
137
|
+
Cancel
|
|
138
|
+
</button>
|
|
139
|
+
</div>
|
|
140
|
+
</form>
|
|
141
|
+
</div>
|
|
142
|
+
|
|
143
|
+
<!-- Records Table -->
|
|
144
|
+
<div v-if="records.length" class="overflow-hidden rounded-xl border border-[var(--esc-panel-border)]">
|
|
145
|
+
<table class="w-full">
|
|
146
|
+
<thead>
|
|
147
|
+
<tr class="border-b border-[var(--esc-panel-border)] bg-[var(--esc-panel-hover)]">
|
|
148
|
+
<th
|
|
149
|
+
class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-[var(--esc-panel-text-muted)]"
|
|
150
|
+
>
|
|
151
|
+
ID
|
|
152
|
+
</th>
|
|
153
|
+
<th
|
|
154
|
+
v-for="field in fields"
|
|
155
|
+
:key="field.name"
|
|
156
|
+
class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-[var(--esc-panel-text-muted)]"
|
|
157
|
+
>
|
|
158
|
+
{{ field.name }}
|
|
159
|
+
</th>
|
|
160
|
+
<th
|
|
161
|
+
class="px-4 py-3 text-right text-xs font-medium uppercase tracking-wider text-[var(--esc-panel-text-muted)]"
|
|
162
|
+
>
|
|
163
|
+
Actions
|
|
164
|
+
</th>
|
|
165
|
+
</tr>
|
|
166
|
+
</thead>
|
|
167
|
+
<tbody class="divide-y divide-[var(--esc-panel-border)]">
|
|
168
|
+
<tr v-for="record in records" :key="record.id" class="hover:bg-[var(--esc-panel-hover)]">
|
|
169
|
+
<td class="px-4 py-3 text-sm text-[var(--esc-panel-text-muted)]">{{ record.id }}</td>
|
|
170
|
+
<td v-for="field in fields" :key="field.name" class="px-4 py-3">
|
|
171
|
+
<!-- Edit mode -->
|
|
172
|
+
<template v-if="editingId === record.id">
|
|
173
|
+
<input
|
|
174
|
+
v-model="editForm.data[field.name]"
|
|
175
|
+
:type="field.type === 'number' ? 'number' : field.type === 'date' ? 'date' : 'text'"
|
|
176
|
+
class="w-full rounded border border-[var(--esc-panel-border-input)] bg-[var(--esc-panel-surface)] px-2 py-1 text-sm text-[var(--esc-panel-text-secondary)] focus:border-[var(--esc-panel-border-input)] focus:outline-none"
|
|
177
|
+
/>
|
|
178
|
+
</template>
|
|
179
|
+
<!-- View mode -->
|
|
180
|
+
<template v-else>
|
|
181
|
+
<span class="text-sm text-[var(--esc-panel-text-secondary)]">
|
|
182
|
+
{{ record.data?.[field.name] || '---' }}
|
|
183
|
+
</span>
|
|
184
|
+
</template>
|
|
185
|
+
</td>
|
|
186
|
+
<td class="px-4 py-3 text-right">
|
|
187
|
+
<template v-if="editingId === record.id">
|
|
188
|
+
<button
|
|
189
|
+
class="mr-2 text-sm text-emerald-400 hover:text-emerald-300"
|
|
190
|
+
@click="saveEdit(record)"
|
|
191
|
+
>
|
|
192
|
+
Save
|
|
193
|
+
</button>
|
|
194
|
+
<button
|
|
195
|
+
class="text-sm text-[var(--esc-panel-text-muted)] hover:text-[var(--esc-panel-text-secondary)]"
|
|
196
|
+
@click="cancelEdit"
|
|
197
|
+
>
|
|
198
|
+
Cancel
|
|
199
|
+
</button>
|
|
200
|
+
</template>
|
|
201
|
+
<template v-else>
|
|
202
|
+
<button
|
|
203
|
+
class="mr-2 text-sm text-[var(--esc-panel-text-muted)] hover:text-[var(--esc-panel-text-secondary)]"
|
|
204
|
+
@click="startEdit(record)"
|
|
205
|
+
>
|
|
206
|
+
Edit
|
|
207
|
+
</button>
|
|
208
|
+
<button
|
|
209
|
+
class="text-sm text-red-500/60 hover:text-red-400"
|
|
210
|
+
@click="deleteRecord(record)"
|
|
211
|
+
>
|
|
212
|
+
Delete
|
|
213
|
+
</button>
|
|
214
|
+
</template>
|
|
215
|
+
</td>
|
|
216
|
+
</tr>
|
|
217
|
+
</tbody>
|
|
218
|
+
</table>
|
|
219
|
+
</div>
|
|
220
|
+
|
|
221
|
+
<div
|
|
222
|
+
v-else-if="!showAddForm"
|
|
223
|
+
class="rounded-xl border border-dashed border-[var(--esc-panel-border)] px-6 py-12 text-center"
|
|
224
|
+
>
|
|
225
|
+
<p class="text-sm text-[var(--esc-panel-text-muted)]">No records yet.</p>
|
|
226
|
+
</div>
|
|
227
|
+
</EscalatedLayout>
|
|
228
|
+
</template>
|
|
@@ -22,27 +22,51 @@ function submit() {
|
|
|
22
22
|
|
|
23
23
|
<template>
|
|
24
24
|
<EscalatedLayout :title="department ? 'Edit Department' : 'New Department'">
|
|
25
|
-
<form
|
|
25
|
+
<form
|
|
26
|
+
class="mx-auto max-w-lg space-y-5 rounded-xl border border-[var(--esc-panel-border)] bg-[var(--esc-panel-surface)] p-6"
|
|
27
|
+
@submit.prevent="submit"
|
|
28
|
+
>
|
|
26
29
|
<div>
|
|
27
|
-
<label class="block text-sm font-medium text-
|
|
28
|
-
<input
|
|
30
|
+
<label class="block text-sm font-medium text-[var(--esc-panel-text-secondary)]">Name</label>
|
|
31
|
+
<input
|
|
32
|
+
v-model="form.name"
|
|
33
|
+
type="text"
|
|
34
|
+
required
|
|
35
|
+
class="mt-1 w-full rounded-lg border border-[var(--esc-panel-border-input)] bg-[var(--esc-panel-surface-alt)] px-3 py-2 text-sm text-[var(--esc-panel-text-secondary)] focus:border-[var(--esc-panel-border-input)] focus:outline-none focus:ring-1 focus:ring-[var(--esc-panel-border-input)]"
|
|
36
|
+
/>
|
|
29
37
|
<div v-if="form.errors.name" class="mt-1 text-sm text-rose-400">{{ form.errors.name }}</div>
|
|
30
38
|
</div>
|
|
31
39
|
<div>
|
|
32
|
-
<label class="block text-sm font-medium text-
|
|
33
|
-
<input
|
|
40
|
+
<label class="block text-sm font-medium text-[var(--esc-panel-text-secondary)]">Slug</label>
|
|
41
|
+
<input
|
|
42
|
+
v-model="form.slug"
|
|
43
|
+
type="text"
|
|
44
|
+
placeholder="Auto-generated if empty"
|
|
45
|
+
class="mt-1 w-full rounded-lg border border-[var(--esc-panel-border-input)] bg-[var(--esc-panel-surface-alt)] px-3 py-2 text-sm text-[var(--esc-panel-text-secondary)] placeholder-gray-500 focus:border-[var(--esc-panel-border-input)] focus:outline-none focus:ring-1 focus:ring-[var(--esc-panel-border-input)]"
|
|
46
|
+
/>
|
|
34
47
|
</div>
|
|
35
48
|
<div>
|
|
36
|
-
<label class="block text-sm font-medium text-
|
|
37
|
-
<textarea
|
|
49
|
+
<label class="block text-sm font-medium text-[var(--esc-panel-text-secondary)]">Description</label>
|
|
50
|
+
<textarea
|
|
51
|
+
v-model="form.description"
|
|
52
|
+
rows="3"
|
|
53
|
+
class="mt-1 w-full rounded-lg border border-[var(--esc-panel-border-input)] bg-[var(--esc-panel-surface-alt)] px-3 py-2 text-sm text-[var(--esc-panel-text-secondary)] focus:border-[var(--esc-panel-border-input)] focus:outline-none focus:ring-1 focus:ring-[var(--esc-panel-border-input)]"
|
|
54
|
+
></textarea>
|
|
38
55
|
</div>
|
|
39
56
|
<label class="flex items-center gap-2">
|
|
40
|
-
<input
|
|
41
|
-
|
|
57
|
+
<input
|
|
58
|
+
v-model="form.is_active"
|
|
59
|
+
type="checkbox"
|
|
60
|
+
class="rounded border-[var(--esc-panel-border-input)] bg-[var(--esc-panel-surface)] text-[var(--esc-panel-text)] focus:ring-[var(--esc-panel-border-input)]"
|
|
61
|
+
/>
|
|
62
|
+
<span class="text-sm text-[var(--esc-panel-text-secondary)]">Active</span>
|
|
42
63
|
</label>
|
|
43
64
|
<div class="flex justify-end">
|
|
44
|
-
<button
|
|
45
|
-
|
|
65
|
+
<button
|
|
66
|
+
type="submit"
|
|
67
|
+
:disabled="form.processing"
|
|
68
|
+
class="rounded-lg bg-gradient-to-r from-[var(--esc-panel-accent)] to-[var(--esc-panel-accent-secondary)] px-5 py-2 text-sm font-medium text-white shadow-lg shadow-[var(--esc-panel-bg)]/20 transition-all hover:from-[var(--esc-panel-accent-hover)] hover:to-[var(--esc-panel-accent-secondary-hover)] disabled:opacity-50"
|
|
69
|
+
>
|
|
46
70
|
{{ department ? 'Update' : 'Create' }}
|
|
47
71
|
</button>
|
|
48
72
|
</div>
|