feat: dashboard on landing page

This commit is contained in:
zernonia 2023-09-05 13:20:52 +08:00
parent 4a748fe14a
commit 8857f489bb
12 changed files with 1754 additions and 10 deletions

View File

@ -13,6 +13,7 @@
"dependencies": { "dependencies": {
"@morev/vue-transitions": "^2.3.6", "@morev/vue-transitions": "^2.3.6",
"@tanstack/vue-table": "^8.9.3", "@tanstack/vue-table": "^8.9.3",
"@unovis/ts": "^1.2.1",
"@vitejs/plugin-vue-jsx": "^3.0.2", "@vitejs/plugin-vue-jsx": "^3.0.2",
"@vueuse/core": "^10.2.1", "@vueuse/core": "^10.2.1",
"class-variance-authority": "^0.6.1", "class-variance-authority": "^0.6.1",

View File

@ -1,5 +0,0 @@
---
layout: docs
---
docs laytou please?

View File

@ -2,4 +2,8 @@
home: true home: true
--- ---
this is main content <script setup>
import Dashboard from "@/examples/dashboard/Example.vue"
</script>
<Dashboard />

View File

@ -0,0 +1,218 @@
<script setup lang="ts">
import Overview from './components/Overview.vue'
import DateRangePicker from './components/DateRangePicker.vue'
import MainNav from './components/MainNav.vue'
import RecentSales from './components/RecentSales.vue'
import Search from './components/Search.vue'
import TeamSwitcher from './components/TeamSwitcher.vue'
import UserNav from './components/UserNav.vue'
import { Button } from '@/lib/registry/default/ui/button'
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from '@/lib/registry/default/ui/card'
import {
Tabs,
TabsContent,
TabsList,
TabsTrigger,
} from '@/lib/registry/default/ui/tabs'
</script>
<template>
<div class="md:hidden">
<img
src="/examples/dashboard-light.png"
width="1280"
height="866"
alt="Dashboard"
class="block dark:hidden"
>
<img
src="/examples/dashboard-dark.png"
width="{1280}"
height="{866}"
alt="Dashboard"
class="hidden dark:block"
>
</div>
<div class="hidden flex-col md:flex">
<div class="border-b">
<div class="flex h-16 items-center px-4">
<TeamSwitcher />
<MainNav class="mx-6" />
<div class="ml-auto flex items-center space-x-4">
<Search />
<UserNav />
</div>
</div>
</div>
<div class="flex-1 space-y-4 p-8 pt-6">
<div class="flex items-center justify-between space-y-2">
<h2 class="text-3xl font-bold tracking-tight">
Dashboard
</h2>
<div class="flex items-center space-x-2">
<DateRangePicker />
<Button>Download</Button>
</div>
</div>
<Tabs default-value="overview" class="space-y-4">
<TabsList>
<TabsTrigger value="overview">
Overview
</TabsTrigger>
<TabsTrigger value="analytics" disabled>
Analytics
</TabsTrigger>
<TabsTrigger value="reports" disabled>
Reports
</TabsTrigger>
<TabsTrigger value="notifications" disabled>
Notifications
</TabsTrigger>
</TabsList>
<TabsContent value="overview" class="space-y-4">
<div class="grid gap-4 md:grid-cols-2 lg:grid-cols-4">
<Card>
<CardHeader class="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle class="text-sm font-medium">
Total Revenue
</CardTitle>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
class="h-4 w-4 text-muted-foreground"
>
<path d="M12 2v20M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6" />
</svg>
</CardHeader>
<CardContent>
<div class="text-2xl font-bold">
$45,231.89
</div>
<p class="text-xs text-muted-foreground">
+20.1% from last month
</p>
</CardContent>
</Card>
<Card>
<CardHeader class="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle class="text-sm font-medium">
Subscriptions
</CardTitle>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
class="h-4 w-4 text-muted-foreground"
>
<path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2" />
<circle cx="9" cy="7" r="4" />
<path d="M22 21v-2a4 4 0 0 0-3-3.87M16 3.13a4 4 0 0 1 0 7.75" />
</svg>
</CardHeader>
<CardContent>
<div class="text-2xl font-bold">
+2350
</div>
<p class="text-xs text-muted-foreground">
+180.1% from last month
</p>
</CardContent>
</Card>
<Card>
<CardHeader class="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle class="text-sm font-medium">
Sales
</CardTitle>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
class="h-4 w-4 text-muted-foreground"
>
<rect width="20" height="14" x="2" y="5" rx="2" />
<path d="M2 10h20" />
</svg>
</CardHeader>
<CardContent>
<div class="text-2xl font-bold">
+12,234
</div>
<p class="text-xs text-muted-foreground">
+19% from last month
</p>
</CardContent>
</Card>
<Card>
<CardHeader class="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle class="text-sm font-medium">
Active Now
</CardTitle>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
class="h-4 w-4 text-muted-foreground"
>
<path d="M22 12h-4l-3 9L9 3l-3 9H2" />
</svg>
</CardHeader>
<CardContent>
<div class="text-2xl font-bold">
+573
</div>
<p class="text-xs text-muted-foreground">
+201 since last hour
</p>
</CardContent>
</Card>
</div>
<div class="grid gap-4 md:grid-cols-2 lg:grid-cols-7">
<Card class="col-span-4">
<CardHeader>
<CardTitle>Overview</CardTitle>
</CardHeader>
<CardContent class="pl-2">
<Overview />
</CardContent>
</Card>
<Card class="col-span-3">
<CardHeader>
<CardTitle>Recent Sales</CardTitle>
<CardDescription>
You made 265 sales this month.
</CardDescription>
</CardHeader>
<CardContent>
<RecentSales />
</CardContent>
</Card>
</div>
</TabsContent>
</Tabs>
</div>
</div>
</template>

View File

@ -0,0 +1,51 @@
<script setup lang="ts">
import { addDays, format } from 'date-fns'
import { Calendar as CalendarIcon } from 'lucide-vue-next'
import { ref } from 'vue'
import { cn } from '@/lib/utils'
import { Button } from '@/lib/registry/default/ui/button'
import { Calendar } from '@/lib/registry/default/ui/calendar'
import {
Popover,
PopoverContent,
PopoverTrigger,
} from '@/lib/registry/default/ui/popover'
const date = ref({
start: new Date(2023, 0, 20),
end: addDays(new Date(2023, 0, 20), 20),
})
</script>
<template>
<div :class="cn('grid gap-2', $attrs.class ?? '')">
<Popover>
<PopoverTrigger as-child>
<Button
id="date"
:variant="'outline'"
:class="cn(
'w-[260px] justify-start text-left font-normal',
!date && 'text-muted-foreground',
)"
>
<CalendarIcon class="mr-2 h-4 w-4" />
<span>
{{ date.start ? (
date.end ? `${format(date.start, 'LLL dd, y')} - ${format(date.end, 'LLL dd, y')}`
: format(date.start, 'LLL dd, y')
) : 'Pick a date' }}
</span>
</Button>
</PopoverTrigger>
<PopoverContent class="w-auto p-0" :align="'end'" :avoid-collisions="true">
<Calendar
v-model.range="date"
:columns="2"
/>
</PopoverContent>
</Popover>
</div>
</template>

View File

@ -0,0 +1,34 @@
<script setup lang="ts">
import { cn } from '@/lib/utils'
</script>
<template>
<nav
:class="cn('flex items-center space-x-4 lg:space-x-6', $attrs.class ?? '')"
>
<Link
href="/examples/dashboard"
class="text-sm font-medium transition-colors hover:text-primary"
>
Overview
</Link>
<Link
href="/examples/dashboard"
class="text-sm font-medium text-muted-foreground transition-colors hover:text-primary"
>
Customers
</Link>
<Link
href="/examples/dashboard"
class="text-sm font-medium text-muted-foreground transition-colors hover:text-primary"
>
Products
</Link>
<Link
href="/examples/dashboard"
class="text-sm font-medium text-muted-foreground transition-colors hover:text-primary"
>
Settings
</Link>
</nav>
</template>

View File

@ -0,0 +1,100 @@
<script setup lang="ts">
import { Axis, StackedBar, XYContainer } from '@unovis/ts'
import { nextTick, onMounted, ref } from 'vue'
const data = [
{
name: 'Jan',
total: Math.floor(Math.random() * 5000) + 1000,
},
{
name: 'Feb',
total: Math.floor(Math.random() * 5000) + 1000,
},
{
name: 'Mar',
total: Math.floor(Math.random() * 5000) + 1000,
},
{
name: 'Apr',
total: Math.floor(Math.random() * 5000) + 1000,
},
{
name: 'May',
total: Math.floor(Math.random() * 5000) + 1000,
},
{
name: 'Jun',
total: Math.floor(Math.random() * 5000) + 1000,
},
{
name: 'Jul',
total: Math.floor(Math.random() * 5000) + 1000,
},
{
name: 'Aug',
total: Math.floor(Math.random() * 5000) + 1000,
},
{
name: 'Sep',
total: Math.floor(Math.random() * 5000) + 1000,
},
{
name: 'Oct',
total: Math.floor(Math.random() * 5000) + 1000,
},
{
name: 'Nov',
total: Math.floor(Math.random() * 5000) + 1000,
},
{
name: 'Dec',
total: Math.floor(Math.random() * 5000) + 1000,
},
]
const container = ref<HTMLElement>()
let chart: XYContainer<typeof data[0]>
onMounted(async () => {
if (!container.value)
return
await nextTick()
const bar = new StackedBar<typeof data[0]>({
x: (d, i) => i,
y: d => d.total,
color: '#adfa1d',
roundedCorners: 4,
barPadding: 0.15,
})
chart = new XYContainer(container.value, {
components: [bar],
height: '350px',
margin: { left: 20, right: 20 },
xAxis: new Axis({
type: 'x',
numTicks: data.length,
tickFormat: (index: number) => data[index].name,
gridLine: false,
tickLine: false,
color: '#888888',
}),
yAxis: new Axis({
type: 'y',
tickFormat: (value: number) => `$${value}`,
gridLine: false,
domainLine: false,
tickLine: false,
color: '#888888',
}),
}, data)
})
</script>
<template>
<div ref="container" />
</template>

View File

@ -0,0 +1,97 @@
<script setup lang="ts">
import {
Avatar,
AvatarFallback,
AvatarImage,
} from '@/lib/registry/default/ui/avatar'
</script>
<template>
<div class="space-y-8">
<div class="flex items-center">
<Avatar class="h-9 w-9">
<AvatarImage src="/avatars/01.png" alt="Avatar" />
<AvatarFallback>OM</AvatarFallback>
</Avatar>
<div class="ml-4 space-y-1">
<p class="text-sm font-medium leading-none">
Olivia Martin
</p>
<p class="text-sm text-muted-foreground">
olivia.martin@email.com
</p>
</div>
<div class="ml-auto font-medium">
+$1,999.00
</div>
</div>
<div class="flex items-center">
<Avatar class="flex h-9 w-9 items-center justify-center space-y-0 border">
<AvatarImage src="/avatars/02.png" alt="Avatar" />
<AvatarFallback>JL</AvatarFallback>
</Avatar>
<div class="ml-4 space-y-1">
<p class="text-sm font-medium leading-none">
Jackson Lee
</p>
<p class="text-sm text-muted-foreground">
jackson.lee@email.com
</p>
</div>
<div class="ml-auto font-medium">
+$39.00
</div>
</div>
<div class="flex items-center">
<Avatar class="h-9 w-9">
<AvatarImage src="/avatars/03.png" alt="Avatar" />
<AvatarFallback>IN</AvatarFallback>
</Avatar>
<div class="ml-4 space-y-1">
<p class="text-sm font-medium leading-none">
Isabella Nguyen
</p>
<p class="text-sm text-muted-foreground">
isabella.nguyen@email.com
</p>
</div>
<div class="ml-auto font-medium">
+$299.00
</div>
</div>
<div class="flex items-center">
<Avatar class="h-9 w-9">
<AvatarImage src="/avatars/04.png" alt="Avatar" />
<AvatarFallback>WK</AvatarFallback>
</Avatar>
<div class="ml-4 space-y-1">
<p class="text-sm font-medium leading-none">
William Kim
</p>
<p class="text-sm text-muted-foreground">
will@email.com
</p>
</div>
<div class="ml-auto font-medium">
+$99.00
</div>
</div>
<div class="flex items-center">
<Avatar class="h-9 w-9">
<AvatarImage src="/avatars/05.png" alt="Avatar" />
<AvatarFallback>SD</AvatarFallback>
</Avatar>
<div class="ml-4 space-y-1">
<p class="text-sm font-medium leading-none">
Sofia Davis
</p>
<p class="text-sm text-muted-foreground">
sofia.davis@email.com
</p>
</div>
<div class="ml-auto font-medium">
+$39.00
</div>
</div>
</div>
</template>

View File

@ -0,0 +1,13 @@
<script setup lang="ts">
import { Input } from '@/lib/registry/default/ui/input'
</script>
<template>
<div>
<Input
type="search"
placeholder="Search..."
class="md:w-[100px] lg:w-[300px]"
/>
</div>
</template>

View File

@ -0,0 +1,194 @@
<script setup lang="ts">
import { ref } from 'vue'
import CaretSortIcon from '~icons/radix-icons/caret-sort'
import { cn } from '@/lib/utils'
import {
Avatar,
AvatarFallback,
AvatarImage,
} from '@/lib/registry/default/ui/avatar'
import { Button } from '@/lib/registry/default/ui/button'
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from '@/lib/registry/default/ui/dialog'
import { Input } from '@/lib/registry/default/ui/input'
import { Label } from '@/lib/registry/default/ui/label'
import {
Popover,
PopoverContent,
PopoverTrigger,
} from '@/lib/registry/default/ui/popover'
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/lib/registry/default/ui/select'
const groups = [
{
label: 'Personal Account',
teams: [
{
label: 'Alicia Koch',
value: 'personal',
},
],
},
{
label: 'Teams',
teams: [
{
label: 'Acme Inc.',
value: 'acme-inc',
},
{
label: 'Monsters Inc.',
value: 'monsters',
},
],
},
]
type Team = (typeof groups)[number]['teams'][number]
const open = ref(false)
const showNewTeamDialog = ref(false)
const selectedTeam = ref<Team>(groups[0].teams[0])
</script>
<template>
<Dialog v-model:open="showNewTeamDialog">
<Popover v-model:open="open">
<PopoverTrigger as-child>
<Button
variant="outline"
role="combobox"
aria-expanded="open"
aria-label="Select a team"
:class="cn('w-[200px] justify-between', $attrs.class ?? '')"
>
<Avatar class="mr-2 h-5 w-5">
<AvatarImage
:src="`https://avatar.vercel.sh/${selectedTeam.value}.png`"
:alt="selectedTeam.label"
/>
<AvatarFallback>SC</AvatarFallback>
</Avatar>
{{ selectedTeam.label }}
<CaretSortIcon class="ml-auto h-4 w-4 shrink-0 opacity-50" />
</Button>
</PopoverTrigger>
<PopoverContent class="w-[200px] p-0">
<!-- <Command>
<CommandList>
<CommandInput placeholder="Search team..." />
<CommandEmpty>No team found.</CommandEmpty>
{groups.map((group) => (
<CommandGroup key={group.label} heading={group.label}>
{group.teams.map((team) => (
<CommandItem
key={team.value}
onSelect={() => {
setSelectedTeam(team)
setOpen(false)
}}
class="text-sm"
>
<Avatar class="mr-2 h-5 w-5">
<AvatarImage
src={`https://avatar.vercel.sh/${team.value}.png`}
alt={team.label}
class="grayscale"
/>
<AvatarFallback>SC</AvatarFallback>
</Avatar>
{team.label}
<CheckIcon
class={cn(
"ml-auto h-4 w-4",
selectedTeam.value === team.value
? "opacity-100"
: "opacity-0"
)}
/>
</CommandItem>
))}
</CommandGroup>
))}
</CommandList>
<CommandSeparator />
<CommandList>
<CommandGroup>
<DialogTrigger asChild>
<CommandItem
onSelect={() => {
setOpen(false)
setShowNewTeamDialog(true)
}}
>
<PlusCircledIcon class="mr-2 h-5 w-5" />
Create Team
</CommandItem>
</DialogTrigger>
</CommandGroup>
</CommandList>
</Command> -->
</PopoverContent>
</Popover>
<DialogContent>
<DialogHeader>
<DialogTitle>Create team</DialogTitle>
<DialogDescription>
Add a new team to manage products and customers.
</DialogDescription>
</DialogHeader>
<div>
<div class="space-y-4 py-2 pb-4">
<div class="space-y-2">
<Label for="name">Team name</Label>
<Input id="name" placeholder="Acme Inc." />
</div>
<div class="space-y-2">
<Label for="plan">Subscription plan</Label>
<Select>
<SelectTrigger>
<SelectValue placeholder="Select a plan" />
</SelectTrigger>
<SelectContent>
<SelectItem value="free">
<span class="font-medium">Free</span> -{" "}
<span class="text-muted-foreground">
Trial for two weeks
</span>
</SelectItem>
<SelectItem value="pro">
<span class="font-medium">Pro</span> -{" "}
<span class="text-muted-foreground">
$9/month per user
</span>
</SelectItem>
</SelectContent>
</Select>
</div>
</div>
</div>
<DialogFooter>
<Button variant="outline" @click="showNewTeamDialog = false">
Cancel
</Button>
<Button type="submit">
Continue
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</template>

View File

@ -0,0 +1,64 @@
<script setup lang="ts">
import {
Avatar,
AvatarFallback,
AvatarImage,
} from '@/lib/registry/default/ui/avatar'
import { Button } from '@/lib/registry/default/ui/button'
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuShortcut,
DropdownMenuTrigger,
} from '@/lib/registry/default/ui/dropdown-menu'
</script>
<template>
<DropdownMenu>
<DropdownMenuTrigger as-child>
<Button variant="ghost" class="relative h-8 w-8 rounded-full">
<Avatar class="h-8 w-8">
<AvatarImage src="/avatars/01.png" alt="@shadcn" />
<AvatarFallback>SC</AvatarFallback>
</Avatar>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent class="w-56" align="end" force-mount>
<DropdownMenuLabel class="font-normal">
<div class="flex flex-col space-y-1">
<p class="text-sm font-medium leading-none">
shadcn
</p>
<p class="text-xs leading-none text-muted-foreground">
m@example.com
</p>
</div>
</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem>
Profile
<DropdownMenuShortcut>P</DropdownMenuShortcut>
</DropdownMenuItem>
<DropdownMenuItem>
Billing
<DropdownMenuShortcut>B</DropdownMenuShortcut>
</DropdownMenuItem>
<DropdownMenuItem>
Settings
<DropdownMenuShortcut>S</DropdownMenuShortcut>
</DropdownMenuItem>
<DropdownMenuItem>New Team</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuItem>
Log out
<DropdownMenuShortcut>Q</DropdownMenuShortcut>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</template>

File diff suppressed because it is too large Load Diff