|
1 | | -/** |
2 | | - * ⚠️ BEFORE MODIFYING THIS FILE: |
3 | | - * |
4 | | - * 1. Create SQL files in config/queries/ |
5 | | - * 2. Run `npm run typegen` to generate query types |
6 | | - * 3. Check appKitTypes.d.ts for available types |
7 | | - * |
8 | | - * Common Mistakes: |
9 | | - * - DataTable does NOT accept `data` or `columns` props |
10 | | - * - Charts use `xKey` and `yKey`, NOT `seriesKey`/`nameKey`/`valueKey` |
11 | | - * - useAnalyticsQuery has no `enabled` option - use conditional rendering |
12 | | - */ |
| 1 | +import { createBrowserRouter, RouterProvider, NavLink, Outlet } from 'react-router'; |
13 | 2 | import { |
14 | | - useAnalyticsQuery, |
15 | | - AreaChart, |
16 | | - LineChart, |
17 | | - RadarChart, |
18 | 3 | Card, |
19 | 4 | CardContent, |
20 | 5 | CardHeader, |
21 | 6 | CardTitle, |
22 | | - Skeleton, |
23 | | - Label, |
24 | | - Select, |
25 | | - SelectContent, |
26 | | - SelectItem, |
27 | | - SelectTrigger, |
28 | | - SelectValue, |
29 | 7 | } from '@databricks/appkit-ui/react'; |
30 | | -import { sql } from "@databricks/appkit-ui/js"; |
31 | | -import { useState, useEffect } from 'react'; |
| 8 | +{{- if .plugins.analytics}} |
| 9 | +import { AnalyticsPage } from './pages/analytics/AnalyticsPage'; |
| 10 | +{{- end}} |
| 11 | +{{- if .plugins.lakebase}} |
| 12 | +import { LakebasePage } from './pages/lakebase/LakebasePage'; |
| 13 | +{{- end}} |
32 | 14 |
|
33 | | -function App() { |
34 | | - const { data, loading, error } = useAnalyticsQuery('hello_world', { |
35 | | - message: sql.string('hello world'), |
36 | | - }); |
| 15 | +const navLinkClass = ({ isActive }: { isActive: boolean }) => |
| 16 | + `px-3 py-1.5 rounded-md text-sm font-medium transition-colors ${ |
| 17 | + isActive |
| 18 | + ? 'bg-primary text-primary-foreground' |
| 19 | + : 'text-muted-foreground hover:bg-muted hover:text-foreground' |
| 20 | + }`; |
37 | 21 |
|
38 | | - const [health, setHealth] = useState<{ |
39 | | - status: string; |
40 | | - timestamp: string; |
41 | | - } | null>(null); |
42 | | - const [healthError, setHealthError] = useState<string | null>(null); |
| 22 | +function Layout() { |
| 23 | + return ( |
| 24 | + <div className="min-h-screen bg-background flex flex-col"> |
| 25 | + <header className="border-b px-6 py-3 flex items-center gap-4"> |
| 26 | + <h1 className="text-lg font-semibold text-foreground">{{.projectName}}</h1> |
| 27 | + <nav className="flex gap-1"> |
| 28 | + <NavLink to="/" end className={navLinkClass}> |
| 29 | + Home |
| 30 | + </NavLink> |
| 31 | +{{- if .plugins.analytics}} |
| 32 | + <NavLink to="/analytics" className={navLinkClass}> |
| 33 | + Analytics |
| 34 | + </NavLink> |
| 35 | +{{- end}} |
| 36 | +{{- if .plugins.lakebase}} |
| 37 | + <NavLink to="/lakebase" className={navLinkClass}> |
| 38 | + Lakebase |
| 39 | + </NavLink> |
| 40 | +{{- end}} |
| 41 | + </nav> |
| 42 | + </header> |
43 | 43 |
|
44 | | - useEffect(() => { |
45 | | - fetch('/health') |
46 | | - .then((response) => response.json()) |
47 | | - .then((data: { status: string }) => setHealth({ ...data, timestamp: new Date().toISOString() })) |
48 | | - .catch((error: { message: string }) => setHealthError(error.message)); |
49 | | - }, []); |
| 44 | + <main className="flex-1 p-6"> |
| 45 | + <Outlet /> |
| 46 | + </main> |
| 47 | + </div> |
| 48 | + ); |
| 49 | +} |
50 | 50 |
|
51 | | - const [maxMonthNum, setMaxMonthNum] = useState<number>(12); |
| 51 | +const router = createBrowserRouter([ |
| 52 | + { |
| 53 | + element: <Layout />, |
| 54 | + children: [ |
| 55 | + { path: '/', element: <HomePage /> }, |
| 56 | +{{- if .plugins.analytics}} |
| 57 | + { path: '/analytics', element: <AnalyticsPage /> }, |
| 58 | +{{- end}} |
| 59 | +{{- if .plugins.lakebase}} |
| 60 | + { path: '/lakebase', element: <LakebasePage /> }, |
| 61 | +{{- end}} |
| 62 | + ], |
| 63 | + }, |
| 64 | +]); |
52 | 65 |
|
53 | | - const salesParameters = { max_month_num: sql.number(maxMonthNum) }; |
| 66 | +export default function App() { |
| 67 | + return <RouterProvider router={router} />; |
| 68 | +} |
54 | 69 |
|
| 70 | +function HomePage() { |
55 | 71 | return ( |
56 | | - <div className="min-h-screen bg-background flex flex-col items-center justify-center p-4 w-full"> |
57 | | - <div className="mb-8 text-center"> |
58 | | - <h1 className="text-4xl font-bold mb-2 text-foreground">Minimal Databricks App</h1> |
59 | | - <p className="text-lg text-muted-foreground max-w-md">A minimal Databricks App powered by Databricks AppKit</p> |
| 72 | + <div className="max-w-2xl mx-auto space-y-6 mt-8"> |
| 73 | + <div className="text-center"> |
| 74 | + <h2 className="text-3xl font-bold mb-2 text-foreground"> |
| 75 | + Welcome to your Databricks App |
| 76 | + </h2> |
| 77 | + <p className="text-lg text-muted-foreground"> |
| 78 | + Powered by Databricks AppKit |
| 79 | + </p> |
60 | 80 | </div> |
61 | 81 |
|
62 | | - <div className="grid grid-cols-1 md:grid-cols-3 gap-6 w-full max-w-7xl"> |
63 | | - <Card className="shadow-lg"> |
64 | | - <CardHeader> |
65 | | - <CardTitle>SQL Query Result</CardTitle> |
66 | | - </CardHeader> |
67 | | - <CardContent> |
68 | | - {loading && ( |
69 | | - <div className="space-y-2"> |
70 | | - <Skeleton className="h-4 w-3/4" /> |
71 | | - <Skeleton className="h-8 w-1/2" /> |
72 | | - </div> |
73 | | - )} |
74 | | - {error && <div className="text-destructive bg-destructive/10 p-3 rounded-md">Error: {error}</div>} |
75 | | - {data && data.length > 0 && ( |
76 | | - <div className="space-y-2"> |
77 | | - <div className="text-sm text-muted-foreground">Query: SELECT :message AS value</div> |
78 | | - <div className="text-2xl font-bold text-primary">{data[0].value}</div> |
79 | | - </div> |
80 | | - )} |
81 | | - {data && data.length === 0 && <div className="text-muted-foreground">No results</div>} |
82 | | - </CardContent> |
83 | | - </Card> |
84 | | - |
85 | | - <Card className="shadow-lg md:col-span-2"> |
86 | | - <CardHeader> |
87 | | - <CardTitle>Health Check</CardTitle> |
88 | | - </CardHeader> |
89 | | - <CardContent> |
90 | | - {!health && !healthError && ( |
91 | | - <div className="space-y-2"> |
92 | | - <Skeleton className="h-6 w-24" /> |
93 | | - <Skeleton className="h-4 w-48" /> |
94 | | - </div> |
95 | | - )} |
96 | | - {healthError && ( |
97 | | - <div className="text-destructive bg-destructive/10 p-3 rounded-md">Error: {healthError}</div> |
98 | | - )} |
99 | | - {health && ( |
100 | | - <div className="space-y-2"> |
101 | | - <div className="flex items-center gap-2"> |
102 | | - <div className="w-2 h-2 rounded-full bg-success animate-pulse"></div> |
103 | | - <div className="text-lg font-semibold text-success">{health.status.toUpperCase()}</div> |
104 | | - </div> |
105 | | - <div className="text-sm text-muted-foreground"> |
106 | | - Last checked: {new Date(health.timestamp).toLocaleString()} |
107 | | - </div> |
108 | | - </div> |
109 | | - )} |
110 | | - </CardContent> |
111 | | - </Card> |
112 | | - |
113 | | - <Card className="shadow-lg md:col-span-3"> |
114 | | - <CardHeader> |
115 | | - <CardTitle>Sales Data Filter</CardTitle> |
116 | | - </CardHeader> |
117 | | - <CardContent> |
118 | | - <div className="max-w-md"> |
119 | | - <div className="space-y-2"> |
120 | | - <Label htmlFor="max-month">Show data up to month</Label> |
121 | | - <Select value={maxMonthNum.toString()} onValueChange={(value) => setMaxMonthNum(parseInt(value))}> |
122 | | - <SelectTrigger id="max-month"> |
123 | | - <SelectValue placeholder="All months" /> |
124 | | - </SelectTrigger> |
125 | | - <SelectContent> |
126 | | - {Array.from({ length: 12 }, (_, i) => ( |
127 | | - <SelectItem key={`month-${i + 1}`} value={(i + 1).toString()}> |
128 | | - {i + 1 === 12 ? 'All months (12)' : `Month ${i + 1}`} |
129 | | - </SelectItem> |
130 | | - ))} |
131 | | - </SelectContent> |
132 | | - </Select> |
133 | | - </div> |
134 | | - </div> |
135 | | - </CardContent> |
136 | | - </Card> |
137 | | - |
138 | | - <Card className="shadow-lg flex min-w-0"> |
139 | | - <CardHeader> |
140 | | - <CardTitle>Sales Trend Area Chart</CardTitle> |
141 | | - </CardHeader> |
142 | | - <CardContent> |
143 | | - <AreaChart queryKey="mocked_sales" parameters={salesParameters} /> |
144 | | - </CardContent> |
145 | | - </Card> |
146 | | - <Card className="shadow-lg flex min-w-0"> |
147 | | - <CardHeader> |
148 | | - <CardTitle>Sales Trend Custom Line Chart</CardTitle> |
149 | | - </CardHeader> |
150 | | - <CardContent> |
151 | | - <LineChart queryKey="mocked_sales" parameters={salesParameters} /> |
152 | | - </CardContent> |
153 | | - </Card> |
154 | | - <Card className="shadow-lg flex min-w-0"> |
155 | | - <CardHeader> |
156 | | - <CardTitle>Sales Trend Radar Chart</CardTitle> |
157 | | - </CardHeader> |
158 | | - <CardContent> |
159 | | - <RadarChart queryKey="mocked_sales" parameters={salesParameters} /> |
160 | | - </CardContent> |
161 | | - </Card> |
162 | | - </div> |
| 82 | + <Card className="shadow-lg"> |
| 83 | + <CardHeader> |
| 84 | + <CardTitle>Getting Started</CardTitle> |
| 85 | + </CardHeader> |
| 86 | + <CardContent className="space-y-3"> |
| 87 | + <p className="text-sm text-muted-foreground">Your app is ready. Explore the resources below to continue building.</p> |
| 88 | + <ul className="space-y-2 text-sm"> |
| 89 | + <li> |
| 90 | + <a |
| 91 | + href="https://github.com/databricks/appkit" |
| 92 | + target="_blank" |
| 93 | + rel="noopener noreferrer" |
| 94 | + className="text-primary underline underline-offset-4 hover:text-primary/80" |
| 95 | + > |
| 96 | + AppKit on GitHub → |
| 97 | + </a> |
| 98 | + </li> |
| 99 | + <li> |
| 100 | + <a |
| 101 | + href="https://databricks.github.io/appkit/" |
| 102 | + target="_blank" |
| 103 | + rel="noopener noreferrer" |
| 104 | + className="text-primary underline underline-offset-4 hover:text-primary/80" |
| 105 | + > |
| 106 | + AppKit documentation → |
| 107 | + </a> |
| 108 | + </li> |
| 109 | + </ul> |
| 110 | + </CardContent> |
| 111 | + </Card> |
163 | 112 | </div> |
164 | 113 | ); |
165 | 114 | } |
166 | | - |
167 | | -export default App; |
0 commit comments