spine-framework-cortex 0.2.22 → 0.2.23
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/package.json +1 -1
- package/pages/crm/AccountDetailPage.tsx +5 -2
- package/pages/crm/AccountsPage.tsx +3 -1
- package/pages/crm/DealDetailPage.tsx +5 -3
- package/pages/crm/DealsPage.tsx +4 -2
- package/pages/kb/KBEditorPage.tsx +4 -2
- package/pages/kb/KBPage.tsx +6 -4
- package/pages/support/RedactionReview.tsx +5 -3
- package/pages/support/SupportPage.tsx +4 -2
- package/pages/support/TicketDetailPage.tsx +4 -2
package/package.json
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { useEffect, useState } from 'react'
|
|
2
2
|
import { useParams, useNavigate } from 'react-router-dom'
|
|
3
|
+
import { useAppPath } from '@core/hooks/useAppPath'
|
|
3
4
|
import { apiFetch } from '@core/lib/api'
|
|
4
5
|
import { Tabs, TabsList, TabsTrigger, TabsContent } from '@core/components/ui/tabs'
|
|
5
6
|
import { Badge } from '@core/components/ui/badge'
|
|
@@ -81,6 +82,7 @@ function PeopleTab({ accountId }: { accountId: string }) {
|
|
|
81
82
|
|
|
82
83
|
function ItemsTab({ accountId, typeSlug, emptyText }: { accountId: string; typeSlug: string; emptyText: string }) {
|
|
83
84
|
const navigate = useNavigate()
|
|
85
|
+
const appPath = useAppPath()
|
|
84
86
|
const [items, setItems] = useState<Item[]>([])
|
|
85
87
|
const [loading, setLoading] = useState(true)
|
|
86
88
|
useEffect(() => {
|
|
@@ -94,7 +96,7 @@ function ItemsTab({ accountId, typeSlug, emptyText }: { accountId: string; typeS
|
|
|
94
96
|
{items.map(item => (
|
|
95
97
|
<div key={item.id}
|
|
96
98
|
className="flex items-center gap-3 px-4 py-3 hover:bg-accent/50 cursor-pointer transition-colors"
|
|
97
|
-
onClick={() => typeSlug === 'support_ticket' ? navigate(`/
|
|
99
|
+
onClick={() => typeSlug === 'support_ticket' ? navigate(appPath(`/support/${item.id}`)) : typeSlug === 'deal' ? navigate(appPath(`/crm/deals/${item.id}`)) : undefined}
|
|
98
100
|
>
|
|
99
101
|
<div className="flex-1 min-w-0">
|
|
100
102
|
<p className="text-sm font-medium truncate">{item.title}</p>
|
|
@@ -330,6 +332,7 @@ function FunnelTab({ account }: { account: Account }) {
|
|
|
330
332
|
export default function AccountDetailPage() {
|
|
331
333
|
const { id } = useParams<{ id: string }>()
|
|
332
334
|
const navigate = useNavigate()
|
|
335
|
+
const appPath = useAppPath()
|
|
333
336
|
const [account, setAccount] = useState<Account | null>(null)
|
|
334
337
|
const [loading, setLoading] = useState(true)
|
|
335
338
|
|
|
@@ -345,7 +348,7 @@ export default function AccountDetailPage() {
|
|
|
345
348
|
return (
|
|
346
349
|
<div className="flex flex-col h-full">
|
|
347
350
|
<div className="px-6 py-4 border-b border-border shrink-0">
|
|
348
|
-
<Button variant="ghost" size="sm" className="mb-2 -ml-2 gap-1 text-muted-foreground" onClick={() => navigate('/
|
|
351
|
+
<Button variant="ghost" size="sm" className="mb-2 -ml-2 gap-1 text-muted-foreground" onClick={() => navigate(appPath('/crm/accounts'))}>
|
|
349
352
|
<ArrowLeft className="h-3.5 w-3.5" /> Accounts
|
|
350
353
|
</Button>
|
|
351
354
|
<div className="flex items-center gap-3">
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { useEffect, useState } from 'react'
|
|
2
2
|
import { useNavigate } from 'react-router-dom'
|
|
3
|
+
import { useAppPath } from '@core/hooks/useAppPath'
|
|
3
4
|
import { apiFetch } from '@core/lib/api'
|
|
4
5
|
import { Input } from '@core/components/ui/input'
|
|
5
6
|
import { Badge } from '@core/components/ui/badge'
|
|
@@ -26,6 +27,7 @@ type Filter = 'all'
|
|
|
26
27
|
|
|
27
28
|
export default function AccountsPage() {
|
|
28
29
|
const navigate = useNavigate()
|
|
30
|
+
const appPath = useAppPath()
|
|
29
31
|
const [accounts, setAccounts] = useState<Account[]>([])
|
|
30
32
|
const [loading, setLoading] = useState(true)
|
|
31
33
|
const [search, setSearch] = useState('')
|
|
@@ -91,7 +93,7 @@ export default function AccountsPage() {
|
|
|
91
93
|
{filtered.map(account => (
|
|
92
94
|
<tr
|
|
93
95
|
key={account.id}
|
|
94
|
-
onClick={() => navigate(`/
|
|
96
|
+
onClick={() => navigate(appPath(`/crm/accounts/${account.id}`))}
|
|
95
97
|
className="hover:bg-accent/50 cursor-pointer transition-colors"
|
|
96
98
|
>
|
|
97
99
|
<td className="px-5 py-3 font-medium">
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { useEffect, useState } from 'react'
|
|
2
2
|
import { useNavigate, useParams } from 'react-router-dom'
|
|
3
|
+
import { useAppPath } from '@core/hooks/useAppPath'
|
|
3
4
|
import { apiFetch } from '@core/lib/api'
|
|
4
5
|
|
|
5
6
|
const STAGES = ['prospecting', 'qualification', 'proposal', 'negotiation', 'closed_won', 'closed_lost']
|
|
@@ -19,6 +20,7 @@ const EMPTY: DealForm = { title: '', stage: 'prospecting', value: '', close_date
|
|
|
19
20
|
export default function DealDetailPage() {
|
|
20
21
|
const { id } = useParams<{ id: string }>()
|
|
21
22
|
const navigate = useNavigate()
|
|
23
|
+
const appPath = useAppPath()
|
|
22
24
|
const isNew = !id || id === 'new'
|
|
23
25
|
|
|
24
26
|
const [form, setForm] = useState<DealForm>(EMPTY)
|
|
@@ -82,7 +84,7 @@ export default function DealDetailPage() {
|
|
|
82
84
|
const handleDelete = async () => {
|
|
83
85
|
if (!confirm('Delete this deal?')) return
|
|
84
86
|
await apiFetch(`/api/admin-data?action=delete&entity=items&id=${id}`, { method: 'POST' })
|
|
85
|
-
navigate('/
|
|
87
|
+
navigate(appPath('/crm/deals'))
|
|
86
88
|
}
|
|
87
89
|
|
|
88
90
|
const set = (field: keyof DealForm) => (e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement>) =>
|
|
@@ -93,7 +95,7 @@ export default function DealDetailPage() {
|
|
|
93
95
|
return (
|
|
94
96
|
<div className="p-6 max-w-2xl">
|
|
95
97
|
<div className="flex items-center gap-3 mb-6">
|
|
96
|
-
<button onClick={() => navigate('/
|
|
98
|
+
<button onClick={() => navigate(appPath('/crm/deals'))} className="text-slate-400 hover:text-slate-700 text-sm">
|
|
97
99
|
← Deals
|
|
98
100
|
</button>
|
|
99
101
|
<h1 className="text-xl font-bold text-slate-900">{isNew ? 'New Deal' : 'Edit Deal'}</h1>
|
|
@@ -174,7 +176,7 @@ export default function DealDetailPage() {
|
|
|
174
176
|
</button>
|
|
175
177
|
) : <div />}
|
|
176
178
|
<div className="flex gap-3">
|
|
177
|
-
<button onClick={() => navigate('/
|
|
179
|
+
<button onClick={() => navigate(appPath('/crm/deals'))} className="px-4 py-2 text-sm text-slate-600 border border-slate-200 rounded-lg hover:bg-slate-50">
|
|
178
180
|
Cancel
|
|
179
181
|
</button>
|
|
180
182
|
<button
|
package/pages/crm/DealsPage.tsx
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { useEffect, useState } from 'react'
|
|
2
2
|
import { useNavigate } from 'react-router-dom'
|
|
3
|
+
import { useAppPath } from '@core/hooks/useAppPath'
|
|
3
4
|
import { apiFetch } from '@core/lib/api'
|
|
4
5
|
|
|
5
6
|
interface Deal {
|
|
@@ -40,6 +41,7 @@ function DealCard({ deal, onClick }: { deal: Deal; onClick: () => void }) {
|
|
|
40
41
|
|
|
41
42
|
export default function DealsPage() {
|
|
42
43
|
const navigate = useNavigate()
|
|
44
|
+
const appPath = useAppPath()
|
|
43
45
|
const [deals, setDeals] = useState<Deal[]>([])
|
|
44
46
|
const [loading, setLoading] = useState(true)
|
|
45
47
|
const [view, setView] = useState<'kanban' | 'list'>('kanban')
|
|
@@ -77,7 +79,7 @@ export default function DealsPage() {
|
|
|
77
79
|
</button>
|
|
78
80
|
</div>
|
|
79
81
|
<button
|
|
80
|
-
onClick={() => navigate('/
|
|
82
|
+
onClick={() => navigate(appPath('/crm/deals/new'))}
|
|
81
83
|
className="bg-blue-600 text-white text-sm font-medium px-4 py-2 rounded-lg hover:bg-blue-700 transition-colors"
|
|
82
84
|
>
|
|
83
85
|
+ New Deal
|
|
@@ -133,7 +135,7 @@ export default function DealsPage() {
|
|
|
133
135
|
<tr>
|
|
134
136
|
<td colSpan={5} className="px-5 py-12 text-center text-slate-400">
|
|
135
137
|
No deals yet.{' '}
|
|
136
|
-
<button onClick={() => navigate('/
|
|
138
|
+
<button onClick={() => navigate(appPath('/crm/deals/new'))} className="text-blue-600 hover:underline">
|
|
137
139
|
Create one →
|
|
138
140
|
</button>
|
|
139
141
|
</td>
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { useState, useEffect } from 'react'
|
|
2
2
|
import { useParams, useNavigate } from 'react-router-dom'
|
|
3
|
+
import { useAppPath } from '@core/hooks/useAppPath'
|
|
3
4
|
import { apiFetch } from '@core/lib/api'
|
|
4
5
|
import { getTypeIdAsync } from '../../hooks/useTypeRegistry'
|
|
5
6
|
import { RichTextEditor } from '@core/components/ui/RichTextEditor'
|
|
@@ -66,6 +67,7 @@ function SidebarSelect({ label, value, onChange, options }: {
|
|
|
66
67
|
export default function KBEditorPage() {
|
|
67
68
|
const { id } = useParams<{ id: string }>()
|
|
68
69
|
const navigate = useNavigate()
|
|
70
|
+
const appPath = useAppPath()
|
|
69
71
|
const isNew = !id || id === 'new'
|
|
70
72
|
const [form, setForm] = useState<ArticleForm>(EMPTY)
|
|
71
73
|
const [loading, setLoading] = useState(!isNew)
|
|
@@ -157,7 +159,7 @@ export default function KBEditorPage() {
|
|
|
157
159
|
}
|
|
158
160
|
}
|
|
159
161
|
|
|
160
|
-
navigate('/
|
|
162
|
+
navigate(appPath('/kb'))
|
|
161
163
|
} catch (e: any) {
|
|
162
164
|
setError(e.message)
|
|
163
165
|
} finally {
|
|
@@ -184,7 +186,7 @@ export default function KBEditorPage() {
|
|
|
184
186
|
{/* Header */}
|
|
185
187
|
<div className="px-6 py-3 border-b border-border shrink-0 flex items-center justify-between">
|
|
186
188
|
<div className="flex items-center gap-3">
|
|
187
|
-
<Button variant="ghost" size="sm" className="gap-1 text-muted-foreground" onClick={() => navigate('/
|
|
189
|
+
<Button variant="ghost" size="sm" className="gap-1 text-muted-foreground" onClick={() => navigate(appPath('/kb'))}>
|
|
188
190
|
<ArrowLeft className="h-3.5 w-3.5" /> KB
|
|
189
191
|
</Button>
|
|
190
192
|
<h1 className="text-base font-semibold">{isNew ? 'New Article' : 'Edit Article'}</h1>
|
package/pages/kb/KBPage.tsx
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { useState, useEffect, useRef, useCallback } from 'react'
|
|
2
2
|
import { useNavigate } from 'react-router-dom'
|
|
3
|
+
import { useAppPath } from '@core/hooks/useAppPath'
|
|
3
4
|
import { apiFetch } from '@core/lib/api'
|
|
4
5
|
import { RichTextEditor } from '@core/components/ui/RichTextEditor'
|
|
5
6
|
import { Button } from '@core/components/ui/button'
|
|
@@ -49,6 +50,7 @@ const PRIORITY_COLORS: Record<string, string> = {
|
|
|
49
50
|
|
|
50
51
|
export default function KBPage() {
|
|
51
52
|
const navigate = useNavigate()
|
|
53
|
+
const appPath = useAppPath()
|
|
52
54
|
const [articles, setArticles] = useState<Article[]>([])
|
|
53
55
|
const [searchResults, setSearchResults] = useState<Article[] | null>(null)
|
|
54
56
|
const [loading, setLoading] = useState(true)
|
|
@@ -120,7 +122,7 @@ export default function KBPage() {
|
|
|
120
122
|
onChange={e => handleSearch(e.target.value)}
|
|
121
123
|
className="flex-1 h-8"
|
|
122
124
|
/>
|
|
123
|
-
<Button size="sm" className="gap-1 shrink-0" onClick={() => navigate('/
|
|
125
|
+
<Button size="sm" className="gap-1 shrink-0" onClick={() => navigate(appPath('/kb/new'))}>
|
|
124
126
|
<Plus size={14} /> New
|
|
125
127
|
</Button>
|
|
126
128
|
</div>
|
|
@@ -153,7 +155,7 @@ export default function KBPage() {
|
|
|
153
155
|
<div className="p-8 text-center text-muted-foreground">
|
|
154
156
|
<BookOpen className="h-10 w-10 mx-auto mb-3 opacity-30" />
|
|
155
157
|
<p className="text-sm">{search ? 'No articles match.' : 'No articles yet.'}</p>
|
|
156
|
-
<Button size="sm" variant="outline" className="mt-3 gap-1" onClick={() => navigate('/
|
|
158
|
+
<Button size="sm" variant="outline" className="mt-3 gap-1" onClick={() => navigate(appPath('/kb/new'))}>
|
|
157
159
|
<Plus size={13} /> Create article
|
|
158
160
|
</Button>
|
|
159
161
|
</div>
|
|
@@ -198,7 +200,7 @@ export default function KBPage() {
|
|
|
198
200
|
<div className="flex-1 flex flex-col items-center justify-center gap-3 text-muted-foreground">
|
|
199
201
|
<BookOpen size={32} className="opacity-30" />
|
|
200
202
|
<p className="text-sm">Select an article to preview</p>
|
|
201
|
-
<Button size="sm" variant="outline" onClick={() => navigate('/
|
|
203
|
+
<Button size="sm" variant="outline" onClick={() => navigate(appPath('/kb/new'))} className="gap-1">
|
|
202
204
|
<Plus size={13} /> New article
|
|
203
205
|
</Button>
|
|
204
206
|
</div>
|
|
@@ -236,7 +238,7 @@ export default function KBPage() {
|
|
|
236
238
|
size="sm"
|
|
237
239
|
variant="outline"
|
|
238
240
|
className="gap-1 shrink-0"
|
|
239
|
-
onClick={() => navigate(`/
|
|
241
|
+
onClick={() => navigate(appPath(`/kb/${selected.id}/edit`))}
|
|
240
242
|
>
|
|
241
243
|
<Edit size={13} /> Edit
|
|
242
244
|
</Button>
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { useState, useEffect, useCallback } from 'react'
|
|
2
2
|
import { useParams, useNavigate } from 'react-router-dom'
|
|
3
|
+
import { useAppPath } from '@core/hooks/useAppPath'
|
|
3
4
|
import { apiFetch } from '@core/lib/api'
|
|
4
5
|
import { getTypeIdAsync } from '../../hooks/useTypeRegistry'
|
|
5
6
|
import { Button } from '@core/components/ui/button'
|
|
@@ -94,6 +95,7 @@ function parseContentWithRedactions(content: string, suggestions: RedactionSugge
|
|
|
94
95
|
export default function RedactionReview() {
|
|
95
96
|
const { id } = useParams<{ id: string }>()
|
|
96
97
|
const navigate = useNavigate()
|
|
98
|
+
const appPath = useAppPath()
|
|
97
99
|
const [ticket, setTicket] = useState<Ticket | null>(null)
|
|
98
100
|
const [analysis, setAnalysis] = useState<RedactionAnalysis | null>(null)
|
|
99
101
|
const [loading, setLoading] = useState(true)
|
|
@@ -311,7 +313,7 @@ export default function RedactionReview() {
|
|
|
311
313
|
}),
|
|
312
314
|
})
|
|
313
315
|
|
|
314
|
-
navigate(`/
|
|
316
|
+
navigate(appPath(`/kb/${kbId}`))
|
|
315
317
|
}
|
|
316
318
|
} finally {
|
|
317
319
|
setProcessing(false)
|
|
@@ -334,7 +336,7 @@ export default function RedactionReview() {
|
|
|
334
336
|
<AlertTriangle className="h-5 w-5" />
|
|
335
337
|
<p>Failed to analyze content for redaction. Please try again.</p>
|
|
336
338
|
</div>
|
|
337
|
-
<Button onClick={() => navigate(`/
|
|
339
|
+
<Button onClick={() => navigate(appPath(`/support/${id}`))}>Back to Ticket</Button>
|
|
338
340
|
</div>
|
|
339
341
|
)
|
|
340
342
|
}
|
|
@@ -345,7 +347,7 @@ export default function RedactionReview() {
|
|
|
345
347
|
<div className="px-6 py-4 border-b border-border shrink-0">
|
|
346
348
|
<div className="flex items-center justify-between">
|
|
347
349
|
<div className="flex items-center gap-3">
|
|
348
|
-
<Button variant="ghost" size="sm" onClick={() => navigate(`/
|
|
350
|
+
<Button variant="ghost" size="sm" onClick={() => navigate(appPath(`/support/${id}`))}>
|
|
349
351
|
<ArrowLeft className="h-4 w-4" />
|
|
350
352
|
</Button>
|
|
351
353
|
<div>
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { useEffect, useState, useMemo } from 'react'
|
|
2
2
|
import { useNavigate } from 'react-router-dom'
|
|
3
|
+
import { useAppPath } from '@core/hooks/useAppPath'
|
|
3
4
|
import { apiFetch } from '@core/lib/api'
|
|
4
5
|
import { useAuth } from '@core/contexts/AuthContext'
|
|
5
6
|
import { Input } from '@core/components/ui/input'
|
|
@@ -105,6 +106,7 @@ const KANBAN_COLUMNS = [
|
|
|
105
106
|
|
|
106
107
|
export default function SupportPage() {
|
|
107
108
|
const navigate = useNavigate()
|
|
109
|
+
const appPath = useAppPath()
|
|
108
110
|
const { user } = useAuth()
|
|
109
111
|
const [tickets, setTickets] = useState<Ticket[]>([])
|
|
110
112
|
const [loading, setLoading] = useState(true)
|
|
@@ -220,7 +222,7 @@ export default function SupportPage() {
|
|
|
220
222
|
{sorted.map(ticket => (
|
|
221
223
|
<tr
|
|
222
224
|
key={ticket.id}
|
|
223
|
-
onClick={() => navigate(`/
|
|
225
|
+
onClick={() => navigate(appPath(`/support/${ticket.id}`))}
|
|
224
226
|
className="hover:bg-accent/50 cursor-pointer transition-colors"
|
|
225
227
|
>
|
|
226
228
|
<td className="px-5 py-3">
|
|
@@ -305,7 +307,7 @@ export default function SupportPage() {
|
|
|
305
307
|
{tickets?.map(ticket => (
|
|
306
308
|
<div
|
|
307
309
|
key={ticket.id}
|
|
308
|
-
onClick={() => navigate(`/
|
|
310
|
+
onClick={() => navigate(appPath(`/support/${ticket.id}`))}
|
|
309
311
|
className="bg-background border rounded-lg p-3 cursor-pointer hover:shadow-sm transition-shadow"
|
|
310
312
|
>
|
|
311
313
|
<div className="flex items-start justify-between gap-2">
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { useEffect, useRef, useState } from 'react'
|
|
2
2
|
import { useParams, useNavigate } from 'react-router-dom'
|
|
3
|
+
import { useAppPath } from '@core/hooks/useAppPath'
|
|
3
4
|
import { apiFetch } from '@core/lib/api'
|
|
4
5
|
import { Button } from '@core/components/ui/button'
|
|
5
6
|
import { Input } from '@core/components/ui/input'
|
|
@@ -728,6 +729,7 @@ function Separator() {
|
|
|
728
729
|
export default function TicketDetailPage() {
|
|
729
730
|
const { id } = useParams<{ id: string }>()
|
|
730
731
|
const navigate = useNavigate()
|
|
732
|
+
const appPath = useAppPath()
|
|
731
733
|
const { user } = useAuth()
|
|
732
734
|
const [ticket, setTicket] = useState<Ticket | null>(null)
|
|
733
735
|
const [threads, setThreads] = useState<Thread[]>([])
|
|
@@ -837,7 +839,7 @@ export default function TicketDetailPage() {
|
|
|
837
839
|
}
|
|
838
840
|
|
|
839
841
|
const handleGenerateKB = () => {
|
|
840
|
-
navigate(`/
|
|
842
|
+
navigate(appPath(`/support/${id}/kb-review`))
|
|
841
843
|
}
|
|
842
844
|
|
|
843
845
|
const externalThread = threads.find(t => !t.visibility || t.visibility === 'external') ?? null
|
|
@@ -850,7 +852,7 @@ export default function TicketDetailPage() {
|
|
|
850
852
|
<div className="flex flex-col h-full">
|
|
851
853
|
<div className="px-6 py-4 border-b border-border shrink-0 flex items-center justify-between">
|
|
852
854
|
<div className="flex items-center gap-3">
|
|
853
|
-
<Button variant="ghost" size="sm" className="gap-1 text-muted-foreground" onClick={() => navigate('/
|
|
855
|
+
<Button variant="ghost" size="sm" className="gap-1 text-muted-foreground" onClick={() => navigate(appPath('/support'))}>
|
|
854
856
|
<ArrowLeft className="h-3.5 w-3.5" /> Support
|
|
855
857
|
</Button>
|
|
856
858
|
<div>
|