Quellcode durchsuchen

feat: add analytics tab in dashboard page (#220)

UncleBigBay vor 9 Monaten
Ursprung
Commit
8749a6b0eb

+ 13 - 4
pnpm-lock.yaml

@@ -170,7 +170,7 @@ importers:
         version: 19.1.7(@types/react@19.1.10)
       '@vitejs/plugin-react-swc':
         specifier: ^4.0.0
-        version: 4.0.0(vite@7.1.2(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.30.1)(tsx@4.20.3))
+        version: 4.0.0(@swc/helpers@0.5.17)(vite@7.1.2(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.30.1)(tsx@4.20.3))
       eslint:
         specifier: ^9.33.0
         version: 9.33.0(jiti@2.5.1)
@@ -1411,6 +1411,9 @@ packages:
   '@swc/counter@0.1.3':
     resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==}
 
+  '@swc/helpers@0.5.17':
+    resolution: {integrity: sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A==}
+
   '@swc/types@0.1.23':
     resolution: {integrity: sha512-u1iIVZV9Q0jxY+yM2vw/hZGDNudsN85bBpTqzAQ9rzkxW9D+e3aEM4Han+ow518gSewkXgjmEK0BD79ZcNVgPw==}
 
@@ -4149,7 +4152,7 @@ snapshots:
   '@swc/core-win32-x64-msvc@1.13.3':
     optional: true
 
-  '@swc/core@1.13.3':
+  '@swc/core@1.13.3(@swc/helpers@0.5.17)':
     dependencies:
       '@swc/counter': 0.1.3
       '@swc/types': 0.1.23
@@ -4164,9 +4167,15 @@ snapshots:
       '@swc/core-win32-arm64-msvc': 1.13.3
       '@swc/core-win32-ia32-msvc': 1.13.3
       '@swc/core-win32-x64-msvc': 1.13.3
+      '@swc/helpers': 0.5.17
 
   '@swc/counter@0.1.3': {}
 
+  '@swc/helpers@0.5.17':
+    dependencies:
+      tslib: 2.8.1
+    optional: true
+
   '@swc/types@0.1.23':
     dependencies:
       '@swc/counter': 0.1.3
@@ -4579,10 +4588,10 @@ snapshots:
       '@typescript-eslint/types': 8.39.1
       eslint-visitor-keys: 4.2.1
 
-  '@vitejs/plugin-react-swc@4.0.0(vite@7.1.2(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.30.1)(tsx@4.20.3))':
+  '@vitejs/plugin-react-swc@4.0.0(@swc/helpers@0.5.17)(vite@7.1.2(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.30.1)(tsx@4.20.3))':
     dependencies:
       '@rolldown/pluginutils': 1.0.0-beta.30
-      '@swc/core': 1.13.3
+      '@swc/core': 1.13.3(@swc/helpers@0.5.17)
       vite: 7.1.2(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.30.1)(tsx@4.20.3)
     transitivePeerDependencies:
       - '@swc/helpers'

+ 77 - 0
src/features/dashboard/components/analytics-chart.tsx

@@ -0,0 +1,77 @@
+import { Area, AreaChart, ResponsiveContainer, XAxis, YAxis } from 'recharts'
+
+const data = [
+  {
+    name: 'Mon',
+    clicks: Math.floor(Math.random() * 900) + 100,
+    uniques: Math.floor(Math.random() * 700) + 80,
+  },
+  {
+    name: 'Tue',
+    clicks: Math.floor(Math.random() * 900) + 100,
+    uniques: Math.floor(Math.random() * 700) + 80,
+  },
+  {
+    name: 'Wed',
+    clicks: Math.floor(Math.random() * 900) + 100,
+    uniques: Math.floor(Math.random() * 700) + 80,
+  },
+  {
+    name: 'Thu',
+    clicks: Math.floor(Math.random() * 900) + 100,
+    uniques: Math.floor(Math.random() * 700) + 80,
+  },
+  {
+    name: 'Fri',
+    clicks: Math.floor(Math.random() * 900) + 100,
+    uniques: Math.floor(Math.random() * 700) + 80,
+  },
+  {
+    name: 'Sat',
+    clicks: Math.floor(Math.random() * 900) + 100,
+    uniques: Math.floor(Math.random() * 700) + 80,
+  },
+  {
+    name: 'Sun',
+    clicks: Math.floor(Math.random() * 900) + 100,
+    uniques: Math.floor(Math.random() * 700) + 80,
+  },
+]
+
+export function AnalyticsChart() {
+  return (
+    <ResponsiveContainer width='100%' height={300}>
+      <AreaChart data={data}>
+        <XAxis
+          dataKey='name'
+          stroke='#888888'
+          fontSize={12}
+          tickLine={false}
+          axisLine={false}
+        />
+        <YAxis
+          stroke='#888888'
+          fontSize={12}
+          tickLine={false}
+          axisLine={false}
+        />
+        <Area
+          type='monotone'
+          dataKey='clicks'
+          stroke='currentColor'
+          className='text-primary'
+          fill='currentColor'
+          fillOpacity={0.15}
+        />
+        <Area
+          type='monotone'
+          dataKey='uniques'
+          stroke='currentColor'
+          className='text-muted-foreground'
+          fill='currentColor'
+          fillOpacity={0.1}
+        />
+      </AreaChart>
+    </ResponsiveContainer>
+  )
+}

+ 193 - 0
src/features/dashboard/components/analytics.tsx

@@ -0,0 +1,193 @@
+import {
+  Card,
+  CardContent,
+  CardDescription,
+  CardHeader,
+  CardTitle,
+} from '@/components/ui/card'
+import { AnalyticsChart } from './analytics-chart'
+
+export function Analytics() {
+  type Item = { name: string; value: number }
+
+  const SimpleBarList = ({
+    items,
+    valueFormatter,
+    barClass,
+  }: {
+    items: Item[]
+    valueFormatter: (n: number) => string
+    barClass: string
+  }) => {
+    const max = Math.max(...items.map((i) => i.value), 1)
+    return (
+      <ul className='space-y-3'>
+        {items.map((i) => {
+          const width = `${Math.round((i.value / max) * 100)}%`
+          return (
+            <li
+              key={i.name}
+              className='flex items-center justify-between gap-3'
+            >
+              <div className='min-w-0 flex-1'>
+                <div className='text-muted-foreground mb-1 truncate text-xs'>
+                  {i.name}
+                </div>
+                <div className='bg-muted h-2.5 w-full rounded-full'>
+                  <div
+                    className={`h-2.5 rounded-full ${barClass}`}
+                    style={{ width }}
+                  />
+                </div>
+              </div>
+              <div className='ps-2 text-xs font-medium tabular-nums'>
+                {valueFormatter(i.value)}
+              </div>
+            </li>
+          )
+        })}
+      </ul>
+    )
+  }
+  return (
+    <div className='space-y-4'>
+      <Card>
+        <CardHeader>
+          <CardTitle>Traffic Overview</CardTitle>
+          <CardDescription>Weekly clicks and unique visitors</CardDescription>
+        </CardHeader>
+        <CardContent className='px-6'>
+          <AnalyticsChart />
+        </CardContent>
+      </Card>
+      <div className='grid gap-4 sm:grid-cols-2 lg:grid-cols-4'>
+        <Card>
+          <CardHeader className='flex flex-row items-center justify-between space-y-0 pb-2'>
+            <CardTitle className='text-sm font-medium'>Total Clicks</CardTitle>
+            <svg
+              xmlns='http://www.w3.org/2000/svg'
+              viewBox='0 0 24 24'
+              fill='none'
+              stroke='currentColor'
+              strokeLinecap='round'
+              strokeLinejoin='round'
+              strokeWidth='2'
+              className='text-muted-foreground h-4 w-4'
+            >
+              <path d='M3 3v18h18' />
+              <path d='M7 15l4-4 4 4 4-6' />
+            </svg>
+          </CardHeader>
+          <CardContent>
+            <div className='text-2xl font-bold'>1,248</div>
+            <p className='text-muted-foreground text-xs'>+12.4% vs last week</p>
+          </CardContent>
+        </Card>
+        <Card>
+          <CardHeader className='flex flex-row items-center justify-between space-y-0 pb-2'>
+            <CardTitle className='text-sm font-medium'>
+              Unique Visitors
+            </CardTitle>
+            <svg
+              xmlns='http://www.w3.org/2000/svg'
+              viewBox='0 0 24 24'
+              fill='none'
+              stroke='currentColor'
+              strokeLinecap='round'
+              strokeLinejoin='round'
+              strokeWidth='2'
+              className='text-muted-foreground h-4 w-4'
+            >
+              <circle cx='12' cy='7' r='4' />
+              <path d='M6 21v-2a6 6 0 0 1 12 0v2' />
+            </svg>
+          </CardHeader>
+          <CardContent>
+            <div className='text-2xl font-bold'>832</div>
+            <p className='text-muted-foreground text-xs'>+5.8% vs last week</p>
+          </CardContent>
+        </Card>
+        <Card>
+          <CardHeader className='flex flex-row items-center justify-between space-y-0 pb-2'>
+            <CardTitle className='text-sm font-medium'>Bounce Rate</CardTitle>
+            <svg
+              xmlns='http://www.w3.org/2000/svg'
+              viewBox='0 0 24 24'
+              fill='none'
+              stroke='currentColor'
+              strokeLinecap='round'
+              strokeLinejoin='round'
+              strokeWidth='2'
+              className='text-muted-foreground h-4 w-4'
+            >
+              <path d='M3 12h6l3 6 3-6h6' />
+            </svg>
+          </CardHeader>
+          <CardContent>
+            <div className='text-2xl font-bold'>42%</div>
+            <p className='text-muted-foreground text-xs'>-3.2% vs last week</p>
+          </CardContent>
+        </Card>
+        <Card>
+          <CardHeader className='flex flex-row items-center justify-between space-y-0 pb-2'>
+            <CardTitle className='text-sm font-medium'>Avg. Session</CardTitle>
+            <svg
+              xmlns='http://www.w3.org/2000/svg'
+              viewBox='0 0 24 24'
+              fill='none'
+              stroke='currentColor'
+              strokeLinecap='round'
+              strokeLinejoin='round'
+              strokeWidth='2'
+              className='text-muted-foreground h-4 w-4'
+            >
+              <circle cx='12' cy='12' r='10' />
+              <path d='M12 6v6l4 2' />
+            </svg>
+          </CardHeader>
+          <CardContent>
+            <div className='text-2xl font-bold'>3m 24s</div>
+            <p className='text-muted-foreground text-xs'>+18s vs last week</p>
+          </CardContent>
+        </Card>
+      </div>
+      <div className='grid grid-cols-1 gap-4 lg:grid-cols-7'>
+        <Card className='col-span-1 lg:col-span-4'>
+          <CardHeader>
+            <CardTitle>Referrers</CardTitle>
+            <CardDescription>Top sources driving traffic</CardDescription>
+          </CardHeader>
+          <CardContent>
+            <SimpleBarList
+              items={[
+                { name: 'Direct', value: 512 },
+                { name: 'Product Hunt', value: 238 },
+                { name: 'Twitter', value: 174 },
+                { name: 'Blog', value: 104 },
+              ]}
+              barClass='bg-primary'
+              valueFormatter={(n) => `${n}`}
+            />
+          </CardContent>
+        </Card>
+        <Card className='col-span-1 lg:col-span-3'>
+          <CardHeader>
+            <CardTitle>Devices</CardTitle>
+            <CardDescription>How users access your app</CardDescription>
+          </CardHeader>
+          <CardContent>
+            <SimpleBarList
+              items={[
+                { name: 'Desktop', value: 74 },
+                { name: 'Mobile', value: 22 },
+                { name: 'Tablet', value: 4 },
+              ]}
+              barClass='bg-muted-foreground'
+              valueFormatter={(n) => `${n}%`}
+            />
+          </CardContent>
+        </Card>
+      </div>
+    </div>
+  )
+}

+ 5 - 3
src/features/dashboard/index.tsx

@@ -14,6 +14,7 @@ import { TopNav } from '@/components/layout/top-nav'
 import { ProfileDropdown } from '@/components/profile-dropdown'
 import { Search } from '@/components/search'
 import { ThemeSwitch } from '@/components/theme-switch'
+import { Analytics } from './components/analytics'
 import { Overview } from './components/overview'
 import { RecentSales } from './components/recent-sales'
 
@@ -47,9 +48,7 @@ export function Dashboard() {
           <div className='w-full overflow-x-auto pb-2'>
             <TabsList>
               <TabsTrigger value='overview'>Overview</TabsTrigger>
-              <TabsTrigger value='analytics' disabled>
-                Analytics
-              </TabsTrigger>
+              <TabsTrigger value='analytics'>Analytics</TabsTrigger>
               <TabsTrigger value='reports' disabled>
                 Reports
               </TabsTrigger>
@@ -184,6 +183,9 @@ export function Dashboard() {
               </Card>
             </div>
           </TabsContent>
+          <TabsContent value='analytics' className='space-y-4'>
+            <Analytics />
+          </TabsContent>
         </Tabs>
       </Main>
     </>