@murumets-ee/create 0.1.14 → 0.1.15
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/dist/cli.mjs +238 -3
- package/dist/index.mjs +238 -3
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/cli.mjs
CHANGED
|
@@ -1,15 +1,29 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import*as e from"@clack/prompts";import t from"picocolors";import{dirname as n,join as r}from"node:path";import{execSync as i}from"node:child_process";import{appendFile as a,mkdir as o,readFile as s,rm as c,writeFile as l}from"node:fs/promises";var u=Object.defineProperty,d=(e,t)=>{let n={};for(var r in e)u(n,r,{get:e[r],enumerable:!0});return t||u(n,Symbol.toStringTag,{value:`Module`}),n};function f(e){let t={},n=e.slice(2);for(let e=0;e<n.length;e++){let r=n[e];if(r===`--mode`&&n[e+1]){let r=n[++e];(r===`single`||r===`multi`)&&(t.mode=r)}else r===`--install`?t.install=!0:r===`--no-install`?t.install=!1:!r.startsWith(`-`)&&!t.name&&(t.name=r)}return t}async function p(t){let n=f(process.argv),r=n.name??t;if(r&&n.mode&&n.install!==void 0)return{name:r,mode:n.mode,installDeps:n.install};e.intro(`@murumets-ee/create`);let i=r??await e.text({message:`Project name:`,placeholder:`my-project`,validate:e=>{if(!e)return`Project name is required`;if(!/^[a-z0-9-]+$/.test(e))return`Use lowercase letters, numbers, and hyphens only`}});if(e.isCancel(i))return e.cancel(`Cancelled.`),null;let a=n.mode??await e.select({message:`App setup:`,options:[{value:`single`,label:`Single app`,hint:`everything in one Next.js project`},{value:`multi`,label:`Admin + Frontend`,hint:`two apps in a pnpm workspace`}]});if(e.isCancel(a))return e.cancel(`Cancelled.`),null;let o=n.install??await e.confirm({message:`Install dependencies?`,initialValue:!0});return e.isCancel(o)?(e.cancel(`Cancelled.`),null):{name:i,mode:a,installDeps:o}}var m=d({appendToFile:()=>y,deleteFiles:()=>_,mergePackageJson:()=>g,runCommand:()=>v,writeFile:()=>h});async function h(e,t){await o(n(e),{recursive:!0}),await l(e,t,`utf-8`)}async function g(e,t){let n=await s(e,`utf-8`),r=JSON.parse(n);t.type&&(r.type=t.type),t.dependencies&&(r.dependencies={...r.dependencies,...t.dependencies}),t.devDependencies&&(r.devDependencies={...r.devDependencies,...t.devDependencies}),t.scripts&&(r.scripts={...r.scripts,...t.scripts}),await l(e,`${JSON.stringify(r,null,2)}\n`,`utf-8`)}async function _(e,t){for(let n of t)await c(r(e,n),{force:!0,recursive:!0})}function v(e,t){return i(e,{cwd:t?.cwd,stdio:`pipe`,encoding:`utf-8`})}async function y(e,t){await a(e,t,`utf-8`)}async function b(e,t){await h(r(e,`lib/admin-config.ts`),`import { Media } from '@murumets-ee/media'
|
|
3
|
+
import {
|
|
4
|
+
Ticket,
|
|
5
|
+
TicketMessage,
|
|
6
|
+
TicketAttachment,
|
|
7
|
+
Department,
|
|
8
|
+
TicketTag,
|
|
9
|
+
} from '@murumets-ee/ticketing'
|
|
3
10
|
import { Article, Category } from '@/entities'
|
|
4
11
|
|
|
5
12
|
/** All entities available in the admin (drives sidebar nav + CRUD) */
|
|
6
|
-
export const allEntities = [
|
|
13
|
+
export const allEntities = [
|
|
14
|
+
Article, Media, Category,
|
|
15
|
+
Ticket, TicketMessage, TicketAttachment, Department, TicketTag,
|
|
16
|
+
]
|
|
7
17
|
|
|
8
18
|
/** Taxonomy entities keyed by name */
|
|
9
|
-
export const taxonomyVocabularies = {
|
|
19
|
+
export const taxonomyVocabularies = {
|
|
20
|
+
category: Category,
|
|
21
|
+
department: Department,
|
|
22
|
+
ticket_tag: TicketTag,
|
|
23
|
+
}
|
|
10
24
|
|
|
11
25
|
/** Entities exposed via generic CRUD API handler */
|
|
12
|
-
export const crudEntities = [Article, Media]
|
|
26
|
+
export const crudEntities = [Article, Media, Ticket, TicketMessage, TicketAttachment]
|
|
13
27
|
|
|
14
28
|
/** Plugin resources for the permission catalog */
|
|
15
29
|
export const pluginResources = [
|
|
@@ -17,6 +31,7 @@ export const pluginResources = [
|
|
|
17
31
|
{ resource: 'settings', actions: ['view', 'update'] },
|
|
18
32
|
{ resource: 'audit-logs', actions: ['view'] },
|
|
19
33
|
{ resource: 'permissions', actions: ['view', 'create', 'update', 'delete'] },
|
|
34
|
+
{ resource: 'ticketing', actions: ['view', 'create', 'update', 'delete'] },
|
|
20
35
|
]
|
|
21
36
|
`),await h(r(e,`lib/content-locale.ts`),`import { cookies } from 'next/headers'
|
|
22
37
|
import { hasLocale } from 'next-intl'
|
|
@@ -107,15 +122,20 @@ import { AdminShell } from '@murumets-ee/admin-ui'
|
|
|
107
122
|
import type { AdminNavGroup } from '@murumets-ee/admin-ui/server'
|
|
108
123
|
import {
|
|
109
124
|
Activity,
|
|
125
|
+
Building2,
|
|
110
126
|
FileText,
|
|
111
127
|
FolderTree,
|
|
112
128
|
Home,
|
|
113
129
|
Image,
|
|
114
130
|
ImageDown,
|
|
131
|
+
Inbox,
|
|
132
|
+
Kanban,
|
|
115
133
|
Lock,
|
|
116
134
|
PenTool,
|
|
117
135
|
ShieldCheck,
|
|
136
|
+
Tag,
|
|
118
137
|
Tags,
|
|
138
|
+
Ticket,
|
|
119
139
|
Users,
|
|
120
140
|
type LucideIcon,
|
|
121
141
|
} from 'lucide-react'
|
|
@@ -127,6 +147,7 @@ const ICON_MAP: Record<string, LucideIcon> = {
|
|
|
127
147
|
'file-text': FileText,
|
|
128
148
|
'folder-tree': FolderTree,
|
|
129
149
|
tags: Tags,
|
|
150
|
+
ticket: Ticket,
|
|
130
151
|
}
|
|
131
152
|
|
|
132
153
|
interface AdminLayoutProps {
|
|
@@ -162,6 +183,16 @@ export function AdminLayout({
|
|
|
162
183
|
],
|
|
163
184
|
}
|
|
164
185
|
|
|
186
|
+
const supportGroup: SidebarNavGroup = {
|
|
187
|
+
label: 'Support',
|
|
188
|
+
items: [
|
|
189
|
+
{ label: 'Tickets', href: '/admin/tickets', icon: Inbox },
|
|
190
|
+
{ label: 'Board', href: '/admin/tickets/board', icon: Kanban },
|
|
191
|
+
{ label: 'Departments', href: '/admin/departments', icon: Building2 },
|
|
192
|
+
{ label: 'Ticket Tags', href: '/admin/ticket-tags', icon: Tag },
|
|
193
|
+
],
|
|
194
|
+
}
|
|
195
|
+
|
|
165
196
|
const systemGroup: SidebarNavGroup = {
|
|
166
197
|
label: 'System',
|
|
167
198
|
items: [
|
|
@@ -177,6 +208,7 @@ export function AdminLayout({
|
|
|
177
208
|
const navGroups: SidebarNavGroup[] = [
|
|
178
209
|
staticBefore,
|
|
179
210
|
...dynamicGroups,
|
|
211
|
+
supportGroup,
|
|
180
212
|
systemGroup,
|
|
181
213
|
]
|
|
182
214
|
|
|
@@ -1017,6 +1049,207 @@ export default async function ActivityPage({ params }: { params: Promise<{ local
|
|
|
1017
1049
|
)
|
|
1018
1050
|
})
|
|
1019
1051
|
}
|
|
1052
|
+
`),await h(r(e,`app/[locale]/(shell)/admin/tickets/page.tsx`),`import { Ticket, Department, TicketTag } from '@murumets-ee/ticketing'
|
|
1053
|
+
import { headers } from 'next/headers'
|
|
1054
|
+
import { setRequestLocale } from 'next-intl/server'
|
|
1055
|
+
import { withAdminContext } from '@/lib/with-admin-context'
|
|
1056
|
+
import { auth } from '@/lib/auth'
|
|
1057
|
+
import { TicketsInboxClient } from './tickets-inbox-client'
|
|
1058
|
+
import { fetchEntityList } from '@murumets-ee/admin-ui/server'
|
|
1059
|
+
import type {
|
|
1060
|
+
TicketData,
|
|
1061
|
+
DepartmentData,
|
|
1062
|
+
TagData,
|
|
1063
|
+
} from '@murumets-ee/ticketing-ui'
|
|
1064
|
+
|
|
1065
|
+
interface TicketsPageProps {
|
|
1066
|
+
params: Promise<{ locale: string }>
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
export default async function TicketsPage({ params }: TicketsPageProps) {
|
|
1070
|
+
const { locale } = await params
|
|
1071
|
+
setRequestLocale(locale)
|
|
1072
|
+
|
|
1073
|
+
const h = await headers()
|
|
1074
|
+
const session = await auth.api.getSession({ headers: h })
|
|
1075
|
+
const currentUserId = session?.user?.id ?? ''
|
|
1076
|
+
|
|
1077
|
+
return withAdminContext(locale, async () => {
|
|
1078
|
+
const ticketData = await fetchEntityList(Ticket, {
|
|
1079
|
+
sortField: 'lastReplyAt',
|
|
1080
|
+
sortDirection: 'desc',
|
|
1081
|
+
limit: 50,
|
|
1082
|
+
})
|
|
1083
|
+
|
|
1084
|
+
const departmentData = await fetchEntityList(Department, {
|
|
1085
|
+
sortField: 'name',
|
|
1086
|
+
sortDirection: 'asc',
|
|
1087
|
+
limit: 100,
|
|
1088
|
+
})
|
|
1089
|
+
|
|
1090
|
+
const tagData = await fetchEntityList(TicketTag, {
|
|
1091
|
+
sortField: 'name',
|
|
1092
|
+
sortDirection: 'asc',
|
|
1093
|
+
limit: 100,
|
|
1094
|
+
})
|
|
1095
|
+
|
|
1096
|
+
return (
|
|
1097
|
+
<TicketsInboxClient
|
|
1098
|
+
initialTickets={ticketData.items as unknown as TicketData[]}
|
|
1099
|
+
initialTotal={ticketData.total}
|
|
1100
|
+
initialDepartments={departmentData.items as unknown as DepartmentData[]}
|
|
1101
|
+
initialTags={tagData.items as unknown as TagData[]}
|
|
1102
|
+
currentUserId={currentUserId}
|
|
1103
|
+
locale={locale}
|
|
1104
|
+
/>
|
|
1105
|
+
)
|
|
1106
|
+
})
|
|
1107
|
+
}
|
|
1108
|
+
`),await h(r(e,`app/[locale]/(shell)/admin/tickets/tickets-inbox-client.tsx`),`'use client'
|
|
1109
|
+
|
|
1110
|
+
import { QueryProvider } from '@murumets-ee/admin-ui'
|
|
1111
|
+
import { InboxProvider, TicketInbox } from '@murumets-ee/ticketing-ui/inbox'
|
|
1112
|
+
import type {
|
|
1113
|
+
TicketData,
|
|
1114
|
+
DepartmentData,
|
|
1115
|
+
TagData,
|
|
1116
|
+
} from '@murumets-ee/ticketing-ui'
|
|
1117
|
+
|
|
1118
|
+
interface TicketsInboxClientProps {
|
|
1119
|
+
initialTickets: TicketData[]
|
|
1120
|
+
initialTotal: number
|
|
1121
|
+
initialDepartments: DepartmentData[]
|
|
1122
|
+
initialTags: TagData[]
|
|
1123
|
+
currentUserId: string
|
|
1124
|
+
locale: string
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
export function TicketsInboxClient({
|
|
1128
|
+
initialTickets,
|
|
1129
|
+
initialTotal,
|
|
1130
|
+
initialDepartments,
|
|
1131
|
+
initialTags,
|
|
1132
|
+
currentUserId,
|
|
1133
|
+
}: TicketsInboxClientProps) {
|
|
1134
|
+
return (
|
|
1135
|
+
<QueryProvider>
|
|
1136
|
+
<InboxProvider
|
|
1137
|
+
apiBasePath="/api/admin"
|
|
1138
|
+
initialTickets={initialTickets}
|
|
1139
|
+
initialTotal={initialTotal}
|
|
1140
|
+
initialDepartments={initialDepartments}
|
|
1141
|
+
initialTags={initialTags}
|
|
1142
|
+
currentUserId={currentUserId}
|
|
1143
|
+
>
|
|
1144
|
+
<div className="h-[calc(100vh-3.5rem)]">
|
|
1145
|
+
<TicketInbox currentUserId={currentUserId} />
|
|
1146
|
+
</div>
|
|
1147
|
+
</InboxProvider>
|
|
1148
|
+
</QueryProvider>
|
|
1149
|
+
)
|
|
1150
|
+
}
|
|
1151
|
+
`),await h(r(e,`app/[locale]/(shell)/admin/tickets/board/page.tsx`),`import { Ticket, Department } from '@murumets-ee/ticketing'
|
|
1152
|
+
import { setRequestLocale } from 'next-intl/server'
|
|
1153
|
+
import { withAdminContext } from '@/lib/with-admin-context'
|
|
1154
|
+
import { fetchEntityList } from '@murumets-ee/admin-ui/server'
|
|
1155
|
+
import { TicketBoardClient } from './ticket-board-client'
|
|
1156
|
+
import type { TicketData, DepartmentData } from '@murumets-ee/ticketing-ui'
|
|
1157
|
+
|
|
1158
|
+
interface TicketBoardPageProps {
|
|
1159
|
+
params: Promise<{ locale: string }>
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
export default async function TicketBoardPage({ params }: TicketBoardPageProps) {
|
|
1163
|
+
const { locale } = await params
|
|
1164
|
+
setRequestLocale(locale)
|
|
1165
|
+
|
|
1166
|
+
return withAdminContext(locale, async () => {
|
|
1167
|
+
const ticketData = await fetchEntityList(Ticket, {
|
|
1168
|
+
sortField: 'lastReplyAt',
|
|
1169
|
+
sortDirection: 'desc',
|
|
1170
|
+
limit: 200,
|
|
1171
|
+
})
|
|
1172
|
+
|
|
1173
|
+
const departmentData = await fetchEntityList(Department, {
|
|
1174
|
+
sortField: 'name',
|
|
1175
|
+
sortDirection: 'asc',
|
|
1176
|
+
limit: 100,
|
|
1177
|
+
})
|
|
1178
|
+
|
|
1179
|
+
return (
|
|
1180
|
+
<div className="p-6 h-[calc(100vh-3.5rem)]">
|
|
1181
|
+
<div className="mb-4">
|
|
1182
|
+
<h1 className="text-2xl font-bold">Ticket Board</h1>
|
|
1183
|
+
<p className="text-sm text-muted-foreground">
|
|
1184
|
+
Kanban view of tickets by status
|
|
1185
|
+
</p>
|
|
1186
|
+
</div>
|
|
1187
|
+
<TicketBoardClient
|
|
1188
|
+
initialTickets={ticketData.items as unknown as TicketData[]}
|
|
1189
|
+
initialDepartments={departmentData.items as unknown as DepartmentData[]}
|
|
1190
|
+
locale={locale}
|
|
1191
|
+
/>
|
|
1192
|
+
</div>
|
|
1193
|
+
)
|
|
1194
|
+
})
|
|
1195
|
+
}
|
|
1196
|
+
`),await h(r(e,`app/[locale]/(shell)/admin/tickets/board/ticket-board-client.tsx`),`'use client'
|
|
1197
|
+
|
|
1198
|
+
import { useMemo } from 'react'
|
|
1199
|
+
import { TicketBoard } from '@murumets-ee/ticketing-ui/board'
|
|
1200
|
+
import type { TicketData, DepartmentData } from '@murumets-ee/ticketing-ui'
|
|
1201
|
+
import { useRouter } from 'next/navigation'
|
|
1202
|
+
|
|
1203
|
+
interface TicketBoardClientProps {
|
|
1204
|
+
initialTickets: TicketData[]
|
|
1205
|
+
initialDepartments: DepartmentData[]
|
|
1206
|
+
locale: string
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
export function TicketBoardClient({
|
|
1210
|
+
initialTickets,
|
|
1211
|
+
initialDepartments,
|
|
1212
|
+
locale,
|
|
1213
|
+
}: TicketBoardClientProps) {
|
|
1214
|
+
const router = useRouter()
|
|
1215
|
+
const departmentMap = useMemo(
|
|
1216
|
+
() => new Map(initialDepartments.map((d) => [d.id, d])),
|
|
1217
|
+
[initialDepartments],
|
|
1218
|
+
)
|
|
1219
|
+
|
|
1220
|
+
return (
|
|
1221
|
+
<TicketBoard
|
|
1222
|
+
tickets={initialTickets}
|
|
1223
|
+
departments={departmentMap}
|
|
1224
|
+
onTicketSelect={(ticket) => {
|
|
1225
|
+
router.push(\`/\${locale}/admin/tickets?selected=\${ticket.id}\`)
|
|
1226
|
+
}}
|
|
1227
|
+
className="h-[calc(100%-4rem)]"
|
|
1228
|
+
/>
|
|
1229
|
+
)
|
|
1230
|
+
}
|
|
1231
|
+
`),await h(r(e,`app/api/ticketing/inbound/route.ts`),`/**
|
|
1232
|
+
* Resend inbound email webhook endpoint.
|
|
1233
|
+
*
|
|
1234
|
+
* Public endpoint (no session auth) — protected by webhook signature
|
|
1235
|
+
* verification (HMAC-SHA256). Lives outside the admin API handler
|
|
1236
|
+
* because the admin handler requires authentication.
|
|
1237
|
+
*/
|
|
1238
|
+
|
|
1239
|
+
import { getToolkitApp } from '@/lib/app'
|
|
1240
|
+
|
|
1241
|
+
await getToolkitApp()
|
|
1242
|
+
|
|
1243
|
+
export async function POST(req: Request): Promise<Response> {
|
|
1244
|
+
const { handleInboundWebhook } = await import('@murumets-ee/ticketing')
|
|
1245
|
+
const { QueueClient } = await import('@murumets-ee/queue/client')
|
|
1246
|
+
const { getApp } = await import('@murumets-ee/core')
|
|
1247
|
+
|
|
1248
|
+
const db = getApp().db.readWrite
|
|
1249
|
+
const queueClient = new QueueClient({ db })
|
|
1250
|
+
|
|
1251
|
+
return handleInboundWebhook(req, (type, payload) => queueClient.enqueue(type, payload))
|
|
1252
|
+
}
|
|
1020
1253
|
`),await h(r(e,`app/api/admin/[...path]/route.ts`),`import { createAdminApiHandler } from '@murumets-ee/admin-ui/server'
|
|
1021
1254
|
import { buildPermissionChecker, buildResourceCatalog } from '@murumets-ee/auth'
|
|
1022
1255
|
import { permissionRoutes } from '@murumets-ee/auth/admin'
|
|
@@ -1036,6 +1269,7 @@ import { createSettingsClient } from '@murumets-ee/settings'
|
|
|
1036
1269
|
import { settingsRoutes } from '@murumets-ee/settings/admin'
|
|
1037
1270
|
import { storageRoutes } from '@murumets-ee/storage/admin'
|
|
1038
1271
|
import { taxonomyRoutes } from '@murumets-ee/taxonomy/admin'
|
|
1272
|
+
import { ticketingRoutes } from '@murumets-ee/ticketing/admin'
|
|
1039
1273
|
import {
|
|
1040
1274
|
allEntities,
|
|
1041
1275
|
crudEntities,
|
|
@@ -1058,6 +1292,7 @@ const routes = [
|
|
|
1058
1292
|
storageRoutes(),
|
|
1059
1293
|
settingsRoutes(siteSettings),
|
|
1060
1294
|
taxonomyRoutes(taxonomyVocabularies),
|
|
1295
|
+
ticketingRoutes(),
|
|
1061
1296
|
logRoutes(() => new AuditLogClient(getApp().db.readWrite)),
|
|
1062
1297
|
permissionRoutes({
|
|
1063
1298
|
getStatements: () => buildResourceCatalog(allEntities, pluginResources),
|
package/dist/index.mjs
CHANGED
|
@@ -1,14 +1,28 @@
|
|
|
1
1
|
import{i as e,n as t,o as n,r,t as i}from"./utils-Bmw75lEa.mjs";import{join as a}from"node:path";import{mkdir as o}from"node:fs/promises";async function s(e,t){await n(a(e,`lib/admin-config.ts`),`import { Media } from '@murumets-ee/media'
|
|
2
|
+
import {
|
|
3
|
+
Ticket,
|
|
4
|
+
TicketMessage,
|
|
5
|
+
TicketAttachment,
|
|
6
|
+
Department,
|
|
7
|
+
TicketTag,
|
|
8
|
+
} from '@murumets-ee/ticketing'
|
|
2
9
|
import { Article, Category } from '@/entities'
|
|
3
10
|
|
|
4
11
|
/** All entities available in the admin (drives sidebar nav + CRUD) */
|
|
5
|
-
export const allEntities = [
|
|
12
|
+
export const allEntities = [
|
|
13
|
+
Article, Media, Category,
|
|
14
|
+
Ticket, TicketMessage, TicketAttachment, Department, TicketTag,
|
|
15
|
+
]
|
|
6
16
|
|
|
7
17
|
/** Taxonomy entities keyed by name */
|
|
8
|
-
export const taxonomyVocabularies = {
|
|
18
|
+
export const taxonomyVocabularies = {
|
|
19
|
+
category: Category,
|
|
20
|
+
department: Department,
|
|
21
|
+
ticket_tag: TicketTag,
|
|
22
|
+
}
|
|
9
23
|
|
|
10
24
|
/** Entities exposed via generic CRUD API handler */
|
|
11
|
-
export const crudEntities = [Article, Media]
|
|
25
|
+
export const crudEntities = [Article, Media, Ticket, TicketMessage, TicketAttachment]
|
|
12
26
|
|
|
13
27
|
/** Plugin resources for the permission catalog */
|
|
14
28
|
export const pluginResources = [
|
|
@@ -16,6 +30,7 @@ export const pluginResources = [
|
|
|
16
30
|
{ resource: 'settings', actions: ['view', 'update'] },
|
|
17
31
|
{ resource: 'audit-logs', actions: ['view'] },
|
|
18
32
|
{ resource: 'permissions', actions: ['view', 'create', 'update', 'delete'] },
|
|
33
|
+
{ resource: 'ticketing', actions: ['view', 'create', 'update', 'delete'] },
|
|
19
34
|
]
|
|
20
35
|
`),await n(a(e,`lib/content-locale.ts`),`import { cookies } from 'next/headers'
|
|
21
36
|
import { hasLocale } from 'next-intl'
|
|
@@ -106,15 +121,20 @@ import { AdminShell } from '@murumets-ee/admin-ui'
|
|
|
106
121
|
import type { AdminNavGroup } from '@murumets-ee/admin-ui/server'
|
|
107
122
|
import {
|
|
108
123
|
Activity,
|
|
124
|
+
Building2,
|
|
109
125
|
FileText,
|
|
110
126
|
FolderTree,
|
|
111
127
|
Home,
|
|
112
128
|
Image,
|
|
113
129
|
ImageDown,
|
|
130
|
+
Inbox,
|
|
131
|
+
Kanban,
|
|
114
132
|
Lock,
|
|
115
133
|
PenTool,
|
|
116
134
|
ShieldCheck,
|
|
135
|
+
Tag,
|
|
117
136
|
Tags,
|
|
137
|
+
Ticket,
|
|
118
138
|
Users,
|
|
119
139
|
type LucideIcon,
|
|
120
140
|
} from 'lucide-react'
|
|
@@ -126,6 +146,7 @@ const ICON_MAP: Record<string, LucideIcon> = {
|
|
|
126
146
|
'file-text': FileText,
|
|
127
147
|
'folder-tree': FolderTree,
|
|
128
148
|
tags: Tags,
|
|
149
|
+
ticket: Ticket,
|
|
129
150
|
}
|
|
130
151
|
|
|
131
152
|
interface AdminLayoutProps {
|
|
@@ -161,6 +182,16 @@ export function AdminLayout({
|
|
|
161
182
|
],
|
|
162
183
|
}
|
|
163
184
|
|
|
185
|
+
const supportGroup: SidebarNavGroup = {
|
|
186
|
+
label: 'Support',
|
|
187
|
+
items: [
|
|
188
|
+
{ label: 'Tickets', href: '/admin/tickets', icon: Inbox },
|
|
189
|
+
{ label: 'Board', href: '/admin/tickets/board', icon: Kanban },
|
|
190
|
+
{ label: 'Departments', href: '/admin/departments', icon: Building2 },
|
|
191
|
+
{ label: 'Ticket Tags', href: '/admin/ticket-tags', icon: Tag },
|
|
192
|
+
],
|
|
193
|
+
}
|
|
194
|
+
|
|
164
195
|
const systemGroup: SidebarNavGroup = {
|
|
165
196
|
label: 'System',
|
|
166
197
|
items: [
|
|
@@ -176,6 +207,7 @@ export function AdminLayout({
|
|
|
176
207
|
const navGroups: SidebarNavGroup[] = [
|
|
177
208
|
staticBefore,
|
|
178
209
|
...dynamicGroups,
|
|
210
|
+
supportGroup,
|
|
179
211
|
systemGroup,
|
|
180
212
|
]
|
|
181
213
|
|
|
@@ -1016,6 +1048,207 @@ export default async function ActivityPage({ params }: { params: Promise<{ local
|
|
|
1016
1048
|
)
|
|
1017
1049
|
})
|
|
1018
1050
|
}
|
|
1051
|
+
`),await n(a(e,`app/[locale]/(shell)/admin/tickets/page.tsx`),`import { Ticket, Department, TicketTag } from '@murumets-ee/ticketing'
|
|
1052
|
+
import { headers } from 'next/headers'
|
|
1053
|
+
import { setRequestLocale } from 'next-intl/server'
|
|
1054
|
+
import { withAdminContext } from '@/lib/with-admin-context'
|
|
1055
|
+
import { auth } from '@/lib/auth'
|
|
1056
|
+
import { TicketsInboxClient } from './tickets-inbox-client'
|
|
1057
|
+
import { fetchEntityList } from '@murumets-ee/admin-ui/server'
|
|
1058
|
+
import type {
|
|
1059
|
+
TicketData,
|
|
1060
|
+
DepartmentData,
|
|
1061
|
+
TagData,
|
|
1062
|
+
} from '@murumets-ee/ticketing-ui'
|
|
1063
|
+
|
|
1064
|
+
interface TicketsPageProps {
|
|
1065
|
+
params: Promise<{ locale: string }>
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
export default async function TicketsPage({ params }: TicketsPageProps) {
|
|
1069
|
+
const { locale } = await params
|
|
1070
|
+
setRequestLocale(locale)
|
|
1071
|
+
|
|
1072
|
+
const h = await headers()
|
|
1073
|
+
const session = await auth.api.getSession({ headers: h })
|
|
1074
|
+
const currentUserId = session?.user?.id ?? ''
|
|
1075
|
+
|
|
1076
|
+
return withAdminContext(locale, async () => {
|
|
1077
|
+
const ticketData = await fetchEntityList(Ticket, {
|
|
1078
|
+
sortField: 'lastReplyAt',
|
|
1079
|
+
sortDirection: 'desc',
|
|
1080
|
+
limit: 50,
|
|
1081
|
+
})
|
|
1082
|
+
|
|
1083
|
+
const departmentData = await fetchEntityList(Department, {
|
|
1084
|
+
sortField: 'name',
|
|
1085
|
+
sortDirection: 'asc',
|
|
1086
|
+
limit: 100,
|
|
1087
|
+
})
|
|
1088
|
+
|
|
1089
|
+
const tagData = await fetchEntityList(TicketTag, {
|
|
1090
|
+
sortField: 'name',
|
|
1091
|
+
sortDirection: 'asc',
|
|
1092
|
+
limit: 100,
|
|
1093
|
+
})
|
|
1094
|
+
|
|
1095
|
+
return (
|
|
1096
|
+
<TicketsInboxClient
|
|
1097
|
+
initialTickets={ticketData.items as unknown as TicketData[]}
|
|
1098
|
+
initialTotal={ticketData.total}
|
|
1099
|
+
initialDepartments={departmentData.items as unknown as DepartmentData[]}
|
|
1100
|
+
initialTags={tagData.items as unknown as TagData[]}
|
|
1101
|
+
currentUserId={currentUserId}
|
|
1102
|
+
locale={locale}
|
|
1103
|
+
/>
|
|
1104
|
+
)
|
|
1105
|
+
})
|
|
1106
|
+
}
|
|
1107
|
+
`),await n(a(e,`app/[locale]/(shell)/admin/tickets/tickets-inbox-client.tsx`),`'use client'
|
|
1108
|
+
|
|
1109
|
+
import { QueryProvider } from '@murumets-ee/admin-ui'
|
|
1110
|
+
import { InboxProvider, TicketInbox } from '@murumets-ee/ticketing-ui/inbox'
|
|
1111
|
+
import type {
|
|
1112
|
+
TicketData,
|
|
1113
|
+
DepartmentData,
|
|
1114
|
+
TagData,
|
|
1115
|
+
} from '@murumets-ee/ticketing-ui'
|
|
1116
|
+
|
|
1117
|
+
interface TicketsInboxClientProps {
|
|
1118
|
+
initialTickets: TicketData[]
|
|
1119
|
+
initialTotal: number
|
|
1120
|
+
initialDepartments: DepartmentData[]
|
|
1121
|
+
initialTags: TagData[]
|
|
1122
|
+
currentUserId: string
|
|
1123
|
+
locale: string
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
export function TicketsInboxClient({
|
|
1127
|
+
initialTickets,
|
|
1128
|
+
initialTotal,
|
|
1129
|
+
initialDepartments,
|
|
1130
|
+
initialTags,
|
|
1131
|
+
currentUserId,
|
|
1132
|
+
}: TicketsInboxClientProps) {
|
|
1133
|
+
return (
|
|
1134
|
+
<QueryProvider>
|
|
1135
|
+
<InboxProvider
|
|
1136
|
+
apiBasePath="/api/admin"
|
|
1137
|
+
initialTickets={initialTickets}
|
|
1138
|
+
initialTotal={initialTotal}
|
|
1139
|
+
initialDepartments={initialDepartments}
|
|
1140
|
+
initialTags={initialTags}
|
|
1141
|
+
currentUserId={currentUserId}
|
|
1142
|
+
>
|
|
1143
|
+
<div className="h-[calc(100vh-3.5rem)]">
|
|
1144
|
+
<TicketInbox currentUserId={currentUserId} />
|
|
1145
|
+
</div>
|
|
1146
|
+
</InboxProvider>
|
|
1147
|
+
</QueryProvider>
|
|
1148
|
+
)
|
|
1149
|
+
}
|
|
1150
|
+
`),await n(a(e,`app/[locale]/(shell)/admin/tickets/board/page.tsx`),`import { Ticket, Department } from '@murumets-ee/ticketing'
|
|
1151
|
+
import { setRequestLocale } from 'next-intl/server'
|
|
1152
|
+
import { withAdminContext } from '@/lib/with-admin-context'
|
|
1153
|
+
import { fetchEntityList } from '@murumets-ee/admin-ui/server'
|
|
1154
|
+
import { TicketBoardClient } from './ticket-board-client'
|
|
1155
|
+
import type { TicketData, DepartmentData } from '@murumets-ee/ticketing-ui'
|
|
1156
|
+
|
|
1157
|
+
interface TicketBoardPageProps {
|
|
1158
|
+
params: Promise<{ locale: string }>
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
export default async function TicketBoardPage({ params }: TicketBoardPageProps) {
|
|
1162
|
+
const { locale } = await params
|
|
1163
|
+
setRequestLocale(locale)
|
|
1164
|
+
|
|
1165
|
+
return withAdminContext(locale, async () => {
|
|
1166
|
+
const ticketData = await fetchEntityList(Ticket, {
|
|
1167
|
+
sortField: 'lastReplyAt',
|
|
1168
|
+
sortDirection: 'desc',
|
|
1169
|
+
limit: 200,
|
|
1170
|
+
})
|
|
1171
|
+
|
|
1172
|
+
const departmentData = await fetchEntityList(Department, {
|
|
1173
|
+
sortField: 'name',
|
|
1174
|
+
sortDirection: 'asc',
|
|
1175
|
+
limit: 100,
|
|
1176
|
+
})
|
|
1177
|
+
|
|
1178
|
+
return (
|
|
1179
|
+
<div className="p-6 h-[calc(100vh-3.5rem)]">
|
|
1180
|
+
<div className="mb-4">
|
|
1181
|
+
<h1 className="text-2xl font-bold">Ticket Board</h1>
|
|
1182
|
+
<p className="text-sm text-muted-foreground">
|
|
1183
|
+
Kanban view of tickets by status
|
|
1184
|
+
</p>
|
|
1185
|
+
</div>
|
|
1186
|
+
<TicketBoardClient
|
|
1187
|
+
initialTickets={ticketData.items as unknown as TicketData[]}
|
|
1188
|
+
initialDepartments={departmentData.items as unknown as DepartmentData[]}
|
|
1189
|
+
locale={locale}
|
|
1190
|
+
/>
|
|
1191
|
+
</div>
|
|
1192
|
+
)
|
|
1193
|
+
})
|
|
1194
|
+
}
|
|
1195
|
+
`),await n(a(e,`app/[locale]/(shell)/admin/tickets/board/ticket-board-client.tsx`),`'use client'
|
|
1196
|
+
|
|
1197
|
+
import { useMemo } from 'react'
|
|
1198
|
+
import { TicketBoard } from '@murumets-ee/ticketing-ui/board'
|
|
1199
|
+
import type { TicketData, DepartmentData } from '@murumets-ee/ticketing-ui'
|
|
1200
|
+
import { useRouter } from 'next/navigation'
|
|
1201
|
+
|
|
1202
|
+
interface TicketBoardClientProps {
|
|
1203
|
+
initialTickets: TicketData[]
|
|
1204
|
+
initialDepartments: DepartmentData[]
|
|
1205
|
+
locale: string
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
export function TicketBoardClient({
|
|
1209
|
+
initialTickets,
|
|
1210
|
+
initialDepartments,
|
|
1211
|
+
locale,
|
|
1212
|
+
}: TicketBoardClientProps) {
|
|
1213
|
+
const router = useRouter()
|
|
1214
|
+
const departmentMap = useMemo(
|
|
1215
|
+
() => new Map(initialDepartments.map((d) => [d.id, d])),
|
|
1216
|
+
[initialDepartments],
|
|
1217
|
+
)
|
|
1218
|
+
|
|
1219
|
+
return (
|
|
1220
|
+
<TicketBoard
|
|
1221
|
+
tickets={initialTickets}
|
|
1222
|
+
departments={departmentMap}
|
|
1223
|
+
onTicketSelect={(ticket) => {
|
|
1224
|
+
router.push(\`/\${locale}/admin/tickets?selected=\${ticket.id}\`)
|
|
1225
|
+
}}
|
|
1226
|
+
className="h-[calc(100%-4rem)]"
|
|
1227
|
+
/>
|
|
1228
|
+
)
|
|
1229
|
+
}
|
|
1230
|
+
`),await n(a(e,`app/api/ticketing/inbound/route.ts`),`/**
|
|
1231
|
+
* Resend inbound email webhook endpoint.
|
|
1232
|
+
*
|
|
1233
|
+
* Public endpoint (no session auth) — protected by webhook signature
|
|
1234
|
+
* verification (HMAC-SHA256). Lives outside the admin API handler
|
|
1235
|
+
* because the admin handler requires authentication.
|
|
1236
|
+
*/
|
|
1237
|
+
|
|
1238
|
+
import { getToolkitApp } from '@/lib/app'
|
|
1239
|
+
|
|
1240
|
+
await getToolkitApp()
|
|
1241
|
+
|
|
1242
|
+
export async function POST(req: Request): Promise<Response> {
|
|
1243
|
+
const { handleInboundWebhook } = await import('@murumets-ee/ticketing')
|
|
1244
|
+
const { QueueClient } = await import('@murumets-ee/queue/client')
|
|
1245
|
+
const { getApp } = await import('@murumets-ee/core')
|
|
1246
|
+
|
|
1247
|
+
const db = getApp().db.readWrite
|
|
1248
|
+
const queueClient = new QueueClient({ db })
|
|
1249
|
+
|
|
1250
|
+
return handleInboundWebhook(req, (type, payload) => queueClient.enqueue(type, payload))
|
|
1251
|
+
}
|
|
1019
1252
|
`),await n(a(e,`app/api/admin/[...path]/route.ts`),`import { createAdminApiHandler } from '@murumets-ee/admin-ui/server'
|
|
1020
1253
|
import { buildPermissionChecker, buildResourceCatalog } from '@murumets-ee/auth'
|
|
1021
1254
|
import { permissionRoutes } from '@murumets-ee/auth/admin'
|
|
@@ -1035,6 +1268,7 @@ import { createSettingsClient } from '@murumets-ee/settings'
|
|
|
1035
1268
|
import { settingsRoutes } from '@murumets-ee/settings/admin'
|
|
1036
1269
|
import { storageRoutes } from '@murumets-ee/storage/admin'
|
|
1037
1270
|
import { taxonomyRoutes } from '@murumets-ee/taxonomy/admin'
|
|
1271
|
+
import { ticketingRoutes } from '@murumets-ee/ticketing/admin'
|
|
1038
1272
|
import {
|
|
1039
1273
|
allEntities,
|
|
1040
1274
|
crudEntities,
|
|
@@ -1057,6 +1291,7 @@ const routes = [
|
|
|
1057
1291
|
storageRoutes(),
|
|
1058
1292
|
settingsRoutes(siteSettings),
|
|
1059
1293
|
taxonomyRoutes(taxonomyVocabularies),
|
|
1294
|
+
ticketingRoutes(),
|
|
1060
1295
|
logRoutes(() => new AuditLogClient(getApp().db.readWrite)),
|
|
1061
1296
|
permissionRoutes({
|
|
1062
1297
|
getStatements: () => buildResourceCatalog(allEntities, pluginResources),
|