create-quadrokit 0.2.11 → 0.2.13
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/templates/README.md +4 -4
- package/templates/admin-shell/src/components/layout/AppShell.tsx +55 -0
- package/templates/admin-shell/src/components/layout/LanguageSwitcher.tsx +37 -0
- package/templates/admin-shell/src/components/layout/NotificationsMenu.tsx +79 -0
- package/templates/admin-shell/src/components/layout/PageHeading.tsx +40 -0
- package/templates/admin-shell/src/components/layout/Sidebar.tsx +65 -0
- package/templates/admin-shell/src/components/layout/ThemeMenu.tsx +90 -0
- package/templates/admin-shell/src/components/layout/TopHeader.tsx +78 -0
- package/templates/admin-shell/src/i18n.ts +16 -2
- package/templates/admin-shell/src/locales/en.json +48 -1
- package/templates/admin-shell/src/locales/fr.json +62 -0
- package/templates/admin-shell/src/main.tsx +8 -2
- package/templates/admin-shell/src/pages/HomePage.tsx +1 -2
- package/templates/admin-shell/src/pages/SampleDataPage.tsx +37 -0
- package/templates/admin-shell/src/router.tsx +13 -4
- package/templates/admin-shell/src/types/router.ts +4 -0
- package/templates/dashboard/package.json +1 -0
- package/templates/dashboard/src/components/dashboard/ChartCard.tsx +29 -0
- package/templates/dashboard/src/components/dashboard/DashboardOverview.tsx +79 -0
- package/templates/dashboard/src/components/dashboard/KpiCard.tsx +31 -0
- package/templates/dashboard/src/components/dashboard/RegionsBarChart.tsx +45 -0
- package/templates/dashboard/src/components/dashboard/SeriesBarChart.tsx +55 -0
- package/templates/dashboard/src/components/dashboard/TrendLineChart.tsx +55 -0
- package/templates/dashboard/src/components/dashboard/chartTheme.ts +18 -0
- package/templates/dashboard/src/components/dashboard/dashboardMockData.ts +51 -0
- package/templates/dashboard/src/components/dashboard/index.ts +1 -0
- package/templates/dashboard/src/components/layout/AppShell.tsx +55 -0
- package/templates/dashboard/src/components/layout/LanguageSwitcher.tsx +37 -0
- package/templates/dashboard/src/components/layout/NotificationsMenu.tsx +79 -0
- package/templates/dashboard/src/components/layout/PageHeading.tsx +40 -0
- package/templates/dashboard/src/components/layout/Sidebar.tsx +65 -0
- package/templates/dashboard/src/components/layout/ThemeMenu.tsx +90 -0
- package/templates/dashboard/src/components/layout/TopHeader.tsx +78 -0
- package/templates/dashboard/src/i18n.ts +16 -2
- package/templates/dashboard/src/locales/en.json +66 -1
- package/templates/dashboard/src/locales/fr.json +80 -0
- package/templates/dashboard/src/main.tsx +8 -2
- package/templates/dashboard/src/pages/HomePage.tsx +17 -2
- package/templates/dashboard/src/pages/SampleDataPage.tsx +37 -0
- package/templates/dashboard/src/router.tsx +13 -4
- package/templates/dashboard/src/types/router.ts +4 -0
- package/templates/ecommerce/src/components/layout/AppShell.tsx +55 -0
- package/templates/ecommerce/src/components/layout/LanguageSwitcher.tsx +37 -0
- package/templates/ecommerce/src/components/layout/NotificationsMenu.tsx +79 -0
- package/templates/ecommerce/src/components/layout/PageHeading.tsx +40 -0
- package/templates/ecommerce/src/components/layout/Sidebar.tsx +65 -0
- package/templates/ecommerce/src/components/layout/ThemeMenu.tsx +90 -0
- package/templates/ecommerce/src/components/layout/TopHeader.tsx +78 -0
- package/templates/ecommerce/src/i18n.ts +16 -2
- package/templates/ecommerce/src/locales/en.json +43 -1
- package/templates/ecommerce/src/locales/fr.json +62 -0
- package/templates/ecommerce/src/main.tsx +8 -2
- package/templates/ecommerce/src/pages/HomePage.tsx +1 -2
- package/templates/ecommerce/src/pages/SampleDataPage.tsx +37 -0
- package/templates/ecommerce/src/router.tsx +13 -4
- package/templates/ecommerce/src/types/router.ts +4 -0
- package/templates/website/src/components/layout/AppShell.tsx +55 -0
- package/templates/website/src/components/layout/LanguageSwitcher.tsx +37 -0
- package/templates/website/src/components/layout/NotificationsMenu.tsx +79 -0
- package/templates/website/src/components/layout/PageHeading.tsx +40 -0
- package/templates/website/src/components/layout/Sidebar.tsx +65 -0
- package/templates/website/src/components/layout/ThemeMenu.tsx +90 -0
- package/templates/website/src/components/layout/TopHeader.tsx +78 -0
- package/templates/website/src/i18n.ts +16 -2
- package/templates/website/src/locales/en.json +43 -1
- package/templates/website/src/locales/fr.json +63 -0
- package/templates/website/src/main.tsx +8 -2
- package/templates/website/src/pages/HomePage.tsx +1 -4
- package/templates/website/src/pages/SampleDataPage.tsx +37 -0
- package/templates/website/src/router.tsx +13 -4
- package/templates/website/src/types/router.ts +4 -0
- package/templates/admin-shell/src/components/AppShell.tsx +0 -68
- package/templates/admin-shell/src/pages/AgenciesPage.tsx +0 -83
- package/templates/dashboard/src/components/AppShell.tsx +0 -44
- package/templates/dashboard/src/pages/AgenciesPage.tsx +0 -83
- package/templates/ecommerce/src/components/AppShell.tsx +0 -44
- package/templates/ecommerce/src/pages/AgenciesPage.tsx +0 -83
- package/templates/website/src/components/AppShell.tsx +0 -44
- package/templates/website/src/pages/AgenciesPage.tsx +0 -83
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
import { ThemeToolbar } from '@quadrokit/ui'
|
|
2
|
-
import { useTranslation } from 'react-i18next'
|
|
3
|
-
import { Link, NavLink, Outlet } from 'react-router-dom'
|
|
4
|
-
|
|
5
|
-
export function AppShell() {
|
|
6
|
-
const { t } = useTranslation()
|
|
7
|
-
|
|
8
|
-
return (
|
|
9
|
-
<div className="min-h-dvh bg-background text-foreground">
|
|
10
|
-
<div className="flex min-h-dvh">
|
|
11
|
-
<aside className="hidden w-56 shrink-0 border-r border-border bg-card/40 p-4 md:block">
|
|
12
|
-
<Link to="/" className="block text-lg font-semibold tracking-tight">
|
|
13
|
-
{t('app.title')}
|
|
14
|
-
</Link>
|
|
15
|
-
<nav className="mt-6 flex flex-col gap-2 text-sm text-muted-foreground">
|
|
16
|
-
<NavLink
|
|
17
|
-
to="/"
|
|
18
|
-
end
|
|
19
|
-
className={({ isActive }) =>
|
|
20
|
-
isActive ? 'font-medium text-foreground' : 'hover:text-foreground'
|
|
21
|
-
}
|
|
22
|
-
>
|
|
23
|
-
{t('app.nav_home')}
|
|
24
|
-
</NavLink>
|
|
25
|
-
<NavLink
|
|
26
|
-
to="/agencies"
|
|
27
|
-
className={({ isActive }) =>
|
|
28
|
-
isActive ? 'font-medium text-foreground' : 'hover:text-foreground'
|
|
29
|
-
}
|
|
30
|
-
>
|
|
31
|
-
{t('app.nav_agencies')}
|
|
32
|
-
</NavLink>
|
|
33
|
-
</nav>
|
|
34
|
-
</aside>
|
|
35
|
-
<div className="flex min-w-0 flex-1 flex-col">
|
|
36
|
-
<header className="border-b border-border bg-card/40 px-4 py-3 backdrop-blur md:hidden">
|
|
37
|
-
<div className="flex flex-wrap items-center justify-between gap-2">
|
|
38
|
-
<Link to="/" className="font-semibold">
|
|
39
|
-
{t('app.title')}
|
|
40
|
-
</Link>
|
|
41
|
-
<nav className="flex gap-3 text-sm text-muted-foreground">
|
|
42
|
-
<NavLink
|
|
43
|
-
to="/"
|
|
44
|
-
end
|
|
45
|
-
className={({ isActive }) => (isActive ? 'text-foreground' : '')}
|
|
46
|
-
>
|
|
47
|
-
{t('app.nav_home')}
|
|
48
|
-
</NavLink>
|
|
49
|
-
<NavLink
|
|
50
|
-
to="/agencies"
|
|
51
|
-
className={({ isActive }) => (isActive ? 'text-foreground' : '')}
|
|
52
|
-
>
|
|
53
|
-
{t('app.nav_agencies')}
|
|
54
|
-
</NavLink>
|
|
55
|
-
</nav>
|
|
56
|
-
</div>
|
|
57
|
-
</header>
|
|
58
|
-
<div className="border-b border-border px-4 py-3">
|
|
59
|
-
<ThemeToolbar />
|
|
60
|
-
</div>
|
|
61
|
-
<main className="mx-auto w-full max-w-5xl flex-1 px-4 py-8">
|
|
62
|
-
<Outlet />
|
|
63
|
-
</main>
|
|
64
|
-
</div>
|
|
65
|
-
</div>
|
|
66
|
-
</div>
|
|
67
|
-
)
|
|
68
|
-
}
|
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@quadrokit/ui'
|
|
2
|
-
import { useEffect, useRef, useState } from 'react'
|
|
3
|
-
import { quadro } from '@/lib/quadro-client'
|
|
4
|
-
|
|
5
|
-
const PAGE_SIZE = 20
|
|
6
|
-
|
|
7
|
-
export function AgenciesPage() {
|
|
8
|
-
const [names, setNames] = useState<string[]>([])
|
|
9
|
-
const [error, setError] = useState<string | null>(null)
|
|
10
|
-
const loadSeq = useRef(0)
|
|
11
|
-
|
|
12
|
-
useEffect(() => {
|
|
13
|
-
const seq = ++loadSeq.current
|
|
14
|
-
const ac = new AbortController()
|
|
15
|
-
let cancelled = false
|
|
16
|
-
;(async () => {
|
|
17
|
-
try {
|
|
18
|
-
const list = quadro.Agency.all({
|
|
19
|
-
page: 1,
|
|
20
|
-
pageSize: PAGE_SIZE,
|
|
21
|
-
select: ['name'],
|
|
22
|
-
maxItems: PAGE_SIZE,
|
|
23
|
-
signal: ac.signal,
|
|
24
|
-
})
|
|
25
|
-
const acc: string[] = []
|
|
26
|
-
for await (const a of list) {
|
|
27
|
-
if (cancelled || seq !== loadSeq.current) {
|
|
28
|
-
break
|
|
29
|
-
}
|
|
30
|
-
acc.push(a.name ?? '')
|
|
31
|
-
}
|
|
32
|
-
if (!cancelled && seq === loadSeq.current) {
|
|
33
|
-
setNames(acc)
|
|
34
|
-
}
|
|
35
|
-
} catch (e) {
|
|
36
|
-
if (e instanceof Error && e.name === 'AbortError') {
|
|
37
|
-
return
|
|
38
|
-
}
|
|
39
|
-
if (!cancelled && seq === loadSeq.current) {
|
|
40
|
-
setError(e instanceof Error ? e.message : String(e))
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
})()
|
|
44
|
-
return () => {
|
|
45
|
-
cancelled = true
|
|
46
|
-
ac.abort()
|
|
47
|
-
}
|
|
48
|
-
}, [])
|
|
49
|
-
|
|
50
|
-
return (
|
|
51
|
-
<div className="space-y-6">
|
|
52
|
-
<div>
|
|
53
|
-
<h1 className="text-2xl font-semibold">Agencies</h1>
|
|
54
|
-
<p className="text-sm text-muted-foreground">
|
|
55
|
-
Sample list from 4D REST (requires running server + auth cookie).
|
|
56
|
-
</p>
|
|
57
|
-
</div>
|
|
58
|
-
{error && (
|
|
59
|
-
<Card className="border-destructive/50">
|
|
60
|
-
<CardHeader>
|
|
61
|
-
<CardTitle className="text-destructive">Request failed</CardTitle>
|
|
62
|
-
<CardDescription>{error}</CardDescription>
|
|
63
|
-
</CardHeader>
|
|
64
|
-
</Card>
|
|
65
|
-
)}
|
|
66
|
-
<Card>
|
|
67
|
-
<CardHeader>
|
|
68
|
-
<CardTitle>Names</CardTitle>
|
|
69
|
-
<CardDescription>
|
|
70
|
-
{names.length ? `${names.length} loaded` : 'No data yet'}
|
|
71
|
-
</CardDescription>
|
|
72
|
-
</CardHeader>
|
|
73
|
-
<CardContent>
|
|
74
|
-
<ul className="list-inside list-disc space-y-1 text-sm">
|
|
75
|
-
{names.map((n) => (
|
|
76
|
-
<li key={n}>{n || '(empty)'}</li>
|
|
77
|
-
))}
|
|
78
|
-
</ul>
|
|
79
|
-
</CardContent>
|
|
80
|
-
</Card>
|
|
81
|
-
</div>
|
|
82
|
-
)
|
|
83
|
-
}
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import { ThemeToolbar } from '@quadrokit/ui'
|
|
2
|
-
import { useTranslation } from 'react-i18next'
|
|
3
|
-
import { Link, NavLink, Outlet } from 'react-router-dom'
|
|
4
|
-
|
|
5
|
-
export function AppShell() {
|
|
6
|
-
const { t } = useTranslation()
|
|
7
|
-
|
|
8
|
-
return (
|
|
9
|
-
<div className="min-h-dvh bg-background text-foreground">
|
|
10
|
-
<header className="border-b border-border bg-card/40 backdrop-blur">
|
|
11
|
-
<div className="mx-auto flex max-w-5xl flex-col gap-3 px-4 py-4 sm:flex-row sm:items-center sm:justify-between">
|
|
12
|
-
<div className="flex items-center gap-6">
|
|
13
|
-
<Link to="/" className="text-lg font-semibold tracking-tight">
|
|
14
|
-
{t('app.title')}
|
|
15
|
-
</Link>
|
|
16
|
-
<nav className="flex gap-3 text-sm text-muted-foreground">
|
|
17
|
-
<NavLink
|
|
18
|
-
to="/"
|
|
19
|
-
end
|
|
20
|
-
className={({ isActive }) =>
|
|
21
|
-
isActive ? 'font-medium text-foreground' : 'hover:text-foreground'
|
|
22
|
-
}
|
|
23
|
-
>
|
|
24
|
-
{t('app.nav_home')}
|
|
25
|
-
</NavLink>
|
|
26
|
-
<NavLink
|
|
27
|
-
to="/agencies"
|
|
28
|
-
className={({ isActive }) =>
|
|
29
|
-
isActive ? 'font-medium text-foreground' : 'hover:text-foreground'
|
|
30
|
-
}
|
|
31
|
-
>
|
|
32
|
-
{t('app.nav_agencies')}
|
|
33
|
-
</NavLink>
|
|
34
|
-
</nav>
|
|
35
|
-
</div>
|
|
36
|
-
<ThemeToolbar />
|
|
37
|
-
</div>
|
|
38
|
-
</header>
|
|
39
|
-
<main className="mx-auto max-w-5xl px-4 py-8">
|
|
40
|
-
<Outlet />
|
|
41
|
-
</main>
|
|
42
|
-
</div>
|
|
43
|
-
)
|
|
44
|
-
}
|
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@quadrokit/ui'
|
|
2
|
-
import { useEffect, useRef, useState } from 'react'
|
|
3
|
-
import { quadro } from '@/lib/quadro-client'
|
|
4
|
-
|
|
5
|
-
const PAGE_SIZE = 20
|
|
6
|
-
|
|
7
|
-
export function AgenciesPage() {
|
|
8
|
-
const [names, setNames] = useState<string[]>([])
|
|
9
|
-
const [error, setError] = useState<string | null>(null)
|
|
10
|
-
const loadSeq = useRef(0)
|
|
11
|
-
|
|
12
|
-
useEffect(() => {
|
|
13
|
-
const seq = ++loadSeq.current
|
|
14
|
-
const ac = new AbortController()
|
|
15
|
-
let cancelled = false
|
|
16
|
-
;(async () => {
|
|
17
|
-
try {
|
|
18
|
-
const list = quadro.Agency.all({
|
|
19
|
-
page: 1,
|
|
20
|
-
pageSize: PAGE_SIZE,
|
|
21
|
-
select: ['name'],
|
|
22
|
-
maxItems: PAGE_SIZE,
|
|
23
|
-
signal: ac.signal,
|
|
24
|
-
})
|
|
25
|
-
const acc: string[] = []
|
|
26
|
-
for await (const a of list) {
|
|
27
|
-
if (cancelled || seq !== loadSeq.current) {
|
|
28
|
-
break
|
|
29
|
-
}
|
|
30
|
-
acc.push(a.name ?? '')
|
|
31
|
-
}
|
|
32
|
-
if (!cancelled && seq === loadSeq.current) {
|
|
33
|
-
setNames(acc)
|
|
34
|
-
}
|
|
35
|
-
} catch (e) {
|
|
36
|
-
if (e instanceof Error && e.name === 'AbortError') {
|
|
37
|
-
return
|
|
38
|
-
}
|
|
39
|
-
if (!cancelled && seq === loadSeq.current) {
|
|
40
|
-
setError(e instanceof Error ? e.message : String(e))
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
})()
|
|
44
|
-
return () => {
|
|
45
|
-
cancelled = true
|
|
46
|
-
ac.abort()
|
|
47
|
-
}
|
|
48
|
-
}, [])
|
|
49
|
-
|
|
50
|
-
return (
|
|
51
|
-
<div className="space-y-6">
|
|
52
|
-
<div>
|
|
53
|
-
<h1 className="text-2xl font-semibold">Agencies</h1>
|
|
54
|
-
<p className="text-sm text-muted-foreground">
|
|
55
|
-
Sample list from 4D REST (requires running server + auth cookie).
|
|
56
|
-
</p>
|
|
57
|
-
</div>
|
|
58
|
-
{error && (
|
|
59
|
-
<Card className="border-destructive/50">
|
|
60
|
-
<CardHeader>
|
|
61
|
-
<CardTitle className="text-destructive">Request failed</CardTitle>
|
|
62
|
-
<CardDescription>{error}</CardDescription>
|
|
63
|
-
</CardHeader>
|
|
64
|
-
</Card>
|
|
65
|
-
)}
|
|
66
|
-
<Card>
|
|
67
|
-
<CardHeader>
|
|
68
|
-
<CardTitle>Names</CardTitle>
|
|
69
|
-
<CardDescription>
|
|
70
|
-
{names.length ? `${names.length} loaded` : 'No data yet'}
|
|
71
|
-
</CardDescription>
|
|
72
|
-
</CardHeader>
|
|
73
|
-
<CardContent>
|
|
74
|
-
<ul className="list-inside list-disc space-y-1 text-sm">
|
|
75
|
-
{names.map((n) => (
|
|
76
|
-
<li key={n}>{n || '(empty)'}</li>
|
|
77
|
-
))}
|
|
78
|
-
</ul>
|
|
79
|
-
</CardContent>
|
|
80
|
-
</Card>
|
|
81
|
-
</div>
|
|
82
|
-
)
|
|
83
|
-
}
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import { ThemeToolbar } from '@quadrokit/ui'
|
|
2
|
-
import { useTranslation } from 'react-i18next'
|
|
3
|
-
import { Link, NavLink, Outlet } from 'react-router-dom'
|
|
4
|
-
|
|
5
|
-
export function AppShell() {
|
|
6
|
-
const { t } = useTranslation()
|
|
7
|
-
|
|
8
|
-
return (
|
|
9
|
-
<div className="min-h-dvh bg-background text-foreground">
|
|
10
|
-
<header className="border-b border-border bg-card/40 backdrop-blur">
|
|
11
|
-
<div className="mx-auto flex max-w-5xl flex-col gap-3 px-4 py-4 sm:flex-row sm:items-center sm:justify-between">
|
|
12
|
-
<div className="flex items-center gap-6">
|
|
13
|
-
<Link to="/" className="text-lg font-semibold tracking-tight">
|
|
14
|
-
{t('app.title')}
|
|
15
|
-
</Link>
|
|
16
|
-
<nav className="flex gap-3 text-sm text-muted-foreground">
|
|
17
|
-
<NavLink
|
|
18
|
-
to="/"
|
|
19
|
-
end
|
|
20
|
-
className={({ isActive }) =>
|
|
21
|
-
isActive ? 'font-medium text-foreground' : 'hover:text-foreground'
|
|
22
|
-
}
|
|
23
|
-
>
|
|
24
|
-
{t('app.nav_home')}
|
|
25
|
-
</NavLink>
|
|
26
|
-
<NavLink
|
|
27
|
-
to="/agencies"
|
|
28
|
-
className={({ isActive }) =>
|
|
29
|
-
isActive ? 'font-medium text-foreground' : 'hover:text-foreground'
|
|
30
|
-
}
|
|
31
|
-
>
|
|
32
|
-
{t('app.nav_agencies')}
|
|
33
|
-
</NavLink>
|
|
34
|
-
</nav>
|
|
35
|
-
</div>
|
|
36
|
-
<ThemeToolbar />
|
|
37
|
-
</div>
|
|
38
|
-
</header>
|
|
39
|
-
<main className="mx-auto max-w-5xl px-4 py-8">
|
|
40
|
-
<Outlet />
|
|
41
|
-
</main>
|
|
42
|
-
</div>
|
|
43
|
-
)
|
|
44
|
-
}
|
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@quadrokit/ui'
|
|
2
|
-
import { useEffect, useRef, useState } from 'react'
|
|
3
|
-
import { quadro } from '@/lib/quadro-client'
|
|
4
|
-
|
|
5
|
-
const PAGE_SIZE = 20
|
|
6
|
-
|
|
7
|
-
export function AgenciesPage() {
|
|
8
|
-
const [names, setNames] = useState<string[]>([])
|
|
9
|
-
const [error, setError] = useState<string | null>(null)
|
|
10
|
-
const loadSeq = useRef(0)
|
|
11
|
-
|
|
12
|
-
useEffect(() => {
|
|
13
|
-
const seq = ++loadSeq.current
|
|
14
|
-
const ac = new AbortController()
|
|
15
|
-
let cancelled = false
|
|
16
|
-
;(async () => {
|
|
17
|
-
try {
|
|
18
|
-
const list = quadro.Agency.all({
|
|
19
|
-
page: 1,
|
|
20
|
-
pageSize: PAGE_SIZE,
|
|
21
|
-
select: ['name'],
|
|
22
|
-
maxItems: PAGE_SIZE,
|
|
23
|
-
signal: ac.signal,
|
|
24
|
-
})
|
|
25
|
-
const acc: string[] = []
|
|
26
|
-
for await (const a of list) {
|
|
27
|
-
if (cancelled || seq !== loadSeq.current) {
|
|
28
|
-
break
|
|
29
|
-
}
|
|
30
|
-
acc.push(a.name ?? '')
|
|
31
|
-
}
|
|
32
|
-
if (!cancelled && seq === loadSeq.current) {
|
|
33
|
-
setNames(acc)
|
|
34
|
-
}
|
|
35
|
-
} catch (e) {
|
|
36
|
-
if (e instanceof Error && e.name === 'AbortError') {
|
|
37
|
-
return
|
|
38
|
-
}
|
|
39
|
-
if (!cancelled && seq === loadSeq.current) {
|
|
40
|
-
setError(e instanceof Error ? e.message : String(e))
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
})()
|
|
44
|
-
return () => {
|
|
45
|
-
cancelled = true
|
|
46
|
-
ac.abort()
|
|
47
|
-
}
|
|
48
|
-
}, [])
|
|
49
|
-
|
|
50
|
-
return (
|
|
51
|
-
<div className="space-y-6">
|
|
52
|
-
<div>
|
|
53
|
-
<h1 className="text-2xl font-semibold">Agencies</h1>
|
|
54
|
-
<p className="text-sm text-muted-foreground">
|
|
55
|
-
Sample list from 4D REST (requires running server + auth cookie).
|
|
56
|
-
</p>
|
|
57
|
-
</div>
|
|
58
|
-
{error && (
|
|
59
|
-
<Card className="border-destructive/50">
|
|
60
|
-
<CardHeader>
|
|
61
|
-
<CardTitle className="text-destructive">Request failed</CardTitle>
|
|
62
|
-
<CardDescription>{error}</CardDescription>
|
|
63
|
-
</CardHeader>
|
|
64
|
-
</Card>
|
|
65
|
-
)}
|
|
66
|
-
<Card>
|
|
67
|
-
<CardHeader>
|
|
68
|
-
<CardTitle>Names</CardTitle>
|
|
69
|
-
<CardDescription>
|
|
70
|
-
{names.length ? `${names.length} loaded` : 'No data yet'}
|
|
71
|
-
</CardDescription>
|
|
72
|
-
</CardHeader>
|
|
73
|
-
<CardContent>
|
|
74
|
-
<ul className="list-inside list-disc space-y-1 text-sm">
|
|
75
|
-
{names.map((n) => (
|
|
76
|
-
<li key={n}>{n || '(empty)'}</li>
|
|
77
|
-
))}
|
|
78
|
-
</ul>
|
|
79
|
-
</CardContent>
|
|
80
|
-
</Card>
|
|
81
|
-
</div>
|
|
82
|
-
)
|
|
83
|
-
}
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import { ThemeToolbar } from '@quadrokit/ui'
|
|
2
|
-
import { useTranslation } from 'react-i18next'
|
|
3
|
-
import { Link, NavLink, Outlet } from 'react-router-dom'
|
|
4
|
-
|
|
5
|
-
export function AppShell() {
|
|
6
|
-
const { t } = useTranslation()
|
|
7
|
-
|
|
8
|
-
return (
|
|
9
|
-
<div className="min-h-dvh bg-background text-foreground">
|
|
10
|
-
<header className="border-b border-border bg-card/40 backdrop-blur">
|
|
11
|
-
<div className="mx-auto flex max-w-5xl flex-col gap-3 px-4 py-4 sm:flex-row sm:items-center sm:justify-between">
|
|
12
|
-
<div className="flex items-center gap-6">
|
|
13
|
-
<Link to="/" className="text-lg font-semibold tracking-tight">
|
|
14
|
-
{t('app.title')}
|
|
15
|
-
</Link>
|
|
16
|
-
<nav className="flex gap-3 text-sm text-muted-foreground">
|
|
17
|
-
<NavLink
|
|
18
|
-
to="/"
|
|
19
|
-
end
|
|
20
|
-
className={({ isActive }) =>
|
|
21
|
-
isActive ? 'font-medium text-foreground' : 'hover:text-foreground'
|
|
22
|
-
}
|
|
23
|
-
>
|
|
24
|
-
{t('app.nav_home')}
|
|
25
|
-
</NavLink>
|
|
26
|
-
<NavLink
|
|
27
|
-
to="/agencies"
|
|
28
|
-
className={({ isActive }) =>
|
|
29
|
-
isActive ? 'font-medium text-foreground' : 'hover:text-foreground'
|
|
30
|
-
}
|
|
31
|
-
>
|
|
32
|
-
{t('app.nav_agencies')}
|
|
33
|
-
</NavLink>
|
|
34
|
-
</nav>
|
|
35
|
-
</div>
|
|
36
|
-
<ThemeToolbar />
|
|
37
|
-
</div>
|
|
38
|
-
</header>
|
|
39
|
-
<main className="mx-auto max-w-5xl px-4 py-8">
|
|
40
|
-
<Outlet />
|
|
41
|
-
</main>
|
|
42
|
-
</div>
|
|
43
|
-
)
|
|
44
|
-
}
|
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@quadrokit/ui'
|
|
2
|
-
import { useEffect, useRef, useState } from 'react'
|
|
3
|
-
import { quadro } from '@/lib/quadro-client'
|
|
4
|
-
|
|
5
|
-
const PAGE_SIZE = 20
|
|
6
|
-
|
|
7
|
-
export function AgenciesPage() {
|
|
8
|
-
const [names, setNames] = useState<string[]>([])
|
|
9
|
-
const [error, setError] = useState<string | null>(null)
|
|
10
|
-
const loadSeq = useRef(0)
|
|
11
|
-
|
|
12
|
-
useEffect(() => {
|
|
13
|
-
const seq = ++loadSeq.current
|
|
14
|
-
const ac = new AbortController()
|
|
15
|
-
let cancelled = false
|
|
16
|
-
;(async () => {
|
|
17
|
-
try {
|
|
18
|
-
const list = quadro.Agency.all({
|
|
19
|
-
page: 1,
|
|
20
|
-
pageSize: PAGE_SIZE,
|
|
21
|
-
select: ['name'],
|
|
22
|
-
maxItems: PAGE_SIZE,
|
|
23
|
-
signal: ac.signal,
|
|
24
|
-
})
|
|
25
|
-
const acc: string[] = []
|
|
26
|
-
for await (const a of list) {
|
|
27
|
-
if (cancelled || seq !== loadSeq.current) {
|
|
28
|
-
break
|
|
29
|
-
}
|
|
30
|
-
acc.push(a.name ?? '')
|
|
31
|
-
}
|
|
32
|
-
if (!cancelled && seq === loadSeq.current) {
|
|
33
|
-
setNames(acc)
|
|
34
|
-
}
|
|
35
|
-
} catch (e) {
|
|
36
|
-
if (e instanceof Error && e.name === 'AbortError') {
|
|
37
|
-
return
|
|
38
|
-
}
|
|
39
|
-
if (!cancelled && seq === loadSeq.current) {
|
|
40
|
-
setError(e instanceof Error ? e.message : String(e))
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
})()
|
|
44
|
-
return () => {
|
|
45
|
-
cancelled = true
|
|
46
|
-
ac.abort()
|
|
47
|
-
}
|
|
48
|
-
}, [])
|
|
49
|
-
|
|
50
|
-
return (
|
|
51
|
-
<div className="space-y-6">
|
|
52
|
-
<div>
|
|
53
|
-
<h1 className="text-2xl font-semibold">Agencies</h1>
|
|
54
|
-
<p className="text-sm text-muted-foreground">
|
|
55
|
-
Sample list from 4D REST (requires running server + auth cookie).
|
|
56
|
-
</p>
|
|
57
|
-
</div>
|
|
58
|
-
{error && (
|
|
59
|
-
<Card className="border-destructive/50">
|
|
60
|
-
<CardHeader>
|
|
61
|
-
<CardTitle className="text-destructive">Request failed</CardTitle>
|
|
62
|
-
<CardDescription>{error}</CardDescription>
|
|
63
|
-
</CardHeader>
|
|
64
|
-
</Card>
|
|
65
|
-
)}
|
|
66
|
-
<Card>
|
|
67
|
-
<CardHeader>
|
|
68
|
-
<CardTitle>Names</CardTitle>
|
|
69
|
-
<CardDescription>
|
|
70
|
-
{names.length ? `${names.length} loaded` : 'No data yet'}
|
|
71
|
-
</CardDescription>
|
|
72
|
-
</CardHeader>
|
|
73
|
-
<CardContent>
|
|
74
|
-
<ul className="list-inside list-disc space-y-1 text-sm">
|
|
75
|
-
{names.map((n) => (
|
|
76
|
-
<li key={n}>{n || '(empty)'}</li>
|
|
77
|
-
))}
|
|
78
|
-
</ul>
|
|
79
|
-
</CardContent>
|
|
80
|
-
</Card>
|
|
81
|
-
</div>
|
|
82
|
-
)
|
|
83
|
-
}
|