From 7f700d035b73d9d2dadfaf54af3fc537ceb6b35c Mon Sep 17 00:00:00 2001
From: wwayne <wayne.wang0821@gmail.com>
Date: Sun, 7 Apr 2024 12:40:45 +0800
Subject: [PATCH 01/16] update

---
 .../analytics/components/analytic.tsx         | 207 +++++++++++++
 .../components/analyticAccptance.tsx          | 102 +++++++
 .../components/analyticDailyCompletion.tsx    |  94 ++++++
 .../components/analyticYearlyCompletion.tsx   |  39 +++
 .../app/(dashboard)/analytics/page.tsx        |  11 +
 .../app/(dashboard)/analytics/types/stats.ts  |   6 +
 .../app/(dashboard)/components/sidebar.tsx    |   7 +-
 ee/tabby-ui/components/activity-calendar.tsx  |  44 +++
 ee/tabby-ui/components/date-range-picker.tsx  |  80 +++++
 ee/tabby-ui/components/ui/calendar.tsx        |  66 ++++
 ee/tabby-ui/components/ui/icons.tsx           |  24 +-
 ee/tabby-ui/lib/hooks/use-all-members.tsx     |  50 ++++
 ee/tabby-ui/lib/tabby/query.ts                |  53 ++++
 ee/tabby-ui/package.json                      |   9 +
 ee/tabby-ui/yarn.lock                         | 282 +++++++++++++++++-
 15 files changed, 1069 insertions(+), 5 deletions(-)
 create mode 100644 ee/tabby-ui/app/(dashboard)/analytics/components/analytic.tsx
 create mode 100644 ee/tabby-ui/app/(dashboard)/analytics/components/analyticAccptance.tsx
 create mode 100644 ee/tabby-ui/app/(dashboard)/analytics/components/analyticDailyCompletion.tsx
 create mode 100644 ee/tabby-ui/app/(dashboard)/analytics/components/analyticYearlyCompletion.tsx
 create mode 100644 ee/tabby-ui/app/(dashboard)/analytics/page.tsx
 create mode 100644 ee/tabby-ui/app/(dashboard)/analytics/types/stats.ts
 create mode 100644 ee/tabby-ui/components/activity-calendar.tsx
 create mode 100644 ee/tabby-ui/components/date-range-picker.tsx
 create mode 100644 ee/tabby-ui/components/ui/calendar.tsx
 create mode 100644 ee/tabby-ui/lib/hooks/use-all-members.tsx

diff --git a/ee/tabby-ui/app/(dashboard)/analytics/components/analytic.tsx b/ee/tabby-ui/app/(dashboard)/analytics/components/analytic.tsx
new file mode 100644
index 000000000000..079b699ba98a
--- /dev/null
+++ b/ee/tabby-ui/app/(dashboard)/analytics/components/analytic.tsx
@@ -0,0 +1,207 @@
+'use client'
+
+import { useState } from 'react'
+import moment from 'moment'
+import { useQuery } from 'urql'
+import { DateRange } from "react-day-picker"
+import { sum } from "lodash-es"
+import numeral from "numeral"
+
+import { queryDailyStatsInPastYear, queryDailyStats } from '@/lib/tabby/query'
+import { Language } from '@/lib/gql/generates/graphql'
+import { useAllMembers } from '@/lib/hooks/use-all-members'
+
+import {
+  Select,
+  SelectContent,
+  SelectGroup,
+  SelectItem,
+  SelectTrigger,
+  SelectValue,
+} from "@/components/ui/select"
+import { Skeleton } from '@/components/ui/skeleton'
+import DatePickerWithRange from '@/components/date-range-picker'
+import LoadingWrapper from '@/components/loading-wrapper'
+import { AnalyticDailyCompletion } from './analyticDailyCompletion'
+import { AnlyticAcceptance } from './analyticAccptance'
+import { AnalyticYearlyCompletion } from './analyticYearlyCompletion'
+
+import type { DailyStats } from '../types/stats'
+
+const INITIAL_DATE_RANGE = 14
+const KEY_SELECT_ALL = 'all'
+
+function AnalyticSummary({
+  dailyStats,
+}: {
+  dailyStats: DailyStats[] | undefined,
+}) {
+  const totalCompletions = sum(dailyStats?.map(stats => stats.completions))
+  return (
+    <div className="flex items-center justify-center space-x-4 xl:justify-start">
+      <div className="space-y-0.5 rounded-lg border bg-primary-foreground/30 p-4 lg:min-w-[250px]">
+        <p className="text-xs text-muted-foreground lg:text-sm">Total completions</p>
+        <p className="font-bold lg:text-3xl">
+          {numeral(totalCompletions).format('0,0')}
+        </p>
+      </div>
+
+      <div className="space-y-0.5 rounded-lg border bg-primary-foreground/30 p-4 lg:min-w-[250px]">
+        <p className="text-xs text-muted-foreground lg:text-sm">Minutes saved / completion</p>
+        <p className="font-bold lg:text-3xl">2</p>
+      </div>
+
+      <div className="space-y-0.5 rounded-lg border bg-primary-foreground/30 p-4 lg:min-w-[250px]">
+        <p className="text-xs text-muted-foreground lg:text-sm">Hours saved in total</p>
+        <p className="font-bold lg:text-3xl">100</p>
+      </div>
+    </div>
+  )
+}
+
+export function Analytic() {
+  const [members] = useAllMembers()
+  const [dateRange, setDateRange] = useState<DateRange>({
+    from: moment().subtract(INITIAL_DATE_RANGE, 'day').toDate(),
+    to: moment().toDate()
+  })
+  const [selectedMember, setSelectedMember] = useState(KEY_SELECT_ALL)
+  const [selectedLanguage, setSelectedLanguage] = useState<'all' | Language>(KEY_SELECT_ALL)
+
+  // Query stats of selected date range
+  const [{ data: dailyStatsData, fetching: fetchingDailyState }] = useQuery({
+    query: queryDailyStats,
+    variables: {
+      start: moment(dateRange.from).startOf('day').utc().format(),
+      end: moment(dateRange.to).endOf('day').utc().format(),
+      users: selectedMember === KEY_SELECT_ALL ? undefined : [selectedMember],
+      languages: selectedLanguage === KEY_SELECT_ALL ? undefined : [selectedLanguage],
+    }
+  })
+  const dailyStats: DailyStats[] | undefined = dailyStatsData?.dailyStats.map(item => ({
+    start: item.start,
+    end: item.end,
+    completions: item.completions,
+    selects: item.selects,
+  }))
+  console.log(dailyStats)
+
+  // Query yearly stats
+  const [{ data: yearlyStatsData, fetching: fetchingYearlyStats }] = useQuery({
+    query: queryDailyStatsInPastYear,
+    variables: {
+      users: selectedMember === KEY_SELECT_ALL ? undefined : selectedMember
+    }
+  })
+  const yearlyStats: DailyStats[] | undefined = yearlyStatsData?.dailyStatsInPastYear.map(item => ({
+    start: item.start,
+    end: item.end,
+    completions: item.completions,
+    selects: item.selects,
+  }))
+
+  const onDateOpenChange = (isOpen: boolean, dateRange: DateRange | undefined) => {
+    if (!isOpen) {
+      if (dateRange) {
+        setDateRange(dateRange)
+      }
+    }
+  }
+
+  return (
+    <div className="flex flex-col gap-y-6">
+      <div className="flex flex-col items-center justify-between gap-y-3 xl:flex-row xl:gap-y-0">
+        <div className="flex flex-col justify-center xl:justify-start">
+          <h1 className="mb-1.5 scroll-m-20 text-center text-4xl font-extrabold tracking-tight lg:text-5xl xl:text-left">
+            Analytics
+          </h1>
+          <p className="text-muted-foreground">Overview of code completion usage</p>
+        </div>
+
+        <div className="flex flex-col items-center gap-y-2 lg:flex-row lg:gap-y-0 lg:space-x-4">
+          <Select defaultValue={KEY_SELECT_ALL} onValueChange={setSelectedMember}>
+            <SelectTrigger className="w-[300px] lg:w-[180px]" >
+              <div className="flex w-full items-center truncate ">
+                <span className="mr-1.5 text-muted-foreground">
+                  Member:
+                </span>
+                <div className="overflow-hidden text-ellipsis">
+                  <SelectValue />
+                </div>
+              </div>
+            </SelectTrigger>
+            <SelectContent>
+              <SelectGroup>
+                <SelectItem value={KEY_SELECT_ALL}>All</SelectItem>
+                {members.map(member => (
+                  <SelectItem value={member.id} key={member.id}>{member.email}</SelectItem>
+                ))}
+              </SelectGroup>
+            </SelectContent>
+          </Select>
+
+          <Select defaultValue={KEY_SELECT_ALL} onValueChange={(value: 'all' | Language) => setSelectedLanguage(value)}>
+            <SelectTrigger className="w-[300px] lg:w-[180px]" >
+              <div className="flex w-full items-center truncate">
+                <span className="mr-1.5 text-muted-foreground">
+                  Language:
+                </span>
+                <div className="overflow-hidden text-ellipsis">
+                  <SelectValue />
+                </div>
+              </div>
+            </SelectTrigger>
+            <SelectContent>
+              <SelectGroup>
+                <SelectItem value={'all'}>All</SelectItem>
+                {Object.entries(Language).map(([key, value]) => (
+                  <SelectItem key={value} value={value}>{key}</SelectItem>
+                ))}
+              </SelectGroup>
+            </SelectContent>
+          </Select>
+
+          <DatePickerWithRange
+            buttonClassName="h-full"
+            contentAlign="end"
+            dateRange={dateRange}
+            onOpenChange={onDateOpenChange} />
+        </div>
+      </div>
+
+      <LoadingWrapper
+        loading={fetchingDailyState}
+        fallback={<Skeleton className="h-24 w-1/2" />}>
+        <AnalyticSummary dailyStats={dailyStats} />
+      </LoadingWrapper>
+
+      <LoadingWrapper
+        loading={fetchingDailyState}
+        fallback={<Skeleton className="h-64 w-full" />}>
+        <AnalyticDailyCompletion
+          dailyStats={dailyStats}
+          dateRange={dateRange} />
+      </LoadingWrapper>
+
+
+      <div className="flex flex-col gap-y-6 xl:flex-row xl:gap-x-6 xl:gap-y-0">
+        <div className="flex-1">
+          <LoadingWrapper
+            loading={fetchingDailyState}
+            fallback={<Skeleton className="h-64 w-full" />}>
+            <AnlyticAcceptance
+              dailyStats={dailyStats}
+              dateRange={dateRange} />
+          </LoadingWrapper>
+        </div>
+        <div style={{ flex: 3 }}>
+          <LoadingWrapper
+            loading={fetchingDailyState || fetchingYearlyStats}
+            fallback={<Skeleton className="h-64 w-full" />}>
+            <AnalyticYearlyCompletion yearlyStats={yearlyStats} />
+          </LoadingWrapper>
+        </div>
+      </div>
+    </div>
+  )
+}
\ No newline at end of file
diff --git a/ee/tabby-ui/app/(dashboard)/analytics/components/analyticAccptance.tsx b/ee/tabby-ui/app/(dashboard)/analytics/components/analyticAccptance.tsx
new file mode 100644
index 000000000000..7bd0793741c4
--- /dev/null
+++ b/ee/tabby-ui/app/(dashboard)/analytics/components/analyticAccptance.tsx
@@ -0,0 +1,102 @@
+import moment from "moment";
+
+import type { DateRange } from "react-day-picker"
+import type { DailyStats } from '../types/stats'
+
+import {
+  Legend,
+  ResponsiveContainer,
+  PieChart, Pie, Cell,
+} from 'recharts'
+
+export function AnlyticAcceptance ({
+	dailyStats,
+	dateRange,
+}: {
+	dailyStats: DailyStats[] | undefined;
+	dateRange: DateRange;
+}) {
+	const from = dateRange.from || new Date()
+	const to = dateRange.to || from
+
+	let totalAccpet = 0
+	let totalCompletions = 0
+	dailyStats?.forEach(stats => {
+		totalCompletions += stats.completions
+		totalAccpet += stats.selects
+	})
+	const totalPending = totalCompletions - totalAccpet
+
+	const data = [
+		{ name: 'Accept', value: totalAccpet},
+		{ name: 'Pending', value: totalPending},
+	];
+
+	const COLORS = ['#8884d8', '#b9b7e2'];
+
+	const RADIAN = Math.PI / 180;
+	const renderCustomizedLabel = ({
+		cx,
+		cy,
+		midAngle,
+		innerRadius,
+		outerRadius,
+		percent,
+		name
+	}: {
+		cx: number;
+		cy: number;
+		midAngle: number;
+		innerRadius: number;
+		outerRadius: number;
+		percent: number;
+		name: string;
+	}) => {
+		const radius = innerRadius + (outerRadius - innerRadius) * 0.5;
+		const x = cx + radius * Math.cos(-midAngle * RADIAN);
+		const y = cy + radius * Math.sin(-midAngle * RADIAN);
+
+		if (name.toLocaleLowerCase() === 'accept') {
+			return (
+				<text
+					x={x}
+					y={y}
+					fill="white"
+					textAnchor={x > cx ? 'start' : 'end'}
+					dominantBaseline="central"
+					fontSize={12}>
+					{`${(percent * 100).toFixed(0)}%`}
+				</text>
+			);
+		}
+		return
+	};
+
+	return (
+		<div className="rounded-lg border bg-primary-foreground/30 p-4">
+			<h1 className="text-xl font-bold">Acceptance</h1>
+			<p className="mt-0.5 text-xs text-muted-foreground">
+				{moment(from).format('D MMM, YYYY')} - {moment(to).format('D MMM, YYYY')}
+			</p>
+			<ResponsiveContainer width="100%" height={250}>
+				<PieChart width={700} height={400}>
+					<Pie
+						data={data}
+						cx="50%"
+						cy="50%"
+						labelLine={false}
+						label={renderCustomizedLabel}
+						outerRadius={80}
+						fill="#8884d8"
+						dataKey="value"
+					>
+						{data.map((entry, index) => (
+							<Cell key={`cell-${index}`} fill={COLORS[index % COLORS.length]} />
+						))}
+					</Pie>
+					{totalCompletions !== 0 && <Legend />}
+				</PieChart>
+			</ResponsiveContainer>
+		</div>
+	)
+}
\ No newline at end of file
diff --git a/ee/tabby-ui/app/(dashboard)/analytics/components/analyticDailyCompletion.tsx b/ee/tabby-ui/app/(dashboard)/analytics/components/analyticDailyCompletion.tsx
new file mode 100644
index 000000000000..81d172dddc4f
--- /dev/null
+++ b/ee/tabby-ui/app/(dashboard)/analytics/components/analyticDailyCompletion.tsx
@@ -0,0 +1,94 @@
+import { eachDayOfInterval } from 'date-fns'
+import moment from 'moment'
+
+import {
+  Bar,
+  BarChart,
+  ResponsiveContainer,
+  Tooltip,
+  XAxis,
+  YAxis,
+} from 'recharts'
+import { Card, CardContent } from '@/components/ui/card'
+
+import type { DateRange } from "react-day-picker"
+import type { DailyStats } from '../types/stats'
+
+function BarTooltip ({ active, payload, label }: {
+	active?: boolean;
+	label?: string
+	payload?: {
+		name: string;
+		value: number;
+	}[]
+}) {
+	if (active && payload && payload.length) {
+		const completion = payload[0].value
+		if (!completion) return null;
+		return (
+			<Card>
+				<CardContent className="px-4 py-2 text-sm">
+					<p>Completions: <b>{completion}</b></p>
+					<p className="text-muted-foreground">{label}</p>
+				</CardContent>
+			</Card>
+		);
+	}
+
+	return null;
+};
+
+export function AnalyticDailyCompletion ({
+	dailyStats,
+	dateRange,
+}: {
+	dailyStats: DailyStats[] | undefined;
+	dateRange: DateRange;
+}) {
+	const from = dateRange.from || new Date()
+	const to = dateRange.to || from
+
+	const dailyCompletionMap: Record<string, number> = dailyStats?.reduce((acc, cur) => {
+		const date = moment(cur.start).format('YYYY-MM-DD')
+		return { ...acc, [date]: cur.completions }
+	}, {}) || {}
+
+	const daysBetweenRange = eachDayOfInterval({
+		start: from,
+		end: to
+	});
+
+	const chartData = daysBetweenRange.map(date => {
+		const completionKey = moment(date).format('YYYY-MM-DD')
+		const value = dailyCompletionMap[completionKey] || 0
+		return {
+			name: moment(date).format('D MMM'),
+			value
+		}
+	})
+	return (
+		<div className="rounded-lg border bg-primary-foreground/30 p-4">
+			<h1 className="mb-5 text-xl font-bold">Completions</h1>
+			<ResponsiveContainer width="100%" height={350}>
+				<BarChart
+					width={500}
+					height={300}
+					data={chartData}
+					margin={{
+						top: 5,
+						right: 20,
+						left: 20,
+						bottom: 5
+					}}
+				>
+					<Bar dataKey="value" fill="#8884d8" />
+					<XAxis dataKey="name" fontSize={12} />
+					<YAxis fontSize={12} />
+					<Tooltip
+						cursor={{ fill: 'transparent' }}
+						content={<BarTooltip />} />
+				</BarChart>
+			</ResponsiveContainer>
+		</div>
+	)
+}
\ No newline at end of file
diff --git a/ee/tabby-ui/app/(dashboard)/analytics/components/analyticYearlyCompletion.tsx b/ee/tabby-ui/app/(dashboard)/analytics/components/analyticYearlyCompletion.tsx
new file mode 100644
index 000000000000..bad3fcf21529
--- /dev/null
+++ b/ee/tabby-ui/app/(dashboard)/analytics/components/analyticYearlyCompletion.tsx
@@ -0,0 +1,39 @@
+import moment from "moment";
+
+import ActivityCalendar from '@/components/activity-calendar'
+
+import type { DailyStats } from '../types/stats'
+
+export function AnalyticYearlyCompletion ({
+	yearlyStats,
+}: {
+	yearlyStats: DailyStats[] | undefined;
+}) {
+  let lastYearCompletions = 0
+  const dailyCompletionMap: Record<string, number> = yearlyStats?.reduce((acc, cur) => {
+		const date = moment(cur.start).format('YYYY-MM-DD')
+    lastYearCompletions += cur.completions 
+		return { ...acc, [date]: cur.completions }
+	}, {}) || {}
+
+  const data = new Array(365).fill("").map((_, idx) => {
+    const date = moment().subtract(idx, 'days').format('YYYY-MM-DD')
+    const count = dailyCompletionMap[date] || 0
+    const level = Math.min(4, Math.ceil(count / 5))
+    return {
+      date: date,
+      count,
+      level
+    }
+  }).reverse()
+
+  return (
+    <div className="flex h-full flex-col rounded-lg border bg-primary-foreground/30 p-4">
+      <h1 className="text-xl font-bold">Activity</h1>
+      <p className="mt-0.5 text-xs text-muted-foreground">{lastYearCompletions} completions in the last year</p>
+      <div className="mt-5 flex flex-1 items-center justify-center xl:mt-0">
+        <ActivityCalendar data={data} />
+      </div>
+    </div>
+  );
+}
\ No newline at end of file
diff --git a/ee/tabby-ui/app/(dashboard)/analytics/page.tsx b/ee/tabby-ui/app/(dashboard)/analytics/page.tsx
new file mode 100644
index 000000000000..98b0c6afc3c9
--- /dev/null
+++ b/ee/tabby-ui/app/(dashboard)/analytics/page.tsx
@@ -0,0 +1,11 @@
+import { Metadata } from 'next'
+
+import { Analytic } from './components/analyticAccptance'
+
+export const metadata: Metadata = {
+  title: 'Analytic dashboard'
+}
+
+export default function Page() {
+  return <Analytic />
+}
\ No newline at end of file
diff --git a/ee/tabby-ui/app/(dashboard)/analytics/types/stats.ts b/ee/tabby-ui/app/(dashboard)/analytics/types/stats.ts
new file mode 100644
index 000000000000..3205afa5db11
--- /dev/null
+++ b/ee/tabby-ui/app/(dashboard)/analytics/types/stats.ts
@@ -0,0 +1,6 @@
+export type DailyStats = {
+  start: Date;
+  end: Date;
+  completions: number;
+  selects: number;
+}
\ No newline at end of file
diff --git a/ee/tabby-ui/app/(dashboard)/components/sidebar.tsx b/ee/tabby-ui/app/(dashboard)/components/sidebar.tsx
index 7b0d8211a370..6c742c72ccb5 100644
--- a/ee/tabby-ui/app/(dashboard)/components/sidebar.tsx
+++ b/ee/tabby-ui/app/(dashboard)/components/sidebar.tsx
@@ -22,7 +22,8 @@ import {
   IconLightingBolt,
   IconNetwork,
   IconScrollText,
-  IconUser
+  IconUser,
+  IconBarChart
 } from '@/components/ui/icons'
 
 export interface SidebarProps {
@@ -70,6 +71,10 @@ export default function Sidebar({ children, className }: SidebarProps) {
                     <IconScrollText />
                     Jobs
                   </SidebarButton>
+                  <SidebarButton href="/analytic">
+                    <IconBarChart />
+                    Analytic
+                  </SidebarButton>
                   <SidebarCollapsible
                     title={
                       <>
diff --git a/ee/tabby-ui/components/activity-calendar.tsx b/ee/tabby-ui/components/activity-calendar.tsx
new file mode 100644
index 000000000000..9d09e20fe6e0
--- /dev/null
+++ b/ee/tabby-ui/components/activity-calendar.tsx
@@ -0,0 +1,44 @@
+'use client'
+
+import dynamic from 'next/dynamic';
+import { useTheme } from 'next-themes'
+import { useWindowSize } from "@uidotdev/usehooks";
+
+// withou using dynamic, we got error "Error: calcTextDimensions() requires browser APIs"
+const ReactActivityCalendar = dynamic(() => import('react-activity-calendar'), {
+  ssr: false,
+});
+
+export default function ActivityCalendar ({
+  data
+}: {
+  data: {
+    date: string;
+    count: number;
+    level: number;
+  }[]
+}) {
+  const { theme } = useTheme()
+  const size = useWindowSize()
+  const width = size.width || 0
+  const blockSize = width >= 1600
+    ? 13
+    : width >= 1400
+      ? 10
+      : width >= 1000
+        ? 8
+        : 5
+
+  return (
+    <ReactActivityCalendar
+      data={data}
+      colorScheme={theme === 'dark' ? 'dark' : 'light'}
+      theme={{
+        light: ['#ebedf0', '#9be9a8', '#40c463', '#30a14e', '#216e39'],
+        dark: ['rgb(45, 51, 59)', '#0e4429', '#006d32', '#26a641', '#39d353'],
+      }}
+      blockSize={blockSize}
+      hideTotalCount
+      showWeekdayLabels />
+  );
+}
\ No newline at end of file
diff --git a/ee/tabby-ui/components/date-range-picker.tsx b/ee/tabby-ui/components/date-range-picker.tsx
new file mode 100644
index 000000000000..b81e588a1f30
--- /dev/null
+++ b/ee/tabby-ui/components/date-range-picker.tsx
@@ -0,0 +1,80 @@
+"use client"
+
+import * as React from "react"
+import { CalendarIcon } from "@radix-ui/react-icons"
+import { addDays, format } from "date-fns"
+import { DateRange } from "react-day-picker"
+
+import { cn } from "@/lib/utils"
+import { Button } from "@/components/ui/button"
+import { Calendar } from "@/components/ui/calendar"
+import {
+  Popover,
+  PopoverContent,
+  PopoverTrigger,
+} from "@/components/ui/popover"
+
+export default function DatePickerWithRange({
+  dateRange,
+  className,
+  buttonClassName,
+  contentAlign,
+  onOpenChange
+}: React.HTMLAttributes<HTMLDivElement> & {
+  dateRange?: DateRange
+  buttonClassName?: string;
+  contentAlign?: "start" | "end" | "center";
+  onOpenChange?: (isOpen: boolean, date: DateRange | undefined) => void,
+  onSelectDateRange?: (date: DateRange | undefined) => void,
+}) {
+  const [date, setDate] = React.useState<DateRange | undefined>({
+    from: dateRange?.from || new Date(2022, 0, 20),
+    to: dateRange?.to || addDays(new Date(2022, 0, 20), 20),
+  })
+
+  const toggleOpen = (isOpen: boolean) => {
+    if (onOpenChange) onOpenChange(isOpen, date)
+  }
+
+  return (
+    <div className={cn("grid gap-2", className)}>
+      <Popover onOpenChange={toggleOpen}>
+        <PopoverTrigger asChild>
+          <Button
+            id="date"
+            variant={"outline"}
+            className={cn(
+              "w-[300px] justify-start text-left font-normal ",
+              !date && "text-muted-foreground",
+              buttonClassName
+            )}
+          >
+            <CalendarIcon className="size-4 mr-2" />
+            {date?.from ? (
+              date.to ? (
+                <>
+                  {format(date.from, "LLL dd, y")} -{" "}
+                  {format(date.to, "LLL dd, y")}
+                </>
+              ) : (
+                format(date.from, "LLL dd, y")
+              )
+            ) : (
+              <span>Pick a date</span>
+            )}
+          </Button>
+        </PopoverTrigger>
+        <PopoverContent className="w-auto p-0" align={contentAlign}>
+          <Calendar
+            initialFocus
+            mode="range"
+            defaultMonth={date?.from}
+            selected={date}
+            onSelect={setDate}
+            numberOfMonths={2}
+          />
+        </PopoverContent>
+      </Popover>
+    </div>
+  )
+}
\ No newline at end of file
diff --git a/ee/tabby-ui/components/ui/calendar.tsx b/ee/tabby-ui/components/ui/calendar.tsx
new file mode 100644
index 000000000000..43868fea1cf7
--- /dev/null
+++ b/ee/tabby-ui/components/ui/calendar.tsx
@@ -0,0 +1,66 @@
+"use client"
+
+import * as React from "react"
+import { ChevronLeft, ChevronRight } from "lucide-react"
+import { DayPicker } from "react-day-picker"
+
+import { cn } from "@/lib/utils"
+import { buttonVariants } from "@/components/ui/button"
+
+export type CalendarProps = React.ComponentProps<typeof DayPicker>
+
+function Calendar({
+  className,
+  classNames,
+  showOutsideDays = true,
+  ...props
+}: CalendarProps) {
+  return (
+    <DayPicker
+      showOutsideDays={showOutsideDays}
+      className={cn("p-3", className)}
+      classNames={{
+        months: "flex flex-col sm:flex-row space-y-4 sm:space-x-4 sm:space-y-0",
+        month: "space-y-4",
+        caption: "flex justify-center pt-1 relative items-center",
+        caption_label: "text-sm font-medium",
+        nav: "space-x-1 flex items-center",
+        nav_button: cn(
+          buttonVariants({ variant: "outline" }),
+          "h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100"
+        ),
+        nav_button_previous: "absolute left-1",
+        nav_button_next: "absolute right-1",
+        table: "w-full border-collapse space-y-1",
+        head_row: "flex",
+        head_cell:
+          "text-muted-foreground rounded-md w-9 font-normal text-[0.8rem]",
+        row: "flex w-full mt-2",
+        cell: "h-9 w-9 text-center text-sm p-0 relative [&:has([aria-selected].day-range-end)]:rounded-r-md [&:has([aria-selected].day-outside)]:bg-accent/50 [&:has([aria-selected])]:bg-accent first:[&:has([aria-selected])]:rounded-l-md last:[&:has([aria-selected])]:rounded-r-md focus-within:relative focus-within:z-20",
+        day: cn(
+          buttonVariants({ variant: "ghost" }),
+          "h-9 w-9 p-0 font-normal aria-selected:opacity-100"
+        ),
+        day_range_end: "day-range-end",
+        day_selected:
+          "bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground focus:bg-primary focus:text-primary-foreground",
+        day_today: "bg-accent text-accent-foreground",
+        day_outside:
+          "day-outside text-muted-foreground opacity-50 aria-selected:bg-accent/50 aria-selected:text-muted-foreground aria-selected:opacity-30",
+        day_disabled: "text-muted-foreground opacity-50",
+        day_range_middle:
+          "aria-selected:bg-accent aria-selected:text-accent-foreground",
+        day_hidden: "invisible",
+        ...classNames,
+      }}
+      components={{
+        IconLeft: ({ ...props }) => <ChevronLeft className="h-4 w-4" />,
+        IconRight: ({ ...props }) => <ChevronRight className="h-4 w-4" />,
+      }}
+      {...props}
+    />
+  )
+}
+Calendar.displayName = "Calendar"
+
+export { Calendar }
\ No newline at end of file
diff --git a/ee/tabby-ui/components/ui/icons.tsx b/ee/tabby-ui/components/ui/icons.tsx
index 5958e4248cec..eb61e8eca493 100644
--- a/ee/tabby-ui/components/ui/icons.tsx
+++ b/ee/tabby-ui/components/ui/icons.tsx
@@ -1270,6 +1270,27 @@ function IconStopWatch({ className, ...props }: React.ComponentProps<'svg'>) {
   )
 }
 
+function IconBarChart({ className, ...props }: React.ComponentProps<'svg'>) {
+  return (
+    <svg
+      xmlns="http://www.w3.org/2000/svg"
+      viewBox="0 0 24 24"
+      fill="none"
+      stroke="currentColor"
+      strokeWidth="2"
+      strokeLinecap="round"
+      strokeLinejoin="round"
+      className={cn('h-4 w-4', className)}
+      {...props}
+    >
+      <path d="M3 3v18h18" />
+      <path d="M18 17V9" />
+      <path d="M13 17V5" />
+      <path d="M8 17v-3" />
+    </svg>
+  )
+}
+
 export {
   IconEdit,
   IconNextChat,
@@ -1334,5 +1355,6 @@ export {
   IconCloudUpload,
   IconHistory,
   IconClock,
-  IconStopWatch
+  IconStopWatch,
+  IconBarChart
 }
diff --git a/ee/tabby-ui/lib/hooks/use-all-members.tsx b/ee/tabby-ui/lib/hooks/use-all-members.tsx
new file mode 100644
index 000000000000..336c8d45e01d
--- /dev/null
+++ b/ee/tabby-ui/lib/hooks/use-all-members.tsx
@@ -0,0 +1,50 @@
+import { useState, useEffect } from 'react'
+import { useQuery } from 'urql'
+
+import { QueryVariables } from '@/lib/tabby/gql'
+import { listUsers } from '@/lib/tabby/query'
+import { DEFAULT_PAGE_SIZE } from '@/lib/constants'
+
+type Member = {
+  id: string;
+  email: string;
+}
+
+export function useAllMembers() {
+  const [queryVariables, setQueryVariables] = useState<
+    QueryVariables<typeof listUsers>
+  >({ first: DEFAULT_PAGE_SIZE })
+
+  const [list, setList] = useState<Member[]>([])
+  const [isAllLoaded, setIsAllLoaded] = useState(false)
+
+  const [{ data, fetching }] = useQuery({
+    query: listUsers,
+    variables: queryVariables
+  })
+
+  useEffect(() => {
+    if (isAllLoaded) return
+    if (!fetching && data) {
+      const members: Member[] = data?.users.edges.map(edge => ({
+        id: edge.node.id,
+        email: edge.node.email
+      }))
+      const cursor = data?.users.pageInfo.endCursor || ""
+      const hasMore = data?.users.pageInfo.hasNextPage
+      const currentList = [...list]
+
+      setList(currentList.concat(members))
+      if (hasMore) {
+        setQueryVariables({
+          first: DEFAULT_PAGE_SIZE, 
+          after: cursor
+        })
+      } else {
+        setIsAllLoaded(true)
+      }
+    }
+  }, [queryVariables, fetching])
+
+  return [list]
+}
diff --git a/ee/tabby-ui/lib/tabby/query.ts b/ee/tabby-ui/lib/tabby/query.ts
index 2efcc08a8d72..895094464651 100644
--- a/ee/tabby-ui/lib/tabby/query.ts
+++ b/ee/tabby-ui/lib/tabby/query.ts
@@ -102,3 +102,56 @@ export const listJobs = graphql(/* GraphQL */ `
     jobs
   }
 `)
+
+export const listUsers = graphql(/* GraphQL */ `
+  query ListUsers($after: String, $before: String, $first: Int, $last: Int) {
+    users(after: $after, before: $before, first: $first, last: $last) {
+      edges {
+        node {
+          id
+          email
+          isAdmin
+          isOwner
+          createdAt
+          active
+        }
+        cursor
+      }
+      pageInfo {
+        hasNextPage
+        hasPreviousPage
+        startCursor
+        endCursor
+      }
+    }
+  }
+`)
+
+export const queryDailyStatsInPastYear = graphql(/* GraphQL */ `
+  query DailyStatsInPastYear(
+    $users: [ID!]
+  ) {
+    dailyStatsInPastYear(users: $users) {
+      start
+      end
+      completions
+      selects
+    }
+  }
+`)
+
+export const queryDailyStats = graphql(/* GraphQL */ `
+  query DailyStats(
+    $start: DateTimeUtc!,
+    $end: DateTimeUtc!,
+    $users: [ID!],
+    $languages: [Language!]
+  ) {
+    dailyStats(start: $start, end: $end, users: $users, languages: $languages) {
+      start
+      end
+      completions
+      selects
+    }
+  }
+`)
diff --git a/ee/tabby-ui/package.json b/ee/tabby-ui/package.json
index 1ed6a1a5f2a4..a72fa217670d 100644
--- a/ee/tabby-ui/package.json
+++ b/ee/tabby-ui/package.json
@@ -32,6 +32,7 @@
     "@radix-ui/react-dialog": "1.0.4",
     "@radix-ui/react-dropdown-menu": "^2.0.5",
     "@radix-ui/react-hover-card": "^1.0.7",
+    "@radix-ui/react-icons": "^1.3.0",
     "@radix-ui/react-label": "^2.0.2",
     "@radix-ui/react-popover": "^1.0.7",
     "@radix-ui/react-radio-group": "^1.1.3",
@@ -42,6 +43,7 @@
     "@radix-ui/react-switch": "^1.0.3",
     "@radix-ui/react-tabs": "^1.0.4",
     "@radix-ui/react-tooltip": "^1.0.6",
+    "@uidotdev/usehooks": "^2.4.1",
     "@uiw/codemirror-extensions-langs": "^4.21.21",
     "@urql/core": "^4.2.3",
     "@urql/exchange-auth": "^2.1.6",
@@ -54,6 +56,7 @@
     "clsx": "^1.2.1",
     "compare-versions": "^6.1.0",
     "copy-to-clipboard": "^3.3.3",
+    "date-fns": "^3.6.0",
     "downshift": "^8.2.2",
     "filename2prism": "^3.0.0",
     "focus-trap-react": "^10.1.1",
@@ -61,14 +64,18 @@
     "humanize-duration": "^3.31.0",
     "jwt-decode": "^4.0.0",
     "lodash-es": "^4.17.21",
+    "lucide-react": "^0.365.0",
     "mitt": "^3.0.1",
     "moment": "^2.29.4",
     "nanoid": "^4.0.2",
     "next": "^13.4.7",
     "next-themes": "^0.2.1",
+    "numeral": "^2.0.6",
     "openai-edge": "^0.5.1",
     "pretty-bytes": "^6.1.1",
     "react": "^18.2.0",
+    "react-activity-calendar": "^2.2.8",
+    "react-day-picker": "^8.10.0",
     "react-dom": "^18.2.0",
     "react-hook-form": "^7.48.2",
     "react-intersection-observer": "^9.4.4",
@@ -78,6 +85,7 @@
     "react-syntax-highlighter": "^15.5.0",
     "react-textarea-autosize": "^8.4.1",
     "react-topbar-progress-indicator": "^4.1.1",
+    "recharts": "^2.12.4",
     "rehype-raw": "6.1.1",
     "rehype-sanitize": "^6.0.0",
     "remark-gfm": "^3.0.1",
@@ -98,6 +106,7 @@
     "@types/humanize-duration": "^3.27.4",
     "@types/lodash-es": "^4.17.10",
     "@types/node": "^17.0.12",
+    "@types/numeral": "^2.0.5",
     "@types/react": "^18.0.22",
     "@types/react-dom": "^18.0.7",
     "@types/react-syntax-highlighter": "^15.5.6",
diff --git a/ee/tabby-ui/yarn.lock b/ee/tabby-ui/yarn.lock
index 25737c430d4a..6a67a9cb1ee7 100644
--- a/ee/tabby-ui/yarn.lock
+++ b/ee/tabby-ui/yarn.lock
@@ -559,6 +559,13 @@
   dependencies:
     regenerator-runtime "^0.14.0"
 
+"@babel/runtime@^7.5.5", "@babel/runtime@^7.8.7":
+  version "7.24.4"
+  resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.24.4.tgz#de795accd698007a66ba44add6cc86542aff1edd"
+  integrity sha512-dkxf7+hn8mFBwKjs9bvBlArzLVxVbS8usaPUDd5p2a9JCL9tB8OaOVN1isD4+Xyk4ns89/xeOmbQvgdK7IIVdA==
+  dependencies:
+    regenerator-runtime "^0.14.0"
+
 "@babel/template@^7.18.10", "@babel/template@^7.20.7", "@babel/template@^7.22.15":
   version "7.22.15"
   resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.22.15.tgz#09576efc3830f0430f4548ef971dde1350ef2f38"
@@ -2049,6 +2056,11 @@
     "@radix-ui/react-primitive" "1.0.3"
     "@radix-ui/react-use-controllable-state" "1.0.1"
 
+"@radix-ui/react-icons@^1.3.0":
+  version "1.3.0"
+  resolved "https://registry.yarnpkg.com/@radix-ui/react-icons/-/react-icons-1.3.0.tgz#c61af8f323d87682c5ca76b856d60c2312dbcb69"
+  integrity sha512-jQxj/0LKgp+j9BiTXz3O3sgs26RNet2iLWmsPyRz2SIcR4q/4SbazXfnYwbAr+vLYKSfc7qxzyGQA1HLlYiuNw==
+
 "@radix-ui/react-id@1.0.1":
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/@radix-ui/react-id/-/react-id-1.0.1.tgz#73cdc181f650e4df24f0b6a5b7aa426b912c88c0"
@@ -2448,6 +2460,62 @@
     lodash.merge "^4.6.2"
     postcss-selector-parser "6.0.10"
 
+"@types/chroma-js@^2.4.3":
+  version "2.4.4"
+  resolved "https://registry.yarnpkg.com/@types/chroma-js/-/chroma-js-2.4.4.tgz#254dddff54568ff8e5d0dcdb071871a458fdfd31"
+  integrity sha512-/DTccpHTaKomqussrn+ciEvfW4k6NAHzNzs/sts1TCqg333qNxOhy8TNIoQCmbGG3Tl8KdEhkGAssb1n3mTXiQ==
+
+"@types/d3-array@^3.0.3":
+  version "3.2.1"
+  resolved "https://registry.yarnpkg.com/@types/d3-array/-/d3-array-3.2.1.tgz#1f6658e3d2006c4fceac53fde464166859f8b8c5"
+  integrity sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg==
+
+"@types/d3-color@*":
+  version "3.1.3"
+  resolved "https://registry.yarnpkg.com/@types/d3-color/-/d3-color-3.1.3.tgz#368c961a18de721da8200e80bf3943fb53136af2"
+  integrity sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==
+
+"@types/d3-ease@^3.0.0":
+  version "3.0.2"
+  resolved "https://registry.yarnpkg.com/@types/d3-ease/-/d3-ease-3.0.2.tgz#e28db1bfbfa617076f7770dd1d9a48eaa3b6c51b"
+  integrity sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==
+
+"@types/d3-interpolate@^3.0.1":
+  version "3.0.4"
+  resolved "https://registry.yarnpkg.com/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz#412b90e84870285f2ff8a846c6eb60344f12a41c"
+  integrity sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==
+  dependencies:
+    "@types/d3-color" "*"
+
+"@types/d3-path@*":
+  version "3.1.0"
+  resolved "https://registry.yarnpkg.com/@types/d3-path/-/d3-path-3.1.0.tgz#2b907adce762a78e98828f0b438eaca339ae410a"
+  integrity sha512-P2dlU/q51fkOc/Gfl3Ul9kicV7l+ra934qBFXCFhrZMOL6du1TM0pm1ThYvENukyOn5h9v+yMJ9Fn5JK4QozrQ==
+
+"@types/d3-scale@^4.0.2":
+  version "4.0.8"
+  resolved "https://registry.yarnpkg.com/@types/d3-scale/-/d3-scale-4.0.8.tgz#d409b5f9dcf63074464bf8ddfb8ee5a1f95945bb"
+  integrity sha512-gkK1VVTr5iNiYJ7vWDI+yUFFlszhNMtVeneJ6lUTKPjprsvLLI9/tgEGiXJOnlINJA8FyA88gfnQsHbybVZrYQ==
+  dependencies:
+    "@types/d3-time" "*"
+
+"@types/d3-shape@^3.1.0":
+  version "3.1.6"
+  resolved "https://registry.yarnpkg.com/@types/d3-shape/-/d3-shape-3.1.6.tgz#65d40d5a548f0a023821773e39012805e6e31a72"
+  integrity sha512-5KKk5aKGu2I+O6SONMYSNflgiP0WfZIQvVUMan50wHsLG1G94JlxEVnCpQARfTtzytuY0p/9PXXZb3I7giofIA==
+  dependencies:
+    "@types/d3-path" "*"
+
+"@types/d3-time@*", "@types/d3-time@^3.0.0":
+  version "3.0.3"
+  resolved "https://registry.yarnpkg.com/@types/d3-time/-/d3-time-3.0.3.tgz#3c186bbd9d12b9d84253b6be6487ca56b54f88be"
+  integrity sha512-2p6olUZ4w3s+07q3Tm2dbiMZy5pCDfYwtLXXHUnVzXgQlZ/OyPtUz6OL382BkOuGlLXqfT+wqv8Fw2v8/0geBw==
+
+"@types/d3-timer@^3.0.0":
+  version "3.0.2"
+  resolved "https://registry.yarnpkg.com/@types/d3-timer/-/d3-timer-3.0.2.tgz#70bbda77dc23aa727413e22e214afa3f0e852f70"
+  integrity sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==
+
 "@types/debug@^4.0.0":
   version "4.1.9"
   resolved "https://registry.yarnpkg.com/@types/debug/-/debug-4.1.9.tgz#906996938bc672aaf2fb8c0d3733ae1dda05b005"
@@ -2546,6 +2614,11 @@
   resolved "https://registry.yarnpkg.com/@types/node/-/node-18.18.1.tgz#80b22f3df719f15c9736207980e95f35d01ec1aa"
   integrity sha512-3G42sxmm0fF2+Vtb9TJQpnjmP+uKlWvFa8KoEGquh4gqRmoUG/N0ufuhikw6HEsdG2G2oIKhog1GCTfz9v5NdQ==
 
+"@types/numeral@^2.0.5":
+  version "2.0.5"
+  resolved "https://registry.yarnpkg.com/@types/numeral/-/numeral-2.0.5.tgz#388e5c4ff4b0e1787f130753cbbe83d3ba770858"
+  integrity sha512-kH8I7OSSwQu9DS9JYdFWbuvhVzvFRoCPCkGxNwoGgaPeDfEPJlcxNvEOypZhQ3XXHsGbfIuYcxcJxKUfJHnRfw==
+
 "@types/parse5@^6.0.0":
   version "6.0.3"
   resolved "https://registry.yarnpkg.com/@types/parse5/-/parse5-6.0.3.tgz#705bb349e789efa06f43f128cef51240753424cb"
@@ -2725,6 +2798,11 @@
     "@typescript-eslint/types" "7.4.0"
     eslint-visitor-keys "^3.4.1"
 
+"@uidotdev/usehooks@^2.4.1":
+  version "2.4.1"
+  resolved "https://registry.yarnpkg.com/@uidotdev/usehooks/-/usehooks-2.4.1.tgz#4b733eaeae09a7be143c6c9ca158b56cc1ea75bf"
+  integrity sha512-1I+RwWyS+kdv3Mv0Vmc+p0dPYH0DTRAo04HLyXReYBL9AeseDWUJyi4THuksBJcu9F0Pih69Ak150VDnqbVnXg==
+
 "@uiw/codemirror-extensions-langs@^4.21.21":
   version "4.21.21"
   resolved "https://registry.yarnpkg.com/@uiw/codemirror-extensions-langs/-/codemirror-extensions-langs-4.21.21.tgz#c0aee1719a69e62b133b01d26c7a21bb3d1b559a"
@@ -3438,7 +3516,7 @@ chokidar@^3.5.3:
   optionalDependencies:
     fsevents "~2.3.2"
 
-chroma-js@^2.1.2:
+chroma-js@^2.1.2, chroma-js@^2.4.2:
   version "2.4.2"
   resolved "https://registry.yarnpkg.com/chroma-js/-/chroma-js-2.4.2.tgz#dffc214ed0c11fa8eefca2c36651d8e57cbfb2b0"
   integrity sha512-U9eDw6+wt7V8z5NncY2jJfZa+hUH8XEj8FQHgFJTrUFnJfXYf4Ml4adI2vXZOjqRDpFWtYVWypDfZwnJ+HIR4A==
@@ -3511,6 +3589,11 @@ clsx@^1.2.1:
   resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.2.1.tgz#0ddc4a20a549b59c93a4116bb26f5294ca17dc12"
   integrity sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==
 
+clsx@^2.0.0:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/clsx/-/clsx-2.1.0.tgz#e851283bcb5c80ee7608db18487433f7b23f77cb"
+  integrity sha512-m3iNNWpd9rl3jvvcBnu70ylMdrXt8Vlq4HYadnU5fwcOtvkSQWPmj7amUcDT2qYI7risszBjI5AUIUox9D16pg==
+
 codemirror-lang-mermaid@^0.5.0:
   version "0.5.0"
   resolved "https://registry.yarnpkg.com/codemirror-lang-mermaid/-/codemirror-lang-mermaid-0.5.0.tgz#7e31bd474128febf6296d72412e85d86a9a493eb"
@@ -3705,6 +3788,77 @@ csstype@^3.0.2:
   resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.2.tgz#1d4bf9d572f11c14031f0436e1c10bc1f571f50b"
   integrity sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==
 
+"d3-array@2 - 3", "d3-array@2.10.0 - 3", d3-array@^3.1.6:
+  version "3.2.4"
+  resolved "https://registry.yarnpkg.com/d3-array/-/d3-array-3.2.4.tgz#15fec33b237f97ac5d7c986dc77da273a8ed0bb5"
+  integrity sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==
+  dependencies:
+    internmap "1 - 2"
+
+"d3-color@1 - 3":
+  version "3.1.0"
+  resolved "https://registry.yarnpkg.com/d3-color/-/d3-color-3.1.0.tgz#395b2833dfac71507f12ac2f7af23bf819de24e2"
+  integrity sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==
+
+d3-ease@^3.0.1:
+  version "3.0.1"
+  resolved "https://registry.yarnpkg.com/d3-ease/-/d3-ease-3.0.1.tgz#9658ac38a2140d59d346160f1f6c30fda0bd12f4"
+  integrity sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==
+
+"d3-format@1 - 3":
+  version "3.1.0"
+  resolved "https://registry.yarnpkg.com/d3-format/-/d3-format-3.1.0.tgz#9260e23a28ea5cb109e93b21a06e24e2ebd55641"
+  integrity sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==
+
+"d3-interpolate@1.2.0 - 3", d3-interpolate@^3.0.1:
+  version "3.0.1"
+  resolved "https://registry.yarnpkg.com/d3-interpolate/-/d3-interpolate-3.0.1.tgz#3c47aa5b32c5b3dfb56ef3fd4342078a632b400d"
+  integrity sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==
+  dependencies:
+    d3-color "1 - 3"
+
+d3-path@^3.1.0:
+  version "3.1.0"
+  resolved "https://registry.yarnpkg.com/d3-path/-/d3-path-3.1.0.tgz#22df939032fb5a71ae8b1800d61ddb7851c42526"
+  integrity sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==
+
+d3-scale@^4.0.2:
+  version "4.0.2"
+  resolved "https://registry.yarnpkg.com/d3-scale/-/d3-scale-4.0.2.tgz#82b38e8e8ff7080764f8dcec77bd4be393689396"
+  integrity sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==
+  dependencies:
+    d3-array "2.10.0 - 3"
+    d3-format "1 - 3"
+    d3-interpolate "1.2.0 - 3"
+    d3-time "2.1.1 - 3"
+    d3-time-format "2 - 4"
+
+d3-shape@^3.1.0:
+  version "3.2.0"
+  resolved "https://registry.yarnpkg.com/d3-shape/-/d3-shape-3.2.0.tgz#a1a839cbd9ba45f28674c69d7f855bcf91dfc6a5"
+  integrity sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==
+  dependencies:
+    d3-path "^3.1.0"
+
+"d3-time-format@2 - 4":
+  version "4.1.0"
+  resolved "https://registry.yarnpkg.com/d3-time-format/-/d3-time-format-4.1.0.tgz#7ab5257a5041d11ecb4fe70a5c7d16a195bb408a"
+  integrity sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==
+  dependencies:
+    d3-time "1 - 3"
+
+"d3-time@1 - 3", "d3-time@2.1.1 - 3", d3-time@^3.0.0:
+  version "3.1.0"
+  resolved "https://registry.yarnpkg.com/d3-time/-/d3-time-3.1.0.tgz#9310db56e992e3c0175e1ef385e545e48a9bb5c7"
+  integrity sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==
+  dependencies:
+    d3-array "2 - 3"
+
+d3-timer@^3.0.1:
+  version "3.0.1"
+  resolved "https://registry.yarnpkg.com/d3-timer/-/d3-timer-3.0.1.tgz#6284d2a2708285b1abb7e201eda4380af35e63b0"
+  integrity sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==
+
 damerau-levenshtein@^1.0.8:
   version "1.0.8"
   resolved "https://registry.yarnpkg.com/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz#b43d286ccbd36bc5b2f7ed41caf2d0aba1f8a6e7"
@@ -3715,6 +3869,11 @@ dataloader@^2.2.2:
   resolved "https://registry.yarnpkg.com/dataloader/-/dataloader-2.2.2.tgz#216dc509b5abe39d43a9b9d97e6e5e473dfbe3e0"
   integrity sha512-8YnDaaf7N3k/q5HnTJVuzSyLETjoZjVmHc4AeKAzOvKHEFQKcn64OKBfzHYtE9zGjctNM7V9I0MfnUVLpi7M5g==
 
+date-fns@^3.2.0, date-fns@^3.6.0:
+  version "3.6.0"
+  resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-3.6.0.tgz#f20ca4fe94f8b754951b24240676e8618c0206bf"
+  integrity sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==
+
 debounce@^1.2.0:
   version "1.2.1"
   resolved "https://registry.yarnpkg.com/debounce/-/debounce-1.2.1.tgz#38881d8f4166a5c5848020c11827b834bcb3e0a5"
@@ -3739,6 +3898,11 @@ decamelize@^1.2.0:
   resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
   integrity sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==
 
+decimal.js-light@^2.4.1:
+  version "2.5.1"
+  resolved "https://registry.yarnpkg.com/decimal.js-light/-/decimal.js-light-2.5.1.tgz#134fd32508f19e208f4fb2f8dac0d2626a867934"
+  integrity sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==
+
 decode-named-character-reference@^1.0.0:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/decode-named-character-reference/-/decode-named-character-reference-1.0.2.tgz#daabac9690874c394c81e4162a0304b35d824f0e"
@@ -3859,6 +4023,14 @@ doctrine@^3.0.0:
   dependencies:
     esutils "^2.0.2"
 
+dom-helpers@^5.0.1:
+  version "5.2.1"
+  resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-5.2.1.tgz#d9400536b2bf8225ad98fe052e029451ac40e902"
+  integrity sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==
+  dependencies:
+    "@babel/runtime" "^7.8.7"
+    csstype "^3.0.2"
+
 dot-case@^3.0.4:
   version "3.0.4"
   resolved "https://registry.yarnpkg.com/dot-case/-/dot-case-3.0.4.tgz#9b2b670d00a431667a8a75ba29cd1b98809ce751"
@@ -4285,6 +4457,11 @@ event-target-shim@^5.0.0:
   resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789"
   integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==
 
+eventemitter3@^4.0.1:
+  version "4.0.7"
+  resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f"
+  integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==
+
 eventsource-parser@1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/eventsource-parser/-/eventsource-parser-1.0.0.tgz#6332e37fd5512e3c8d9df05773b2bf9e152ccc04"
@@ -4319,6 +4496,11 @@ fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3:
   resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
   integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==
 
+fast-equals@^5.0.1:
+  version "5.0.1"
+  resolved "https://registry.yarnpkg.com/fast-equals/-/fast-equals-5.0.1.tgz#a4eefe3c5d1c0d021aeed0bc10ba5e0c12ee405d"
+  integrity sha512-WF1Wi8PwwSY7/6Kx0vKXtw8RwuSGoM1bvDaJbu7MxDlR1vovZjIAKrnzyrThgAjm6JDTu0fVgWXDlMGspodfoQ==
+
 fast-glob@^3.2.12, fast-glob@^3.2.5, fast-glob@^3.2.9, fast-glob@^3.3.1:
   version "3.3.1"
   resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.1.tgz#784b4e897340f3dbbef17413b3f11acf03c874c4"
@@ -5025,6 +5207,11 @@ internal-slot@^1.0.5:
     has "^1.0.3"
     side-channel "^1.0.4"
 
+"internmap@1 - 2":
+  version "2.0.3"
+  resolved "https://registry.yarnpkg.com/internmap/-/internmap-2.0.3.tgz#6685f23755e43c524e251d29cbc97248e3061009"
+  integrity sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==
+
 invariant@^2.2.4:
   version "2.2.4"
   resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6"
@@ -5634,6 +5821,11 @@ lru-cache@^6.0.0:
   dependencies:
     yallist "^4.0.0"
 
+lucide-react@^0.365.0:
+  version "0.365.0"
+  resolved "https://registry.yarnpkg.com/lucide-react/-/lucide-react-0.365.0.tgz#aa15b62e004bf35d557451f4b3aed2f6da5e2d84"
+  integrity sha512-sJYpPyyzGHI4B3pys+XSFnE4qtSWc68rFnDLxbNNKjkLST5XSx9DNn5+1Z3eFgFiw39PphNRiVBSVb+AL3oKwA==
+
 map-cache@^0.2.0:
   version "0.2.2"
   resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf"
@@ -6322,6 +6514,11 @@ nullthrows@^1.1.1:
   resolved "https://registry.yarnpkg.com/nullthrows/-/nullthrows-1.1.1.tgz#7818258843856ae971eae4208ad7d7eb19a431b1"
   integrity sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw==
 
+numeral@^2.0.6:
+  version "2.0.6"
+  resolved "https://registry.yarnpkg.com/numeral/-/numeral-2.0.6.tgz#4ad080936d443c2561aed9f2197efffe25f4e506"
+  integrity sha512-qaKRmtYPZ5qdw4jWJD6bxEf1FJEqllJrwxCLIm0sQU/A7v2/czigzOb+C2uSiFsa9lBUzeH7M1oK+Q+OLxL3kA==
+
 object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1:
   version "4.1.1"
   resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
@@ -6771,7 +6968,7 @@ promise@^7.1.1:
   dependencies:
     asap "~2.0.3"
 
-prop-types@^15.0.0, prop-types@^15.7.2, prop-types@^15.8.1:
+prop-types@^15.0.0, prop-types@^15.6.2, prop-types@^15.7.2, prop-types@^15.8.1:
   version "15.8.1"
   resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5"
   integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==
@@ -6819,6 +7016,20 @@ queue-microtask@^1.2.2:
   resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243"
   integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==
 
+react-activity-calendar@^2.2.8:
+  version "2.2.8"
+  resolved "https://registry.yarnpkg.com/react-activity-calendar/-/react-activity-calendar-2.2.8.tgz#8742d607588f1b17d094fd09c96832b4f781beb2"
+  integrity sha512-+Zr6UlPC4zLMSkXyg/+bQ0Fd10E8V+nT+AU0FHfCIQi6/dLJe73cR+CkFFf8zPE59XhGO24U7FfUyFgbWqV0Bg==
+  dependencies:
+    "@types/chroma-js" "^2.4.3"
+    chroma-js "^2.4.2"
+    date-fns "^3.2.0"
+
+react-day-picker@^8.10.0:
+  version "8.10.0"
+  resolved "https://registry.yarnpkg.com/react-day-picker/-/react-day-picker-8.10.0.tgz#729c5b9564967a924213978fb9c0751884a60595"
+  integrity sha512-mz+qeyrOM7++1NCb1ARXmkjMkzWVh2GL9YiPbRjKe0zHccvekk4HE+0MPOZOrosn8r8zTHIIeOUXTmXRqmkRmg==
+
 react-dom@^18.2.0:
   version "18.2.0"
   resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.2.0.tgz#22aaf38708db2674ed9ada224ca4aa708d821e3d"
@@ -6837,7 +7048,7 @@ react-intersection-observer@^9.4.4:
   resolved "https://registry.yarnpkg.com/react-intersection-observer/-/react-intersection-observer-9.5.2.tgz#f68363a1ff292323c0808201b58134307a1626d0"
   integrity sha512-EmoV66/yvksJcGa1rdW0nDNc4I1RifDWkT50gXSFnPLYQ4xUptuDD4V7k+Rj1OgVAlww628KLGcxPXFlOkkU/Q==
 
-react-is@^16.13.1:
+react-is@^16.10.2, react-is@^16.13.1:
   version "16.13.1"
   resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
   integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
@@ -6901,6 +7112,15 @@ react-resizable-panels@^1.0.7:
   resolved "https://registry.yarnpkg.com/react-resizable-panels/-/react-resizable-panels-1.0.7.tgz#f48b4dd42f143bde01a0441580578806fadc16eb"
   integrity sha512-CluJkHQheeNqIJly2FYDfri3ME+2h2nCXpf0Y+hTO1K1eVtNxXFA5hVp5cUD6NS70iiufswOmnku9QZiLr1hYg==
 
+react-smooth@^4.0.0:
+  version "4.0.1"
+  resolved "https://registry.yarnpkg.com/react-smooth/-/react-smooth-4.0.1.tgz#6200d8699bfe051ae40ba187988323b1449eab1a"
+  integrity sha512-OE4hm7XqR0jNOq3Qmk9mFLyd6p2+j6bvbPJ7qlB7+oo0eNcL2l7WQzG6MBnT3EXY6xzkLMUBec3AfewJdA0J8w==
+  dependencies:
+    fast-equals "^5.0.1"
+    prop-types "^15.8.1"
+    react-transition-group "^4.4.5"
+
 react-style-singleton@^2.2.1:
   version "2.2.1"
   resolved "https://registry.yarnpkg.com/react-style-singleton/-/react-style-singleton-2.2.1.tgz#f99e420492b2d8f34d38308ff660b60d0b1205b4"
@@ -6937,6 +7157,16 @@ react-topbar-progress-indicator@^4.1.1:
   dependencies:
     topbar "^0.1.3"
 
+react-transition-group@^4.4.5:
+  version "4.4.5"
+  resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.5.tgz#e53d4e3f3344da8521489fbef8f2581d42becdd1"
+  integrity sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==
+  dependencies:
+    "@babel/runtime" "^7.5.5"
+    dom-helpers "^5.0.1"
+    loose-envify "^1.4.0"
+    prop-types "^15.6.2"
+
 react@^18.2.0:
   version "18.2.0"
   resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5"
@@ -6976,6 +7206,27 @@ readdirp@~3.6.0:
   dependencies:
     picomatch "^2.2.1"
 
+recharts-scale@^0.4.4:
+  version "0.4.5"
+  resolved "https://registry.yarnpkg.com/recharts-scale/-/recharts-scale-0.4.5.tgz#0969271f14e732e642fcc5bd4ab270d6e87dd1d9"
+  integrity sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w==
+  dependencies:
+    decimal.js-light "^2.4.1"
+
+recharts@^2.12.4:
+  version "2.12.4"
+  resolved "https://registry.yarnpkg.com/recharts/-/recharts-2.12.4.tgz#e560a57cd44ab554c99a0d93bdd58d059b309a2e"
+  integrity sha512-dM4skmk4fDKEDjL9MNunxv6zcTxePGVEzRnLDXALRpfJ85JoQ0P0APJ/CoJlmnQI0gPjBlOkjzrwrfQrRST3KA==
+  dependencies:
+    clsx "^2.0.0"
+    eventemitter3 "^4.0.1"
+    lodash "^4.17.21"
+    react-is "^16.10.2"
+    react-smooth "^4.0.0"
+    recharts-scale "^0.4.4"
+    tiny-invariant "^1.3.1"
+    victory-vendor "^36.6.8"
+
 reflect.getprototypeof@^1.0.4:
   version "1.0.4"
   resolved "https://registry.yarnpkg.com/reflect.getprototypeof/-/reflect.getprototypeof-1.0.4.tgz#aaccbf41aca3821b87bb71d9dcbc7ad0ba50a3f3"
@@ -7723,6 +7974,11 @@ tiny-inflate@^1.0.0:
   resolved "https://registry.yarnpkg.com/tiny-inflate/-/tiny-inflate-1.0.3.tgz#122715494913a1805166aaf7c93467933eea26c4"
   integrity sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==
 
+tiny-invariant@^1.3.1:
+  version "1.3.3"
+  resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.3.3.tgz#46680b7a873a0d5d10005995eb90a70d74d60127"
+  integrity sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==
+
 title-case@^3.0.3:
   version "3.0.3"
   resolved "https://registry.yarnpkg.com/title-case/-/title-case-3.0.3.tgz#bc689b46f02e411f1d1e1d081f7c3deca0489982"
@@ -8123,6 +8379,26 @@ vfile@^5.0.0:
     unist-util-stringify-position "^3.0.0"
     vfile-message "^3.0.0"
 
+victory-vendor@^36.6.8:
+  version "36.9.2"
+  resolved "https://registry.yarnpkg.com/victory-vendor/-/victory-vendor-36.9.2.tgz#668b02a448fa4ea0f788dbf4228b7e64669ff801"
+  integrity sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ==
+  dependencies:
+    "@types/d3-array" "^3.0.3"
+    "@types/d3-ease" "^3.0.0"
+    "@types/d3-interpolate" "^3.0.1"
+    "@types/d3-scale" "^4.0.2"
+    "@types/d3-shape" "^3.1.0"
+    "@types/d3-time" "^3.0.0"
+    "@types/d3-timer" "^3.0.0"
+    d3-array "^3.1.6"
+    d3-ease "^3.0.1"
+    d3-interpolate "^3.0.1"
+    d3-scale "^4.0.2"
+    d3-shape "^3.1.0"
+    d3-time "^3.0.0"
+    d3-timer "^3.0.1"
+
 w3c-keyname@^2.2.4:
   version "2.2.8"
   resolved "https://registry.yarnpkg.com/w3c-keyname/-/w3c-keyname-2.2.8.tgz#7b17c8c6883d4e8b86ac8aba79d39e880f8869c5"

From 817b79612989b7a9b0b63111a8a8f5ee42e54512 Mon Sep 17 00:00:00 2001
From: wwayne <wayne.wang0821@gmail.com>
Date: Sun, 7 Apr 2024 14:22:08 +0800
Subject: [PATCH 02/16] polishment

---
 .../app/(dashboard)/analytics/components/analytic.tsx      | 1 -
 .../(dashboard)/analytics/components/analyticAccptance.tsx | 7 +++++--
 ee/tabby-ui/app/(dashboard)/analytics/page.tsx             | 4 ++--
 ee/tabby-ui/app/(dashboard)/components/sidebar.tsx         | 4 ++--
 4 files changed, 9 insertions(+), 7 deletions(-)

diff --git a/ee/tabby-ui/app/(dashboard)/analytics/components/analytic.tsx b/ee/tabby-ui/app/(dashboard)/analytics/components/analytic.tsx
index 079b699ba98a..bdd5ec502d0c 100644
--- a/ee/tabby-ui/app/(dashboard)/analytics/components/analytic.tsx
+++ b/ee/tabby-ui/app/(dashboard)/analytics/components/analytic.tsx
@@ -84,7 +84,6 @@ export function Analytic() {
     completions: item.completions,
     selects: item.selects,
   }))
-  console.log(dailyStats)
 
   // Query yearly stats
   const [{ data: yearlyStatsData, fetching: fetchingYearlyStats }] = useQuery({
diff --git a/ee/tabby-ui/app/(dashboard)/analytics/components/analyticAccptance.tsx b/ee/tabby-ui/app/(dashboard)/analytics/components/analyticAccptance.tsx
index 7bd0793741c4..a4ee4b858ff0 100644
--- a/ee/tabby-ui/app/(dashboard)/analytics/components/analyticAccptance.tsx
+++ b/ee/tabby-ui/app/(dashboard)/analytics/components/analyticAccptance.tsx
@@ -1,3 +1,6 @@
+
+'use client'
+
 import moment from "moment";
 
 import type { DateRange } from "react-day-picker"
@@ -78,7 +81,7 @@ export function AnlyticAcceptance ({
 			<p className="mt-0.5 text-xs text-muted-foreground">
 				{moment(from).format('D MMM, YYYY')} - {moment(to).format('D MMM, YYYY')}
 			</p>
-			<ResponsiveContainer width="100%" height={250}>
+			<ResponsiveContainer width="100%" height={210}>
 				<PieChart width={700} height={400}>
 					<Pie
 						data={data}
@@ -94,7 +97,7 @@ export function AnlyticAcceptance ({
 							<Cell key={`cell-${index}`} fill={COLORS[index % COLORS.length]} />
 						))}
 					</Pie>
-					{totalCompletions !== 0 && <Legend />}
+					{totalCompletions !== 0 && <Legend wrapperStyle={{ fontSize: '12px' }} />}
 				</PieChart>
 			</ResponsiveContainer>
 		</div>
diff --git a/ee/tabby-ui/app/(dashboard)/analytics/page.tsx b/ee/tabby-ui/app/(dashboard)/analytics/page.tsx
index 98b0c6afc3c9..93be631a3150 100644
--- a/ee/tabby-ui/app/(dashboard)/analytics/page.tsx
+++ b/ee/tabby-ui/app/(dashboard)/analytics/page.tsx
@@ -1,9 +1,9 @@
 import { Metadata } from 'next'
 
-import { Analytic } from './components/analyticAccptance'
+import { Analytic } from './components/analytic'
 
 export const metadata: Metadata = {
-  title: 'Analytic dashboard'
+  title: 'Analytics Dashboard'
 }
 
 export default function Page() {
diff --git a/ee/tabby-ui/app/(dashboard)/components/sidebar.tsx b/ee/tabby-ui/app/(dashboard)/components/sidebar.tsx
index 6c742c72ccb5..e2d6fd34b3d2 100644
--- a/ee/tabby-ui/app/(dashboard)/components/sidebar.tsx
+++ b/ee/tabby-ui/app/(dashboard)/components/sidebar.tsx
@@ -71,9 +71,9 @@ export default function Sidebar({ children, className }: SidebarProps) {
                     <IconScrollText />
                     Jobs
                   </SidebarButton>
-                  <SidebarButton href="/analytic">
+                  <SidebarButton href="/analytics">
                     <IconBarChart />
-                    Analytic
+                    Analytics
                   </SidebarButton>
                   <SidebarCollapsible
                     title={

From 309abe415cb81649a0079ca853c4e28989cf9944 Mon Sep 17 00:00:00 2001
From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com>
Date: Sun, 7 Apr 2024 06:23:17 +0000
Subject: [PATCH 03/16] [autofix.ci] apply automated fixes

---
 .../analytics/components/analytic.tsx         | 137 +++++++------
 .../components/analyticAccptance.tsx          | 185 +++++++++---------
 .../components/analyticDailyCompletion.tsx    | 151 +++++++-------
 .../components/analyticYearlyCompletion.tsx   |  50 ++---
 .../app/(dashboard)/analytics/page.tsx        |   2 +-
 .../app/(dashboard)/analytics/types/stats.ts  |  10 +-
 .../app/(dashboard)/components/sidebar.tsx    |   4 +-
 ee/tabby-ui/components/activity-calendar.tsx  |  34 ++--
 ee/tabby-ui/components/date-range-picker.tsx  |  46 ++---
 ee/tabby-ui/components/ui/calendar.tsx        |  68 +++----
 ee/tabby-ui/lib/hooks/use-all-members.tsx     |  12 +-
 ee/tabby-ui/lib/tabby/query.ts                |  10 +-
 12 files changed, 371 insertions(+), 338 deletions(-)

diff --git a/ee/tabby-ui/app/(dashboard)/analytics/components/analytic.tsx b/ee/tabby-ui/app/(dashboard)/analytics/components/analytic.tsx
index bdd5ec502d0c..036989e0e5fa 100644
--- a/ee/tabby-ui/app/(dashboard)/analytics/components/analytic.tsx
+++ b/ee/tabby-ui/app/(dashboard)/analytics/components/analytic.tsx
@@ -1,58 +1,63 @@
 'use client'
 
 import { useState } from 'react'
+import { sum } from 'lodash-es'
 import moment from 'moment'
+import numeral from 'numeral'
+import { DateRange } from 'react-day-picker'
 import { useQuery } from 'urql'
-import { DateRange } from "react-day-picker"
-import { sum } from "lodash-es"
-import numeral from "numeral"
 
-import { queryDailyStatsInPastYear, queryDailyStats } from '@/lib/tabby/query'
 import { Language } from '@/lib/gql/generates/graphql'
 import { useAllMembers } from '@/lib/hooks/use-all-members'
-
+import { queryDailyStats, queryDailyStatsInPastYear } from '@/lib/tabby/query'
 import {
   Select,
   SelectContent,
   SelectGroup,
   SelectItem,
   SelectTrigger,
-  SelectValue,
-} from "@/components/ui/select"
+  SelectValue
+} from '@/components/ui/select'
 import { Skeleton } from '@/components/ui/skeleton'
 import DatePickerWithRange from '@/components/date-range-picker'
 import LoadingWrapper from '@/components/loading-wrapper'
-import { AnalyticDailyCompletion } from './analyticDailyCompletion'
-import { AnlyticAcceptance } from './analyticAccptance'
-import { AnalyticYearlyCompletion } from './analyticYearlyCompletion'
 
 import type { DailyStats } from '../types/stats'
+import { AnlyticAcceptance } from './analyticAccptance'
+import { AnalyticDailyCompletion } from './analyticDailyCompletion'
+import { AnalyticYearlyCompletion } from './analyticYearlyCompletion'
 
 const INITIAL_DATE_RANGE = 14
 const KEY_SELECT_ALL = 'all'
 
 function AnalyticSummary({
-  dailyStats,
+  dailyStats
 }: {
-  dailyStats: DailyStats[] | undefined,
+  dailyStats: DailyStats[] | undefined
 }) {
   const totalCompletions = sum(dailyStats?.map(stats => stats.completions))
   return (
     <div className="flex items-center justify-center space-x-4 xl:justify-start">
       <div className="space-y-0.5 rounded-lg border bg-primary-foreground/30 p-4 lg:min-w-[250px]">
-        <p className="text-xs text-muted-foreground lg:text-sm">Total completions</p>
+        <p className="text-xs text-muted-foreground lg:text-sm">
+          Total completions
+        </p>
         <p className="font-bold lg:text-3xl">
           {numeral(totalCompletions).format('0,0')}
         </p>
       </div>
 
       <div className="space-y-0.5 rounded-lg border bg-primary-foreground/30 p-4 lg:min-w-[250px]">
-        <p className="text-xs text-muted-foreground lg:text-sm">Minutes saved / completion</p>
+        <p className="text-xs text-muted-foreground lg:text-sm">
+          Minutes saved / completion
+        </p>
         <p className="font-bold lg:text-3xl">2</p>
       </div>
 
       <div className="space-y-0.5 rounded-lg border bg-primary-foreground/30 p-4 lg:min-w-[250px]">
-        <p className="text-xs text-muted-foreground lg:text-sm">Hours saved in total</p>
+        <p className="text-xs text-muted-foreground lg:text-sm">
+          Hours saved in total
+        </p>
         <p className="font-bold lg:text-3xl">100</p>
       </div>
     </div>
@@ -66,7 +71,9 @@ export function Analytic() {
     to: moment().toDate()
   })
   const [selectedMember, setSelectedMember] = useState(KEY_SELECT_ALL)
-  const [selectedLanguage, setSelectedLanguage] = useState<'all' | Language>(KEY_SELECT_ALL)
+  const [selectedLanguage, setSelectedLanguage] = useState<'all' | Language>(
+    KEY_SELECT_ALL
+  )
 
   // Query stats of selected date range
   const [{ data: dailyStatsData, fetching: fetchingDailyState }] = useQuery({
@@ -75,15 +82,18 @@ export function Analytic() {
       start: moment(dateRange.from).startOf('day').utc().format(),
       end: moment(dateRange.to).endOf('day').utc().format(),
       users: selectedMember === KEY_SELECT_ALL ? undefined : [selectedMember],
-      languages: selectedLanguage === KEY_SELECT_ALL ? undefined : [selectedLanguage],
+      languages:
+        selectedLanguage === KEY_SELECT_ALL ? undefined : [selectedLanguage]
     }
   })
-  const dailyStats: DailyStats[] | undefined = dailyStatsData?.dailyStats.map(item => ({
-    start: item.start,
-    end: item.end,
-    completions: item.completions,
-    selects: item.selects,
-  }))
+  const dailyStats: DailyStats[] | undefined = dailyStatsData?.dailyStats.map(
+    item => ({
+      start: item.start,
+      end: item.end,
+      completions: item.completions,
+      selects: item.selects
+    })
+  )
 
   // Query yearly stats
   const [{ data: yearlyStatsData, fetching: fetchingYearlyStats }] = useQuery({
@@ -92,14 +102,18 @@ export function Analytic() {
       users: selectedMember === KEY_SELECT_ALL ? undefined : selectedMember
     }
   })
-  const yearlyStats: DailyStats[] | undefined = yearlyStatsData?.dailyStatsInPastYear.map(item => ({
-    start: item.start,
-    end: item.end,
-    completions: item.completions,
-    selects: item.selects,
-  }))
-
-  const onDateOpenChange = (isOpen: boolean, dateRange: DateRange | undefined) => {
+  const yearlyStats: DailyStats[] | undefined =
+    yearlyStatsData?.dailyStatsInPastYear.map(item => ({
+      start: item.start,
+      end: item.end,
+      completions: item.completions,
+      selects: item.selects
+    }))
+
+  const onDateOpenChange = (
+    isOpen: boolean,
+    dateRange: DateRange | undefined
+  ) => {
     if (!isOpen) {
       if (dateRange) {
         setDateRange(dateRange)
@@ -114,16 +128,19 @@ export function Analytic() {
           <h1 className="mb-1.5 scroll-m-20 text-center text-4xl font-extrabold tracking-tight lg:text-5xl xl:text-left">
             Analytics
           </h1>
-          <p className="text-muted-foreground">Overview of code completion usage</p>
+          <p className="text-muted-foreground">
+            Overview of code completion usage
+          </p>
         </div>
 
         <div className="flex flex-col items-center gap-y-2 lg:flex-row lg:gap-y-0 lg:space-x-4">
-          <Select defaultValue={KEY_SELECT_ALL} onValueChange={setSelectedMember}>
-            <SelectTrigger className="w-[300px] lg:w-[180px]" >
+          <Select
+            defaultValue={KEY_SELECT_ALL}
+            onValueChange={setSelectedMember}
+          >
+            <SelectTrigger className="w-[300px] lg:w-[180px]">
               <div className="flex w-full items-center truncate ">
-                <span className="mr-1.5 text-muted-foreground">
-                  Member:
-                </span>
+                <span className="mr-1.5 text-muted-foreground">Member:</span>
                 <div className="overflow-hidden text-ellipsis">
                   <SelectValue />
                 </div>
@@ -133,18 +150,23 @@ export function Analytic() {
               <SelectGroup>
                 <SelectItem value={KEY_SELECT_ALL}>All</SelectItem>
                 {members.map(member => (
-                  <SelectItem value={member.id} key={member.id}>{member.email}</SelectItem>
+                  <SelectItem value={member.id} key={member.id}>
+                    {member.email}
+                  </SelectItem>
                 ))}
               </SelectGroup>
             </SelectContent>
           </Select>
 
-          <Select defaultValue={KEY_SELECT_ALL} onValueChange={(value: 'all' | Language) => setSelectedLanguage(value)}>
-            <SelectTrigger className="w-[300px] lg:w-[180px]" >
+          <Select
+            defaultValue={KEY_SELECT_ALL}
+            onValueChange={(value: 'all' | Language) =>
+              setSelectedLanguage(value)
+            }
+          >
+            <SelectTrigger className="w-[300px] lg:w-[180px]">
               <div className="flex w-full items-center truncate">
-                <span className="mr-1.5 text-muted-foreground">
-                  Language:
-                </span>
+                <span className="mr-1.5 text-muted-foreground">Language:</span>
                 <div className="overflow-hidden text-ellipsis">
                   <SelectValue />
                 </div>
@@ -154,7 +176,9 @@ export function Analytic() {
               <SelectGroup>
                 <SelectItem value={'all'}>All</SelectItem>
                 {Object.entries(Language).map(([key, value]) => (
-                  <SelectItem key={value} value={value}>{key}</SelectItem>
+                  <SelectItem key={value} value={value}>
+                    {key}
+                  </SelectItem>
                 ))}
               </SelectGroup>
             </SelectContent>
@@ -164,43 +188,46 @@ export function Analytic() {
             buttonClassName="h-full"
             contentAlign="end"
             dateRange={dateRange}
-            onOpenChange={onDateOpenChange} />
+            onOpenChange={onDateOpenChange}
+          />
         </div>
       </div>
 
       <LoadingWrapper
         loading={fetchingDailyState}
-        fallback={<Skeleton className="h-24 w-1/2" />}>
+        fallback={<Skeleton className="h-24 w-1/2" />}
+      >
         <AnalyticSummary dailyStats={dailyStats} />
       </LoadingWrapper>
 
       <LoadingWrapper
         loading={fetchingDailyState}
-        fallback={<Skeleton className="h-64 w-full" />}>
+        fallback={<Skeleton className="h-64 w-full" />}
+      >
         <AnalyticDailyCompletion
           dailyStats={dailyStats}
-          dateRange={dateRange} />
+          dateRange={dateRange}
+        />
       </LoadingWrapper>
 
-
       <div className="flex flex-col gap-y-6 xl:flex-row xl:gap-x-6 xl:gap-y-0">
         <div className="flex-1">
           <LoadingWrapper
             loading={fetchingDailyState}
-            fallback={<Skeleton className="h-64 w-full" />}>
-            <AnlyticAcceptance
-              dailyStats={dailyStats}
-              dateRange={dateRange} />
+            fallback={<Skeleton className="h-64 w-full" />}
+          >
+            <AnlyticAcceptance dailyStats={dailyStats} dateRange={dateRange} />
           </LoadingWrapper>
         </div>
         <div style={{ flex: 3 }}>
           <LoadingWrapper
             loading={fetchingDailyState || fetchingYearlyStats}
-            fallback={<Skeleton className="h-64 w-full" />}>
+            fallback={<Skeleton className="h-64 w-full" />}
+          >
             <AnalyticYearlyCompletion yearlyStats={yearlyStats} />
           </LoadingWrapper>
         </div>
       </div>
     </div>
   )
-}
\ No newline at end of file
+}
diff --git a/ee/tabby-ui/app/(dashboard)/analytics/components/analyticAccptance.tsx b/ee/tabby-ui/app/(dashboard)/analytics/components/analyticAccptance.tsx
index a4ee4b858ff0..61e79f072a42 100644
--- a/ee/tabby-ui/app/(dashboard)/analytics/components/analyticAccptance.tsx
+++ b/ee/tabby-ui/app/(dashboard)/analytics/components/analyticAccptance.tsx
@@ -1,105 +1,106 @@
-
 'use client'
 
-import moment from "moment";
+import moment from 'moment'
+import type { DateRange } from 'react-day-picker'
+import { Cell, Legend, Pie, PieChart, ResponsiveContainer } from 'recharts'
 
-import type { DateRange } from "react-day-picker"
 import type { DailyStats } from '../types/stats'
 
-import {
-  Legend,
-  ResponsiveContainer,
-  PieChart, Pie, Cell,
-} from 'recharts'
-
-export function AnlyticAcceptance ({
-	dailyStats,
-	dateRange,
+export function AnlyticAcceptance({
+  dailyStats,
+  dateRange
 }: {
-	dailyStats: DailyStats[] | undefined;
-	dateRange: DateRange;
+  dailyStats: DailyStats[] | undefined
+  dateRange: DateRange
 }) {
-	const from = dateRange.from || new Date()
-	const to = dateRange.to || from
+  const from = dateRange.from || new Date()
+  const to = dateRange.to || from
 
-	let totalAccpet = 0
-	let totalCompletions = 0
-	dailyStats?.forEach(stats => {
-		totalCompletions += stats.completions
-		totalAccpet += stats.selects
-	})
-	const totalPending = totalCompletions - totalAccpet
+  let totalAccpet = 0
+  let totalCompletions = 0
+  dailyStats?.forEach(stats => {
+    totalCompletions += stats.completions
+    totalAccpet += stats.selects
+  })
+  const totalPending = totalCompletions - totalAccpet
 
-	const data = [
-		{ name: 'Accept', value: totalAccpet},
-		{ name: 'Pending', value: totalPending},
-	];
+  const data = [
+    { name: 'Accept', value: totalAccpet },
+    { name: 'Pending', value: totalPending }
+  ]
 
-	const COLORS = ['#8884d8', '#b9b7e2'];
+  const COLORS = ['#8884d8', '#b9b7e2']
 
-	const RADIAN = Math.PI / 180;
-	const renderCustomizedLabel = ({
-		cx,
-		cy,
-		midAngle,
-		innerRadius,
-		outerRadius,
-		percent,
-		name
-	}: {
-		cx: number;
-		cy: number;
-		midAngle: number;
-		innerRadius: number;
-		outerRadius: number;
-		percent: number;
-		name: string;
-	}) => {
-		const radius = innerRadius + (outerRadius - innerRadius) * 0.5;
-		const x = cx + radius * Math.cos(-midAngle * RADIAN);
-		const y = cy + radius * Math.sin(-midAngle * RADIAN);
+  const RADIAN = Math.PI / 180
+  const renderCustomizedLabel = ({
+    cx,
+    cy,
+    midAngle,
+    innerRadius,
+    outerRadius,
+    percent,
+    name
+  }: {
+    cx: number
+    cy: number
+    midAngle: number
+    innerRadius: number
+    outerRadius: number
+    percent: number
+    name: string
+  }) => {
+    const radius = innerRadius + (outerRadius - innerRadius) * 0.5
+    const x = cx + radius * Math.cos(-midAngle * RADIAN)
+    const y = cy + radius * Math.sin(-midAngle * RADIAN)
 
-		if (name.toLocaleLowerCase() === 'accept') {
-			return (
-				<text
-					x={x}
-					y={y}
-					fill="white"
-					textAnchor={x > cx ? 'start' : 'end'}
-					dominantBaseline="central"
-					fontSize={12}>
-					{`${(percent * 100).toFixed(0)}%`}
-				</text>
-			);
-		}
-		return
-	};
+    if (name.toLocaleLowerCase() === 'accept') {
+      return (
+        <text
+          x={x}
+          y={y}
+          fill="white"
+          textAnchor={x > cx ? 'start' : 'end'}
+          dominantBaseline="central"
+          fontSize={12}
+        >
+          {`${(percent * 100).toFixed(0)}%`}
+        </text>
+      )
+    }
+    return
+  }
 
-	return (
-		<div className="rounded-lg border bg-primary-foreground/30 p-4">
-			<h1 className="text-xl font-bold">Acceptance</h1>
-			<p className="mt-0.5 text-xs text-muted-foreground">
-				{moment(from).format('D MMM, YYYY')} - {moment(to).format('D MMM, YYYY')}
-			</p>
-			<ResponsiveContainer width="100%" height={210}>
-				<PieChart width={700} height={400}>
-					<Pie
-						data={data}
-						cx="50%"
-						cy="50%"
-						labelLine={false}
-						label={renderCustomizedLabel}
-						outerRadius={80}
-						fill="#8884d8"
-						dataKey="value"
-					>
-						{data.map((entry, index) => (
-							<Cell key={`cell-${index}`} fill={COLORS[index % COLORS.length]} />
-						))}
-					</Pie>
-					{totalCompletions !== 0 && <Legend wrapperStyle={{ fontSize: '12px' }} />}
-				</PieChart>
-			</ResponsiveContainer>
-		</div>
-	)
-}
\ No newline at end of file
+  return (
+    <div className="rounded-lg border bg-primary-foreground/30 p-4">
+      <h1 className="text-xl font-bold">Acceptance</h1>
+      <p className="mt-0.5 text-xs text-muted-foreground">
+        {moment(from).format('D MMM, YYYY')} -{' '}
+        {moment(to).format('D MMM, YYYY')}
+      </p>
+      <ResponsiveContainer width="100%" height={210}>
+        <PieChart width={700} height={400}>
+          <Pie
+            data={data}
+            cx="50%"
+            cy="50%"
+            labelLine={false}
+            label={renderCustomizedLabel}
+            outerRadius={80}
+            fill="#8884d8"
+            dataKey="value"
+          >
+            {data.map((entry, index) => (
+              <Cell
+                key={`cell-${index}`}
+                fill={COLORS[index % COLORS.length]}
+              />
+            ))}
+          </Pie>
+          {totalCompletions !== 0 && (
+            <Legend wrapperStyle={{ fontSize: '12px' }} />
+          )}
+        </PieChart>
+      </ResponsiveContainer>
+    </div>
+  )
+}
diff --git a/ee/tabby-ui/app/(dashboard)/analytics/components/analyticDailyCompletion.tsx b/ee/tabby-ui/app/(dashboard)/analytics/components/analyticDailyCompletion.tsx
index 81d172dddc4f..6490049c0b5b 100644
--- a/ee/tabby-ui/app/(dashboard)/analytics/components/analyticDailyCompletion.tsx
+++ b/ee/tabby-ui/app/(dashboard)/analytics/components/analyticDailyCompletion.tsx
@@ -1,94 +1,99 @@
 import { eachDayOfInterval } from 'date-fns'
 import moment from 'moment'
-
+import type { DateRange } from 'react-day-picker'
 import {
   Bar,
   BarChart,
   ResponsiveContainer,
   Tooltip,
   XAxis,
-  YAxis,
+  YAxis
 } from 'recharts'
+
 import { Card, CardContent } from '@/components/ui/card'
 
-import type { DateRange } from "react-day-picker"
 import type { DailyStats } from '../types/stats'
 
-function BarTooltip ({ active, payload, label }: {
-	active?: boolean;
-	label?: string
-	payload?: {
-		name: string;
-		value: number;
-	}[]
+function BarTooltip({
+  active,
+  payload,
+  label
+}: {
+  active?: boolean
+  label?: string
+  payload?: {
+    name: string
+    value: number
+  }[]
 }) {
-	if (active && payload && payload.length) {
-		const completion = payload[0].value
-		if (!completion) return null;
-		return (
-			<Card>
-				<CardContent className="px-4 py-2 text-sm">
-					<p>Completions: <b>{completion}</b></p>
-					<p className="text-muted-foreground">{label}</p>
-				</CardContent>
-			</Card>
-		);
-	}
+  if (active && payload && payload.length) {
+    const completion = payload[0].value
+    if (!completion) return null
+    return (
+      <Card>
+        <CardContent className="px-4 py-2 text-sm">
+          <p>
+            Completions: <b>{completion}</b>
+          </p>
+          <p className="text-muted-foreground">{label}</p>
+        </CardContent>
+      </Card>
+    )
+  }
 
-	return null;
-};
+  return null
+}
 
-export function AnalyticDailyCompletion ({
-	dailyStats,
-	dateRange,
+export function AnalyticDailyCompletion({
+  dailyStats,
+  dateRange
 }: {
-	dailyStats: DailyStats[] | undefined;
-	dateRange: DateRange;
+  dailyStats: DailyStats[] | undefined
+  dateRange: DateRange
 }) {
-	const from = dateRange.from || new Date()
-	const to = dateRange.to || from
+  const from = dateRange.from || new Date()
+  const to = dateRange.to || from
 
-	const dailyCompletionMap: Record<string, number> = dailyStats?.reduce((acc, cur) => {
-		const date = moment(cur.start).format('YYYY-MM-DD')
-		return { ...acc, [date]: cur.completions }
-	}, {}) || {}
+  const dailyCompletionMap: Record<string, number> =
+    dailyStats?.reduce((acc, cur) => {
+      const date = moment(cur.start).format('YYYY-MM-DD')
+      return { ...acc, [date]: cur.completions }
+    }, {}) || {}
 
-	const daysBetweenRange = eachDayOfInterval({
-		start: from,
-		end: to
-	});
+  const daysBetweenRange = eachDayOfInterval({
+    start: from,
+    end: to
+  })
 
-	const chartData = daysBetweenRange.map(date => {
-		const completionKey = moment(date).format('YYYY-MM-DD')
-		const value = dailyCompletionMap[completionKey] || 0
-		return {
-			name: moment(date).format('D MMM'),
-			value
-		}
-	})
-	return (
-		<div className="rounded-lg border bg-primary-foreground/30 p-4">
-			<h1 className="mb-5 text-xl font-bold">Completions</h1>
-			<ResponsiveContainer width="100%" height={350}>
-				<BarChart
-					width={500}
-					height={300}
-					data={chartData}
-					margin={{
-						top: 5,
-						right: 20,
-						left: 20,
-						bottom: 5
-					}}
-				>
-					<Bar dataKey="value" fill="#8884d8" />
-					<XAxis dataKey="name" fontSize={12} />
-					<YAxis fontSize={12} />
-					<Tooltip
-						cursor={{ fill: 'transparent' }}
-						content={<BarTooltip />} />
-				</BarChart>
-			</ResponsiveContainer>
-		</div>
-	)
-}
\ No newline at end of file
+  const chartData = daysBetweenRange.map(date => {
+    const completionKey = moment(date).format('YYYY-MM-DD')
+    const value = dailyCompletionMap[completionKey] || 0
+    return {
+      name: moment(date).format('D MMM'),
+      value
+    }
+  })
+  return (
+    <div className="rounded-lg border bg-primary-foreground/30 p-4">
+      <h1 className="mb-5 text-xl font-bold">Completions</h1>
+      <ResponsiveContainer width="100%" height={350}>
+        <BarChart
+          width={500}
+          height={300}
+          data={chartData}
+          margin={{
+            top: 5,
+            right: 20,
+            left: 20,
+            bottom: 5
+          }}
+        >
+          <Bar dataKey="value" fill="#8884d8" />
+          <XAxis dataKey="name" fontSize={12} />
+          <YAxis fontSize={12} />
+          <Tooltip cursor={{ fill: 'transparent' }} content={<BarTooltip />} />
+        </BarChart>
+      </ResponsiveContainer>
+    </div>
+  )
+}
diff --git a/ee/tabby-ui/app/(dashboard)/analytics/components/analyticYearlyCompletion.tsx b/ee/tabby-ui/app/(dashboard)/analytics/components/analyticYearlyCompletion.tsx
index bad3fcf21529..bd217f75ea1f 100644
--- a/ee/tabby-ui/app/(dashboard)/analytics/components/analyticYearlyCompletion.tsx
+++ b/ee/tabby-ui/app/(dashboard)/analytics/components/analyticYearlyCompletion.tsx
@@ -1,39 +1,45 @@
-import moment from "moment";
+import moment from 'moment'
 
 import ActivityCalendar from '@/components/activity-calendar'
 
 import type { DailyStats } from '../types/stats'
 
-export function AnalyticYearlyCompletion ({
-	yearlyStats,
+export function AnalyticYearlyCompletion({
+  yearlyStats
 }: {
-	yearlyStats: DailyStats[] | undefined;
+  yearlyStats: DailyStats[] | undefined
 }) {
   let lastYearCompletions = 0
-  const dailyCompletionMap: Record<string, number> = yearlyStats?.reduce((acc, cur) => {
-		const date = moment(cur.start).format('YYYY-MM-DD')
-    lastYearCompletions += cur.completions 
-		return { ...acc, [date]: cur.completions }
-	}, {}) || {}
+  const dailyCompletionMap: Record<string, number> =
+    yearlyStats?.reduce((acc, cur) => {
+      const date = moment(cur.start).format('YYYY-MM-DD')
+      lastYearCompletions += cur.completions
+      return { ...acc, [date]: cur.completions }
+    }, {}) || {}
 
-  const data = new Array(365).fill("").map((_, idx) => {
-    const date = moment().subtract(idx, 'days').format('YYYY-MM-DD')
-    const count = dailyCompletionMap[date] || 0
-    const level = Math.min(4, Math.ceil(count / 5))
-    return {
-      date: date,
-      count,
-      level
-    }
-  }).reverse()
+  const data = new Array(365)
+    .fill('')
+    .map((_, idx) => {
+      const date = moment().subtract(idx, 'days').format('YYYY-MM-DD')
+      const count = dailyCompletionMap[date] || 0
+      const level = Math.min(4, Math.ceil(count / 5))
+      return {
+        date: date,
+        count,
+        level
+      }
+    })
+    .reverse()
 
   return (
     <div className="flex h-full flex-col rounded-lg border bg-primary-foreground/30 p-4">
       <h1 className="text-xl font-bold">Activity</h1>
-      <p className="mt-0.5 text-xs text-muted-foreground">{lastYearCompletions} completions in the last year</p>
+      <p className="mt-0.5 text-xs text-muted-foreground">
+        {lastYearCompletions} completions in the last year
+      </p>
       <div className="mt-5 flex flex-1 items-center justify-center xl:mt-0">
         <ActivityCalendar data={data} />
       </div>
     </div>
-  );
-}
\ No newline at end of file
+  )
+}
diff --git a/ee/tabby-ui/app/(dashboard)/analytics/page.tsx b/ee/tabby-ui/app/(dashboard)/analytics/page.tsx
index 93be631a3150..16dd86eb4e97 100644
--- a/ee/tabby-ui/app/(dashboard)/analytics/page.tsx
+++ b/ee/tabby-ui/app/(dashboard)/analytics/page.tsx
@@ -8,4 +8,4 @@ export const metadata: Metadata = {
 
 export default function Page() {
   return <Analytic />
-}
\ No newline at end of file
+}
diff --git a/ee/tabby-ui/app/(dashboard)/analytics/types/stats.ts b/ee/tabby-ui/app/(dashboard)/analytics/types/stats.ts
index 3205afa5db11..0998078bb19f 100644
--- a/ee/tabby-ui/app/(dashboard)/analytics/types/stats.ts
+++ b/ee/tabby-ui/app/(dashboard)/analytics/types/stats.ts
@@ -1,6 +1,6 @@
 export type DailyStats = {
-  start: Date;
-  end: Date;
-  completions: number;
-  selects: number;
-}
\ No newline at end of file
+  start: Date
+  end: Date
+  completions: number
+  selects: number
+}
diff --git a/ee/tabby-ui/app/(dashboard)/components/sidebar.tsx b/ee/tabby-ui/app/(dashboard)/components/sidebar.tsx
index e2d6fd34b3d2..ef2c9febebe2 100644
--- a/ee/tabby-ui/app/(dashboard)/components/sidebar.tsx
+++ b/ee/tabby-ui/app/(dashboard)/components/sidebar.tsx
@@ -16,14 +16,14 @@ import {
   CollapsibleTrigger
 } from '@/components/ui/collapsible'
 import {
+  IconBarChart,
   IconChevronRight,
   IconGear,
   IconHome,
   IconLightingBolt,
   IconNetwork,
   IconScrollText,
-  IconUser,
-  IconBarChart
+  IconUser
 } from '@/components/ui/icons'
 
 export interface SidebarProps {
diff --git a/ee/tabby-ui/components/activity-calendar.tsx b/ee/tabby-ui/components/activity-calendar.tsx
index 9d09e20fe6e0..ab01762a29d6 100644
--- a/ee/tabby-ui/components/activity-calendar.tsx
+++ b/ee/tabby-ui/components/activity-calendar.tsx
@@ -1,33 +1,28 @@
 'use client'
 
-import dynamic from 'next/dynamic';
+import dynamic from 'next/dynamic'
+import { useWindowSize } from '@uidotdev/usehooks'
 import { useTheme } from 'next-themes'
-import { useWindowSize } from "@uidotdev/usehooks";
 
 // withou using dynamic, we got error "Error: calcTextDimensions() requires browser APIs"
 const ReactActivityCalendar = dynamic(() => import('react-activity-calendar'), {
-  ssr: false,
-});
+  ssr: false
+})
 
-export default function ActivityCalendar ({
+export default function ActivityCalendar({
   data
 }: {
   data: {
-    date: string;
-    count: number;
-    level: number;
+    date: string
+    count: number
+    level: number
   }[]
 }) {
   const { theme } = useTheme()
   const size = useWindowSize()
   const width = size.width || 0
-  const blockSize = width >= 1600
-    ? 13
-    : width >= 1400
-      ? 10
-      : width >= 1000
-        ? 8
-        : 5
+  const blockSize =
+    width >= 1600 ? 13 : width >= 1400 ? 10 : width >= 1000 ? 8 : 5
 
   return (
     <ReactActivityCalendar
@@ -35,10 +30,11 @@ export default function ActivityCalendar ({
       colorScheme={theme === 'dark' ? 'dark' : 'light'}
       theme={{
         light: ['#ebedf0', '#9be9a8', '#40c463', '#30a14e', '#216e39'],
-        dark: ['rgb(45, 51, 59)', '#0e4429', '#006d32', '#26a641', '#39d353'],
+        dark: ['rgb(45, 51, 59)', '#0e4429', '#006d32', '#26a641', '#39d353']
       }}
       blockSize={blockSize}
       hideTotalCount
-      showWeekdayLabels />
-  );
-}
\ No newline at end of file
+      showWeekdayLabels
+    />
+  )
+}
diff --git a/ee/tabby-ui/components/date-range-picker.tsx b/ee/tabby-ui/components/date-range-picker.tsx
index b81e588a1f30..686237c7f15d 100644
--- a/ee/tabby-ui/components/date-range-picker.tsx
+++ b/ee/tabby-ui/components/date-range-picker.tsx
@@ -1,18 +1,18 @@
-"use client"
+'use client'
 
-import * as React from "react"
-import { CalendarIcon } from "@radix-ui/react-icons"
-import { addDays, format } from "date-fns"
-import { DateRange } from "react-day-picker"
+import * as React from 'react'
+import { CalendarIcon } from '@radix-ui/react-icons'
+import { addDays, format } from 'date-fns'
+import { DateRange } from 'react-day-picker'
 
-import { cn } from "@/lib/utils"
-import { Button } from "@/components/ui/button"
-import { Calendar } from "@/components/ui/calendar"
+import { cn } from '@/lib/utils'
+import { Button } from '@/components/ui/button'
+import { Calendar } from '@/components/ui/calendar'
 import {
   Popover,
   PopoverContent,
-  PopoverTrigger,
-} from "@/components/ui/popover"
+  PopoverTrigger
+} from '@/components/ui/popover'
 
 export default function DatePickerWithRange({
   dateRange,
@@ -22,14 +22,14 @@ export default function DatePickerWithRange({
   onOpenChange
 }: React.HTMLAttributes<HTMLDivElement> & {
   dateRange?: DateRange
-  buttonClassName?: string;
-  contentAlign?: "start" | "end" | "center";
-  onOpenChange?: (isOpen: boolean, date: DateRange | undefined) => void,
-  onSelectDateRange?: (date: DateRange | undefined) => void,
+  buttonClassName?: string
+  contentAlign?: 'start' | 'end' | 'center'
+  onOpenChange?: (isOpen: boolean, date: DateRange | undefined) => void
+  onSelectDateRange?: (date: DateRange | undefined) => void
 }) {
   const [date, setDate] = React.useState<DateRange | undefined>({
     from: dateRange?.from || new Date(2022, 0, 20),
-    to: dateRange?.to || addDays(new Date(2022, 0, 20), 20),
+    to: dateRange?.to || addDays(new Date(2022, 0, 20), 20)
   })
 
   const toggleOpen = (isOpen: boolean) => {
@@ -37,15 +37,15 @@ export default function DatePickerWithRange({
   }
 
   return (
-    <div className={cn("grid gap-2", className)}>
+    <div className={cn('grid gap-2', className)}>
       <Popover onOpenChange={toggleOpen}>
         <PopoverTrigger asChild>
           <Button
             id="date"
-            variant={"outline"}
+            variant={'outline'}
             className={cn(
-              "w-[300px] justify-start text-left font-normal ",
-              !date && "text-muted-foreground",
+              'w-[300px] justify-start text-left font-normal ',
+              !date && 'text-muted-foreground',
               buttonClassName
             )}
           >
@@ -53,11 +53,11 @@ export default function DatePickerWithRange({
             {date?.from ? (
               date.to ? (
                 <>
-                  {format(date.from, "LLL dd, y")} -{" "}
-                  {format(date.to, "LLL dd, y")}
+                  {format(date.from, 'LLL dd, y')} -{' '}
+                  {format(date.to, 'LLL dd, y')}
                 </>
               ) : (
-                format(date.from, "LLL dd, y")
+                format(date.from, 'LLL dd, y')
               )
             ) : (
               <span>Pick a date</span>
@@ -77,4 +77,4 @@ export default function DatePickerWithRange({
       </Popover>
     </div>
   )
-}
\ No newline at end of file
+}
diff --git a/ee/tabby-ui/components/ui/calendar.tsx b/ee/tabby-ui/components/ui/calendar.tsx
index 43868fea1cf7..974e302d9096 100644
--- a/ee/tabby-ui/components/ui/calendar.tsx
+++ b/ee/tabby-ui/components/ui/calendar.tsx
@@ -1,11 +1,11 @@
-"use client"
+'use client'
 
-import * as React from "react"
-import { ChevronLeft, ChevronRight } from "lucide-react"
-import { DayPicker } from "react-day-picker"
+import * as React from 'react'
+import { ChevronLeft, ChevronRight } from 'lucide-react'
+import { DayPicker } from 'react-day-picker'
 
-import { cn } from "@/lib/utils"
-import { buttonVariants } from "@/components/ui/button"
+import { cn } from '@/lib/utils'
+import { buttonVariants } from '@/components/ui/button'
 
 export type CalendarProps = React.ComponentProps<typeof DayPicker>
 
@@ -18,49 +18,49 @@ function Calendar({
   return (
     <DayPicker
       showOutsideDays={showOutsideDays}
-      className={cn("p-3", className)}
+      className={cn('p-3', className)}
       classNames={{
-        months: "flex flex-col sm:flex-row space-y-4 sm:space-x-4 sm:space-y-0",
-        month: "space-y-4",
-        caption: "flex justify-center pt-1 relative items-center",
-        caption_label: "text-sm font-medium",
-        nav: "space-x-1 flex items-center",
+        months: 'flex flex-col sm:flex-row space-y-4 sm:space-x-4 sm:space-y-0',
+        month: 'space-y-4',
+        caption: 'flex justify-center pt-1 relative items-center',
+        caption_label: 'text-sm font-medium',
+        nav: 'space-x-1 flex items-center',
         nav_button: cn(
-          buttonVariants({ variant: "outline" }),
-          "h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100"
+          buttonVariants({ variant: 'outline' }),
+          'h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100'
         ),
-        nav_button_previous: "absolute left-1",
-        nav_button_next: "absolute right-1",
-        table: "w-full border-collapse space-y-1",
-        head_row: "flex",
+        nav_button_previous: 'absolute left-1',
+        nav_button_next: 'absolute right-1',
+        table: 'w-full border-collapse space-y-1',
+        head_row: 'flex',
         head_cell:
-          "text-muted-foreground rounded-md w-9 font-normal text-[0.8rem]",
-        row: "flex w-full mt-2",
-        cell: "h-9 w-9 text-center text-sm p-0 relative [&:has([aria-selected].day-range-end)]:rounded-r-md [&:has([aria-selected].day-outside)]:bg-accent/50 [&:has([aria-selected])]:bg-accent first:[&:has([aria-selected])]:rounded-l-md last:[&:has([aria-selected])]:rounded-r-md focus-within:relative focus-within:z-20",
+          'text-muted-foreground rounded-md w-9 font-normal text-[0.8rem]',
+        row: 'flex w-full mt-2',
+        cell: 'h-9 w-9 text-center text-sm p-0 relative [&:has([aria-selected].day-range-end)]:rounded-r-md [&:has([aria-selected].day-outside)]:bg-accent/50 [&:has([aria-selected])]:bg-accent first:[&:has([aria-selected])]:rounded-l-md last:[&:has([aria-selected])]:rounded-r-md focus-within:relative focus-within:z-20',
         day: cn(
-          buttonVariants({ variant: "ghost" }),
-          "h-9 w-9 p-0 font-normal aria-selected:opacity-100"
+          buttonVariants({ variant: 'ghost' }),
+          'h-9 w-9 p-0 font-normal aria-selected:opacity-100'
         ),
-        day_range_end: "day-range-end",
+        day_range_end: 'day-range-end',
         day_selected:
-          "bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground focus:bg-primary focus:text-primary-foreground",
-        day_today: "bg-accent text-accent-foreground",
+          'bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground focus:bg-primary focus:text-primary-foreground',
+        day_today: 'bg-accent text-accent-foreground',
         day_outside:
-          "day-outside text-muted-foreground opacity-50 aria-selected:bg-accent/50 aria-selected:text-muted-foreground aria-selected:opacity-30",
-        day_disabled: "text-muted-foreground opacity-50",
+          'day-outside text-muted-foreground opacity-50 aria-selected:bg-accent/50 aria-selected:text-muted-foreground aria-selected:opacity-30',
+        day_disabled: 'text-muted-foreground opacity-50',
         day_range_middle:
-          "aria-selected:bg-accent aria-selected:text-accent-foreground",
-        day_hidden: "invisible",
-        ...classNames,
+          'aria-selected:bg-accent aria-selected:text-accent-foreground',
+        day_hidden: 'invisible',
+        ...classNames
       }}
       components={{
         IconLeft: ({ ...props }) => <ChevronLeft className="h-4 w-4" />,
-        IconRight: ({ ...props }) => <ChevronRight className="h-4 w-4" />,
+        IconRight: ({ ...props }) => <ChevronRight className="h-4 w-4" />
       }}
       {...props}
     />
   )
 }
-Calendar.displayName = "Calendar"
+Calendar.displayName = 'Calendar'
 
-export { Calendar }
\ No newline at end of file
+export { Calendar }
diff --git a/ee/tabby-ui/lib/hooks/use-all-members.tsx b/ee/tabby-ui/lib/hooks/use-all-members.tsx
index 336c8d45e01d..b179ebe37797 100644
--- a/ee/tabby-ui/lib/hooks/use-all-members.tsx
+++ b/ee/tabby-ui/lib/hooks/use-all-members.tsx
@@ -1,13 +1,13 @@
-import { useState, useEffect } from 'react'
+import { useEffect, useState } from 'react'
 import { useQuery } from 'urql'
 
+import { DEFAULT_PAGE_SIZE } from '@/lib/constants'
 import { QueryVariables } from '@/lib/tabby/gql'
 import { listUsers } from '@/lib/tabby/query'
-import { DEFAULT_PAGE_SIZE } from '@/lib/constants'
 
 type Member = {
-  id: string;
-  email: string;
+  id: string
+  email: string
 }
 
 export function useAllMembers() {
@@ -30,14 +30,14 @@ export function useAllMembers() {
         id: edge.node.id,
         email: edge.node.email
       }))
-      const cursor = data?.users.pageInfo.endCursor || ""
+      const cursor = data?.users.pageInfo.endCursor || ''
       const hasMore = data?.users.pageInfo.hasNextPage
       const currentList = [...list]
 
       setList(currentList.concat(members))
       if (hasMore) {
         setQueryVariables({
-          first: DEFAULT_PAGE_SIZE, 
+          first: DEFAULT_PAGE_SIZE,
           after: cursor
         })
       } else {
diff --git a/ee/tabby-ui/lib/tabby/query.ts b/ee/tabby-ui/lib/tabby/query.ts
index 895094464651..83be636224fd 100644
--- a/ee/tabby-ui/lib/tabby/query.ts
+++ b/ee/tabby-ui/lib/tabby/query.ts
@@ -128,9 +128,7 @@ export const listUsers = graphql(/* GraphQL */ `
 `)
 
 export const queryDailyStatsInPastYear = graphql(/* GraphQL */ `
-  query DailyStatsInPastYear(
-    $users: [ID!]
-  ) {
+  query DailyStatsInPastYear($users: [ID!]) {
     dailyStatsInPastYear(users: $users) {
       start
       end
@@ -142,9 +140,9 @@ export const queryDailyStatsInPastYear = graphql(/* GraphQL */ `
 
 export const queryDailyStats = graphql(/* GraphQL */ `
   query DailyStats(
-    $start: DateTimeUtc!,
-    $end: DateTimeUtc!,
-    $users: [ID!],
+    $start: DateTimeUtc!
+    $end: DateTimeUtc!
+    $users: [ID!]
     $languages: [Language!]
   ) {
     dailyStats(start: $start, end: $end, users: $users, languages: $languages) {

From e86ad97b16b079f9ece85b4958a6262df5e0b065 Mon Sep 17 00:00:00 2001
From: Meng Zhang <meng@tabbyml.com>
Date: Sun, 7 Apr 2024 00:04:48 -0700
Subject: [PATCH 04/16] improve report page (#1776)

---
 .../app/(dashboard)/components/sidebar.tsx    |  4 ++--
 .../components/analytic.tsx                   | 24 +++++++++++--------
 .../components/analyticAccptance.tsx          |  0
 .../components/analyticDailyCompletion.tsx    |  0
 .../components/analyticYearlyCompletion.tsx   |  2 +-
 .../{analytics => reports}/page.tsx           |  0
 .../{analytics => reports}/types/stats.ts     |  0
 7 files changed, 17 insertions(+), 13 deletions(-)
 rename ee/tabby-ui/app/(dashboard)/{analytics => reports}/components/analytic.tsx (95%)
 rename ee/tabby-ui/app/(dashboard)/{analytics => reports}/components/analyticAccptance.tsx (100%)
 rename ee/tabby-ui/app/(dashboard)/{analytics => reports}/components/analyticDailyCompletion.tsx (100%)
 rename ee/tabby-ui/app/(dashboard)/{analytics => reports}/components/analyticYearlyCompletion.tsx (95%)
 rename ee/tabby-ui/app/(dashboard)/{analytics => reports}/page.tsx (100%)
 rename ee/tabby-ui/app/(dashboard)/{analytics => reports}/types/stats.ts (100%)

diff --git a/ee/tabby-ui/app/(dashboard)/components/sidebar.tsx b/ee/tabby-ui/app/(dashboard)/components/sidebar.tsx
index ef2c9febebe2..e049b58fc8a0 100644
--- a/ee/tabby-ui/app/(dashboard)/components/sidebar.tsx
+++ b/ee/tabby-ui/app/(dashboard)/components/sidebar.tsx
@@ -71,9 +71,9 @@ export default function Sidebar({ children, className }: SidebarProps) {
                     <IconScrollText />
                     Jobs
                   </SidebarButton>
-                  <SidebarButton href="/analytics">
+                  <SidebarButton href="/reports">
                     <IconBarChart />
-                    Analytics
+                    Reports
                   </SidebarButton>
                   <SidebarCollapsible
                     title={
diff --git a/ee/tabby-ui/app/(dashboard)/analytics/components/analytic.tsx b/ee/tabby-ui/app/(dashboard)/reports/components/analytic.tsx
similarity index 95%
rename from ee/tabby-ui/app/(dashboard)/analytics/components/analytic.tsx
rename to ee/tabby-ui/app/(dashboard)/reports/components/analytic.tsx
index 036989e0e5fa..f54b3f94b0bc 100644
--- a/ee/tabby-ui/app/(dashboard)/analytics/components/analytic.tsx
+++ b/ee/tabby-ui/app/(dashboard)/reports/components/analytic.tsx
@@ -40,25 +40,29 @@ function AnalyticSummary({
     <div className="flex items-center justify-center space-x-4 xl:justify-start">
       <div className="space-y-0.5 rounded-lg border bg-primary-foreground/30 p-4 lg:min-w-[250px]">
         <p className="text-xs text-muted-foreground lg:text-sm">
-          Total completions
+          Accept Rate
         </p>
         <p className="font-bold lg:text-3xl">
-          {numeral(totalCompletions).format('0,0')}
+          TBD
         </p>
       </div>
 
       <div className="space-y-0.5 rounded-lg border bg-primary-foreground/30 p-4 lg:min-w-[250px]">
         <p className="text-xs text-muted-foreground lg:text-sm">
-          Minutes saved / completion
+          Total completions
+        </p>
+        <p className="font-bold lg:text-3xl">
+          {numeral(totalCompletions).format('0,0')}
         </p>
-        <p className="font-bold lg:text-3xl">2</p>
       </div>
 
       <div className="space-y-0.5 rounded-lg border bg-primary-foreground/30 p-4 lg:min-w-[250px]">
         <p className="text-xs text-muted-foreground lg:text-sm">
-          Hours saved in total
+          Total acceptances
+        </p>
+        <p className="font-bold lg:text-3xl">
+          TBD
         </p>
-        <p className="font-bold lg:text-3xl">100</p>
       </div>
     </div>
   )
@@ -126,10 +130,10 @@ export function Analytic() {
       <div className="flex flex-col items-center justify-between gap-y-3 xl:flex-row xl:gap-y-0">
         <div className="flex flex-col justify-center xl:justify-start">
           <h1 className="mb-1.5 scroll-m-20 text-center text-4xl font-extrabold tracking-tight lg:text-5xl xl:text-left">
-            Analytics
+            Reports
           </h1>
           <p className="text-muted-foreground">
-            Overview of code completion usage
+            Statistics around Tabby IDE / Extensions
           </p>
         </div>
 
@@ -211,14 +215,14 @@ export function Analytic() {
       </LoadingWrapper>
 
       <div className="flex flex-col gap-y-6 xl:flex-row xl:gap-x-6 xl:gap-y-0">
-        <div className="flex-1">
+        {false && <div className="flex-1">
           <LoadingWrapper
             loading={fetchingDailyState}
             fallback={<Skeleton className="h-64 w-full" />}
           >
             <AnlyticAcceptance dailyStats={dailyStats} dateRange={dateRange} />
           </LoadingWrapper>
-        </div>
+        </div>}
         <div style={{ flex: 3 }}>
           <LoadingWrapper
             loading={fetchingDailyState || fetchingYearlyStats}
diff --git a/ee/tabby-ui/app/(dashboard)/analytics/components/analyticAccptance.tsx b/ee/tabby-ui/app/(dashboard)/reports/components/analyticAccptance.tsx
similarity index 100%
rename from ee/tabby-ui/app/(dashboard)/analytics/components/analyticAccptance.tsx
rename to ee/tabby-ui/app/(dashboard)/reports/components/analyticAccptance.tsx
diff --git a/ee/tabby-ui/app/(dashboard)/analytics/components/analyticDailyCompletion.tsx b/ee/tabby-ui/app/(dashboard)/reports/components/analyticDailyCompletion.tsx
similarity index 100%
rename from ee/tabby-ui/app/(dashboard)/analytics/components/analyticDailyCompletion.tsx
rename to ee/tabby-ui/app/(dashboard)/reports/components/analyticDailyCompletion.tsx
diff --git a/ee/tabby-ui/app/(dashboard)/analytics/components/analyticYearlyCompletion.tsx b/ee/tabby-ui/app/(dashboard)/reports/components/analyticYearlyCompletion.tsx
similarity index 95%
rename from ee/tabby-ui/app/(dashboard)/analytics/components/analyticYearlyCompletion.tsx
rename to ee/tabby-ui/app/(dashboard)/reports/components/analyticYearlyCompletion.tsx
index bd217f75ea1f..75b2b8182244 100644
--- a/ee/tabby-ui/app/(dashboard)/analytics/components/analyticYearlyCompletion.tsx
+++ b/ee/tabby-ui/app/(dashboard)/reports/components/analyticYearlyCompletion.tsx
@@ -35,7 +35,7 @@ export function AnalyticYearlyCompletion({
     <div className="flex h-full flex-col rounded-lg border bg-primary-foreground/30 p-4">
       <h1 className="text-xl font-bold">Activity</h1>
       <p className="mt-0.5 text-xs text-muted-foreground">
-        {lastYearCompletions} completions in the last year
+        {lastYearCompletions} activities in the last year
       </p>
       <div className="mt-5 flex flex-1 items-center justify-center xl:mt-0">
         <ActivityCalendar data={data} />
diff --git a/ee/tabby-ui/app/(dashboard)/analytics/page.tsx b/ee/tabby-ui/app/(dashboard)/reports/page.tsx
similarity index 100%
rename from ee/tabby-ui/app/(dashboard)/analytics/page.tsx
rename to ee/tabby-ui/app/(dashboard)/reports/page.tsx
diff --git a/ee/tabby-ui/app/(dashboard)/analytics/types/stats.ts b/ee/tabby-ui/app/(dashboard)/reports/types/stats.ts
similarity index 100%
rename from ee/tabby-ui/app/(dashboard)/analytics/types/stats.ts
rename to ee/tabby-ui/app/(dashboard)/reports/types/stats.ts

From 85ebc2b72d6ffb2e0e2ee5691a6afe0151d6d6ce Mon Sep 17 00:00:00 2001
From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com>
Date: Sun, 7 Apr 2024 07:05:54 +0000
Subject: [PATCH 05/16] [autofix.ci] apply automated fixes

---
 .../reports/components/analytic.tsx           | 33 +++++++++----------
 1 file changed, 16 insertions(+), 17 deletions(-)

diff --git a/ee/tabby-ui/app/(dashboard)/reports/components/analytic.tsx b/ee/tabby-ui/app/(dashboard)/reports/components/analytic.tsx
index f54b3f94b0bc..a070ea589a3b 100644
--- a/ee/tabby-ui/app/(dashboard)/reports/components/analytic.tsx
+++ b/ee/tabby-ui/app/(dashboard)/reports/components/analytic.tsx
@@ -39,12 +39,8 @@ function AnalyticSummary({
   return (
     <div className="flex items-center justify-center space-x-4 xl:justify-start">
       <div className="space-y-0.5 rounded-lg border bg-primary-foreground/30 p-4 lg:min-w-[250px]">
-        <p className="text-xs text-muted-foreground lg:text-sm">
-          Accept Rate
-        </p>
-        <p className="font-bold lg:text-3xl">
-          TBD
-        </p>
+        <p className="text-xs text-muted-foreground lg:text-sm">Accept Rate</p>
+        <p className="font-bold lg:text-3xl">TBD</p>
       </div>
 
       <div className="space-y-0.5 rounded-lg border bg-primary-foreground/30 p-4 lg:min-w-[250px]">
@@ -60,9 +56,7 @@ function AnalyticSummary({
         <p className="text-xs text-muted-foreground lg:text-sm">
           Total acceptances
         </p>
-        <p className="font-bold lg:text-3xl">
-          TBD
-        </p>
+        <p className="font-bold lg:text-3xl">TBD</p>
       </div>
     </div>
   )
@@ -215,14 +209,19 @@ export function Analytic() {
       </LoadingWrapper>
 
       <div className="flex flex-col gap-y-6 xl:flex-row xl:gap-x-6 xl:gap-y-0">
-        {false && <div className="flex-1">
-          <LoadingWrapper
-            loading={fetchingDailyState}
-            fallback={<Skeleton className="h-64 w-full" />}
-          >
-            <AnlyticAcceptance dailyStats={dailyStats} dateRange={dateRange} />
-          </LoadingWrapper>
-        </div>}
+        {false && (
+          <div className="flex-1">
+            <LoadingWrapper
+              loading={fetchingDailyState}
+              fallback={<Skeleton className="h-64 w-full" />}
+            >
+              <AnlyticAcceptance
+                dailyStats={dailyStats}
+                dateRange={dateRange}
+              />
+            </LoadingWrapper>
+          </div>
+        )}
         <div style={{ flex: 3 }}>
           <LoadingWrapper
             loading={fetchingDailyState || fetchingYearlyStats}

From a7325cb6b1e01d979c35ab1847d39942622f0977 Mon Sep 17 00:00:00 2001
From: wwayne <wayne.wang0821@gmail.com>
Date: Sun, 7 Apr 2024 16:19:53 +0800
Subject: [PATCH 06/16] code detail polishment

---
 .../reports/components/analytic.tsx           |  4 +--
 .../reports}/hooks/use-all-members.tsx        |  0
 ee/tabby-ui/app/(dashboard)/reports/query.ts  | 28 +++++++++++++++++++
 .../settings/team/components/user-table.tsx   | 25 +----------------
 ee/tabby-ui/components/ui/icons.tsx           |  5 ++++
 ee/tabby-ui/lib/tabby/query.ts                | 27 ------------------
 6 files changed, 36 insertions(+), 53 deletions(-)
 rename ee/tabby-ui/{lib => app/(dashboard)/reports}/hooks/use-all-members.tsx (100%)
 create mode 100644 ee/tabby-ui/app/(dashboard)/reports/query.ts

diff --git a/ee/tabby-ui/app/(dashboard)/reports/components/analytic.tsx b/ee/tabby-ui/app/(dashboard)/reports/components/analytic.tsx
index a070ea589a3b..7f499e9f4a82 100644
--- a/ee/tabby-ui/app/(dashboard)/reports/components/analytic.tsx
+++ b/ee/tabby-ui/app/(dashboard)/reports/components/analytic.tsx
@@ -8,8 +8,8 @@ import { DateRange } from 'react-day-picker'
 import { useQuery } from 'urql'
 
 import { Language } from '@/lib/gql/generates/graphql'
-import { useAllMembers } from '@/lib/hooks/use-all-members'
-import { queryDailyStats, queryDailyStatsInPastYear } from '@/lib/tabby/query'
+import { useAllMembers } from '@/app/(dashboard)/reports/hooks/use-all-members'
+import { queryDailyStats, queryDailyStatsInPastYear } from '../query'
 import {
   Select,
   SelectContent,
diff --git a/ee/tabby-ui/lib/hooks/use-all-members.tsx b/ee/tabby-ui/app/(dashboard)/reports/hooks/use-all-members.tsx
similarity index 100%
rename from ee/tabby-ui/lib/hooks/use-all-members.tsx
rename to ee/tabby-ui/app/(dashboard)/reports/hooks/use-all-members.tsx
diff --git a/ee/tabby-ui/app/(dashboard)/reports/query.ts b/ee/tabby-ui/app/(dashboard)/reports/query.ts
new file mode 100644
index 000000000000..936164f4f748
--- /dev/null
+++ b/ee/tabby-ui/app/(dashboard)/reports/query.ts
@@ -0,0 +1,28 @@
+import { graphql } from '@/lib/gql/generates'
+
+export const queryDailyStatsInPastYear = graphql(/* GraphQL */ `
+  query DailyStatsInPastYear($users: [ID!]) {
+    dailyStatsInPastYear(users: $users) {
+      start
+      end
+      completions
+      selects
+    }
+  }
+`)
+
+export const queryDailyStats = graphql(/* GraphQL */ `
+  query DailyStats(
+    $start: DateTimeUtc!
+    $end: DateTimeUtc!
+    $users: [ID!]
+    $languages: [Language!]
+  ) {
+    dailyStats(start: $start, end: $end, users: $users, languages: $languages) {
+      start
+      end
+      completions
+      selects
+    }
+  }
+`)
diff --git a/ee/tabby-ui/app/(dashboard)/settings/team/components/user-table.tsx b/ee/tabby-ui/app/(dashboard)/settings/team/components/user-table.tsx
index a4c230f18510..3a30b29159e2 100644
--- a/ee/tabby-ui/app/(dashboard)/settings/team/components/user-table.tsx
+++ b/ee/tabby-ui/app/(dashboard)/settings/team/components/user-table.tsx
@@ -10,6 +10,7 @@ import { graphql } from '@/lib/gql/generates'
 import type { ListUsersQuery } from '@/lib/gql/generates/graphql'
 import { useMe } from '@/lib/hooks/use-me'
 import { QueryVariables, useMutation } from '@/lib/tabby/gql'
+import { listUsers } from '@/lib/tabby/query'
 import type { ArrayElementType } from '@/lib/types'
 import { Badge } from '@/components/ui/badge'
 import { Button } from '@/components/ui/button'
@@ -39,30 +40,6 @@ import LoadingWrapper from '@/components/loading-wrapper'
 
 import { UpdateUserRoleDialog } from './user-role-dialog'
 
-const listUsers = graphql(/* GraphQL */ `
-  query ListUsers($after: String, $before: String, $first: Int, $last: Int) {
-    users(after: $after, before: $before, first: $first, last: $last) {
-      edges {
-        node {
-          id
-          email
-          isAdmin
-          isOwner
-          createdAt
-          active
-        }
-        cursor
-      }
-      pageInfo {
-        hasNextPage
-        hasPreviousPage
-        startCursor
-        endCursor
-      }
-    }
-  }
-`)
-
 const updateUserActiveMutation = graphql(/* GraphQL */ `
   mutation UpdateUserActive($id: ID!, $active: Boolean!) {
     updateUserActive(id: $id, active: $active)
diff --git a/ee/tabby-ui/components/ui/icons.tsx b/ee/tabby-ui/components/ui/icons.tsx
index eb61e8eca493..ab7caeff5b09 100644
--- a/ee/tabby-ui/components/ui/icons.tsx
+++ b/ee/tabby-ui/components/ui/icons.tsx
@@ -184,6 +184,8 @@ function IconArrowRight({ className, ...props }: React.ComponentProps<'svg'>) {
   )
 }
 
+// FIXME: Remove to use ChevronRight from lucide-react
+// currently ui/calendar is using ChevronRight as the default option.
 function IconChevronRight({
   className,
   ...props
@@ -204,6 +206,9 @@ function IconChevronRight({
     </svg>
   )
 }
+
+// FIXME: Remove to use ChevronLeft from lucide-react 
+// currently ui/calendar is using ChevronLeft as the default option.
 function IconChevronLeft({ className, ...props }: React.ComponentProps<'svg'>) {
   return (
     <svg
diff --git a/ee/tabby-ui/lib/tabby/query.ts b/ee/tabby-ui/lib/tabby/query.ts
index 83be636224fd..464e479b970f 100644
--- a/ee/tabby-ui/lib/tabby/query.ts
+++ b/ee/tabby-ui/lib/tabby/query.ts
@@ -126,30 +126,3 @@ export const listUsers = graphql(/* GraphQL */ `
     }
   }
 `)
-
-export const queryDailyStatsInPastYear = graphql(/* GraphQL */ `
-  query DailyStatsInPastYear($users: [ID!]) {
-    dailyStatsInPastYear(users: $users) {
-      start
-      end
-      completions
-      selects
-    }
-  }
-`)
-
-export const queryDailyStats = graphql(/* GraphQL */ `
-  query DailyStats(
-    $start: DateTimeUtc!
-    $end: DateTimeUtc!
-    $users: [ID!]
-    $languages: [Language!]
-  ) {
-    dailyStats(start: $start, end: $end, users: $users, languages: $languages) {
-      start
-      end
-      completions
-      selects
-    }
-  }
-`)

From 254598b39548e4471a5632a009a81d6e86f03b0b Mon Sep 17 00:00:00 2001
From: wwayne <wayne.wang0821@gmail.com>
Date: Sun, 7 Apr 2024 19:10:14 +0800
Subject: [PATCH 07/16] update ui

---
 .../reports/components/analytic.tsx           | 227 ++++++++++--------
 .../components/analyticDailyCompletion.tsx    |  70 ++++--
 .../reports/components/analyticStats.tsx      |  12 +
 .../components/analyticYearlyCompletion.tsx   |  11 +-
 ee/tabby-ui/components/ui/icons.tsx           |  21 +-
 5 files changed, 211 insertions(+), 130 deletions(-)
 create mode 100644 ee/tabby-ui/app/(dashboard)/reports/components/analyticStats.tsx

diff --git a/ee/tabby-ui/app/(dashboard)/reports/components/analytic.tsx b/ee/tabby-ui/app/(dashboard)/reports/components/analytic.tsx
index 7f499e9f4a82..f15ac8bdd31f 100644
--- a/ee/tabby-ui/app/(dashboard)/reports/components/analytic.tsx
+++ b/ee/tabby-ui/app/(dashboard)/reports/components/analytic.tsx
@@ -8,8 +8,10 @@ import { DateRange } from 'react-day-picker'
 import { useQuery } from 'urql'
 
 import { Language } from '@/lib/gql/generates/graphql'
-import { useAllMembers } from '@/app/(dashboard)/reports/hooks/use-all-members'
+import { useAllMembers } from '../hooks/use-all-members'
 import { queryDailyStats, queryDailyStatsInPastYear } from '../query'
+
+import { IconActivity, IconCode, IconCheck } from '@/components/ui/icons'
 import {
   Select,
   SelectContent,
@@ -18,15 +20,15 @@ import {
   SelectTrigger,
   SelectValue
 } from '@/components/ui/select'
-import { Skeleton } from '@/components/ui/skeleton'
 import DatePickerWithRange from '@/components/date-range-picker'
 import LoadingWrapper from '@/components/loading-wrapper'
+import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/card'
 
-import type { DailyStats } from '../types/stats'
-import { AnlyticAcceptance } from './analyticAccptance'
 import { AnalyticDailyCompletion } from './analyticDailyCompletion'
 import { AnalyticYearlyCompletion } from './analyticYearlyCompletion'
 
+import type { DailyStats } from '../types/stats'
+
 const INITIAL_DATE_RANGE = 14
 const KEY_SELECT_ALL = 'all'
 
@@ -37,27 +39,51 @@ function AnalyticSummary({
 }) {
   const totalCompletions = sum(dailyStats?.map(stats => stats.completions))
   return (
-    <div className="flex items-center justify-center space-x-4 xl:justify-start">
-      <div className="space-y-0.5 rounded-lg border bg-primary-foreground/30 p-4 lg:min-w-[250px]">
-        <p className="text-xs text-muted-foreground lg:text-sm">Accept Rate</p>
-        <p className="font-bold lg:text-3xl">TBD</p>
-      </div>
+    <div className="flex w-full items-center justify-center space-x-6 xl:justify-start">
+      <Card className="flex-1 bg-primary-foreground/30">
+        <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
+          <CardTitle className="text-sm font-medium">
+            Accept Rate
+          </CardTitle>
+          <IconActivity className="text-muted-foreground" />
+        </CardHeader>
+        <CardContent>
+          <div className="text-2xl font-bold">TBD</div>
+          <p className="text-xs text-muted-foreground">
+            +TBD from last week
+          </p>
+        </CardContent>
+      </Card>
 
-      <div className="space-y-0.5 rounded-lg border bg-primary-foreground/30 p-4 lg:min-w-[250px]">
-        <p className="text-xs text-muted-foreground lg:text-sm">
-          Total completions
-        </p>
-        <p className="font-bold lg:text-3xl">
-          {numeral(totalCompletions).format('0,0')}
-        </p>
-      </div>
+      <Card className="flex-1 bg-primary-foreground/30">
+        <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
+          <CardTitle className="text-sm font-medium">
+            Total completions
+          </CardTitle>
+          <IconCode className="text-muted-foreground" />
+        </CardHeader>
+        <CardContent>
+          <div className="text-2xl font-bold">{numeral(totalCompletions).format('0,0')}</div>
+          <p className="text-xs text-muted-foreground">
+            +TBD from last week
+          </p>
+        </CardContent>
+      </Card>
 
-      <div className="space-y-0.5 rounded-lg border bg-primary-foreground/30 p-4 lg:min-w-[250px]">
-        <p className="text-xs text-muted-foreground lg:text-sm">
-          Total acceptances
-        </p>
-        <p className="font-bold lg:text-3xl">TBD</p>
-      </div>
+      <Card className="flex-1 bg-primary-foreground/30">
+        <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
+          <CardTitle className="text-sm font-medium">
+            Total acceptances
+          </CardTitle>
+          <IconCheck className="h-4 w-4 text-muted-foreground" />
+        </CardHeader>
+        <CardContent>
+          <div className="text-2xl font-bold">TBD</div>
+          <p className="text-xs text-muted-foreground">
+            +TBD from last week
+          </p>
+        </CardContent>
+      </Card>
     </div>
   )
 }
@@ -120,8 +146,8 @@ export function Analytic() {
   }
 
   return (
-    <div className="flex flex-col gap-y-6">
-      <div className="flex flex-col items-center justify-between gap-y-3 xl:flex-row xl:gap-y-0">
+    <div className="mx-auto max-w-5xl">
+      <div className="mb-3 flex flex-col items-center justify-between gap-y-3 xl:flex-row xl:gap-y-0">
         <div className="flex flex-col justify-center xl:justify-start">
           <h1 className="mb-1.5 scroll-m-20 text-center text-4xl font-extrabold tracking-tight lg:text-5xl xl:text-left">
             Reports
@@ -131,84 +157,89 @@ export function Analytic() {
           </p>
         </div>
 
-        <div className="flex flex-col items-center gap-y-2 lg:flex-row lg:gap-y-0 lg:space-x-4">
-          <Select
-            defaultValue={KEY_SELECT_ALL}
-            onValueChange={setSelectedMember}
-          >
-            <SelectTrigger className="w-[300px] lg:w-[180px]">
-              <div className="flex w-full items-center truncate ">
-                <span className="mr-1.5 text-muted-foreground">Member:</span>
-                <div className="overflow-hidden text-ellipsis">
-                  <SelectValue />
-                </div>
-              </div>
-            </SelectTrigger>
-            <SelectContent>
-              <SelectGroup>
-                <SelectItem value={KEY_SELECT_ALL}>All</SelectItem>
-                {members.map(member => (
-                  <SelectItem value={member.id} key={member.id}>
-                    {member.email}
-                  </SelectItem>
-                ))}
-              </SelectGroup>
-            </SelectContent>
-          </Select>
-
-          <Select
-            defaultValue={KEY_SELECT_ALL}
-            onValueChange={(value: 'all' | Language) =>
-              setSelectedLanguage(value)
-            }
-          >
-            <SelectTrigger className="w-[300px] lg:w-[180px]">
-              <div className="flex w-full items-center truncate">
-                <span className="mr-1.5 text-muted-foreground">Language:</span>
-                <div className="overflow-hidden text-ellipsis">
-                  <SelectValue />
-                </div>
+        <Select
+          defaultValue={KEY_SELECT_ALL}
+          onValueChange={setSelectedMember}
+        >
+          <SelectTrigger className="w-[300px] lg:w-[150px]">
+            <div className="flex w-full items-center truncate ">
+              <span className="mr-1.5 text-muted-foreground">Member:</span>
+              <div className="overflow-hidden text-ellipsis">
+                <SelectValue />
               </div>
-            </SelectTrigger>
-            <SelectContent>
-              <SelectGroup>
-                <SelectItem value={'all'}>All</SelectItem>
-                {Object.entries(Language).map(([key, value]) => (
-                  <SelectItem key={value} value={value}>
-                    {key}
-                  </SelectItem>
-                ))}
-              </SelectGroup>
-            </SelectContent>
-          </Select>
-
-          <DatePickerWithRange
-            buttonClassName="h-full"
-            contentAlign="end"
+            </div>
+          </SelectTrigger>
+          <SelectContent align='end'>
+            <SelectGroup>
+              <SelectItem value={KEY_SELECT_ALL}>All</SelectItem>
+              {members.map(member => (
+                <SelectItem value={member.id} key={member.id}>
+                  {member.email}
+                </SelectItem>
+              ))}
+            </SelectGroup>
+          </SelectContent>
+        </Select>
+      </div>
+
+      <LoadingWrapper>
+        <div className="mb-10 flex flex-col gap-y-5">
+          <div className="flex items-center justify-between">
+            <h1 className="text-xl font-semibold">Usage</h1>
+            
+            <div className="flex items-center gap-x-3">
+              <Select
+                defaultValue={KEY_SELECT_ALL}
+                onValueChange={(value: 'all' | Language) =>
+                  setSelectedLanguage(value)
+                }
+              >
+                <SelectTrigger className="w-[300px] lg:w-[180px]">
+                  <div className="flex w-full items-center truncate">
+                    <span className="mr-1.5 text-muted-foreground">Language:</span>
+                    <div className="overflow-hidden text-ellipsis">
+                      <SelectValue />
+                    </div>
+                  </div>
+                </SelectTrigger>
+                <SelectContent>
+                  <SelectGroup>
+                    <SelectItem value={'all'}>All</SelectItem>
+                    {Object.entries(Language).map(([key, value]) => (
+                      <SelectItem key={value} value={value}>
+                        {key}
+                      </SelectItem>
+                    ))}
+                  </SelectGroup>
+                </SelectContent>
+              </Select>
+
+              <DatePickerWithRange
+                buttonClassName="h-full"
+                contentAlign="end"
+                dateRange={dateRange}
+                onOpenChange={onDateOpenChange}
+              />
+            </div>
+          </div>
+
+          <AnalyticSummary dailyStats={dailyStats} />
+
+          <AnalyticDailyCompletion
+            dailyStats={dailyStats}
             dateRange={dateRange}
-            onOpenChange={onDateOpenChange}
           />
         </div>
-      </div>
-
-      <LoadingWrapper
-        loading={fetchingDailyState}
-        fallback={<Skeleton className="h-24 w-1/2" />}
-      >
-        <AnalyticSummary dailyStats={dailyStats} />
       </LoadingWrapper>
-
-      <LoadingWrapper
-        loading={fetchingDailyState}
-        fallback={<Skeleton className="h-64 w-full" />}
-      >
-        <AnalyticDailyCompletion
-          dailyStats={dailyStats}
-          dateRange={dateRange}
-        />
+      
+      <LoadingWrapper>
+        <div className="mb-10">
+          <h1 className="mb-3 text-xl font-semibold">Activity</h1>
+          <AnalyticYearlyCompletion yearlyStats={yearlyStats} />
+        </div>
       </LoadingWrapper>
 
-      <div className="flex flex-col gap-y-6 xl:flex-row xl:gap-x-6 xl:gap-y-0">
+      {/* <div className="flex flex-col gap-y-6 xl:flex-row xl:gap-x-6 xl:gap-y-0">
         {false && (
           <div className="flex-1">
             <LoadingWrapper
@@ -227,10 +258,10 @@ export function Analytic() {
             loading={fetchingDailyState || fetchingYearlyStats}
             fallback={<Skeleton className="h-64 w-full" />}
           >
-            <AnalyticYearlyCompletion yearlyStats={yearlyStats} />
+            
           </LoadingWrapper>
         </div>
-      </div>
+      </div> */}
     </div>
   )
 }
diff --git a/ee/tabby-ui/app/(dashboard)/reports/components/analyticDailyCompletion.tsx b/ee/tabby-ui/app/(dashboard)/reports/components/analyticDailyCompletion.tsx
index 6490049c0b5b..a8c7dbc6c977 100644
--- a/ee/tabby-ui/app/(dashboard)/reports/components/analyticDailyCompletion.tsx
+++ b/ee/tabby-ui/app/(dashboard)/reports/components/analyticDailyCompletion.tsx
@@ -1,6 +1,10 @@
+'use client'
+
 import { eachDayOfInterval } from 'date-fns'
 import moment from 'moment'
-import type { DateRange } from 'react-day-picker'
+import { sum } from 'lodash-es'
+import { useTheme } from 'next-themes'
+
 import {
   Bar,
   BarChart,
@@ -9,9 +13,9 @@ import {
   XAxis,
   YAxis
 } from 'recharts'
-
 import { Card, CardContent } from '@/components/ui/card'
 
+import type { DateRange } from 'react-day-picker'
 import type { DailyStats } from '../types/stats'
 
 function BarTooltip({
@@ -19,21 +23,29 @@ function BarTooltip({
   payload,
   label
 }: {
-  active?: boolean
-  label?: string
+  active?: boolean;
+  label?: string;
   payload?: {
-    name: string
-    value: number
+    name: string;
+    payload: {
+      completion: number;
+      select: number;
+      pending: number;
+    };
+    
   }[]
 }) {
   if (active && payload && payload.length) {
-    const completion = payload[0].value
+    const {completion, select} = payload[0].payload
     if (!completion) return null
     return (
       <Card>
-        <CardContent className="px-4 py-2 text-sm">
-          <p>
-            Completions: <b>{completion}</b>
+        <CardContent className="flex flex-col gap-y-0.5 px-4 py-2 text-sm">
+          <p className="flex items-center">
+            <span className="mr-3 inline-block w-20">Completion:</span><b>{completion}</b>
+          </p>
+          <p className="flex items-center">
+          <span className="mr-3 inline-block w-20">Acceptance:</span><b>{select}</b>
           </p>
           <p className="text-muted-foreground">{label}</p>
         </CardContent>
@@ -51,14 +63,19 @@ export function AnalyticDailyCompletion({
   dailyStats: DailyStats[] | undefined
   dateRange: DateRange
 }) {
+  const { theme } = useTheme()
+  const totalCompletions = sum(dailyStats?.map(stats => stats.completions))
   const from = dateRange.from || new Date()
   const to = dateRange.to || from
 
-  const dailyCompletionMap: Record<string, number> =
-    dailyStats?.reduce((acc, cur) => {
-      const date = moment(cur.start).format('YYYY-MM-DD')
-      return { ...acc, [date]: cur.completions }
-    }, {}) || {}
+  const dailyCompletionMap: Record<string, number> = {}
+  const dailySelectMap: Record<string, number> = {}
+
+  dailyStats?.forEach(stats => {
+    const date = moment(stats.start).format('YYYY-MM-DD')
+    dailyCompletionMap[date] = stats.completions
+    dailySelectMap[date] = stats.selects
+  }, {})
 
   const daysBetweenRange = eachDayOfInterval({
     start: from,
@@ -66,20 +83,22 @@ export function AnalyticDailyCompletion({
   })
 
   const chartData = daysBetweenRange.map(date => {
-    const completionKey = moment(date).format('YYYY-MM-DD')
-    const value = dailyCompletionMap[completionKey] || 0
+    const dateKey = moment(date).format('YYYY-MM-DD')
+    const completion = dailyCompletionMap[dateKey] || 0
+    const select = dailySelectMap[dateKey] || 0
+    const pending = completion - select
     return {
       name: moment(date).format('D MMM'),
-      value
+      completion,
+      select,
+      pending
     }
   })
   return (
-    <div className="rounded-lg border bg-primary-foreground/30 p-4">
-      <h1 className="mb-5 text-xl font-bold">Completions</h1>
-      <ResponsiveContainer width="100%" height={350}>
+    <div className="rounded-lg border bg-primary-foreground/30 px-6 py-4">
+      <h3 className="mb-5 text-sm font-medium tracking-tight">Completions</h3>
+      <ResponsiveContainer width="100%" height={300}>
         <BarChart
-          width={500}
-          height={300}
           data={chartData}
           margin={{
             top: 5,
@@ -88,9 +107,10 @@ export function AnalyticDailyCompletion({
             bottom: 5
           }}
         >
-          <Bar dataKey="value" fill="#8884d8" />
+          <Bar dataKey="select" stackId="stats" fill={theme === 'dark' ? '#e8e1d3' : '#54452c'} radius={3} />
+          <Bar dataKey="pending" stackId="stats" fill={theme === 'dark' ? '#423929' : '#e8e1d3'} radius={3} />
           <XAxis dataKey="name" fontSize={12} />
-          <YAxis fontSize={12} />
+          <YAxis fontSize={12} width={20} />
           <Tooltip cursor={{ fill: 'transparent' }} content={<BarTooltip />} />
         </BarChart>
       </ResponsiveContainer>
diff --git a/ee/tabby-ui/app/(dashboard)/reports/components/analyticStats.tsx b/ee/tabby-ui/app/(dashboard)/reports/components/analyticStats.tsx
new file mode 100644
index 000000000000..afae66fff980
--- /dev/null
+++ b/ee/tabby-ui/app/(dashboard)/reports/components/analyticStats.tsx
@@ -0,0 +1,12 @@
+import type { DateRange } from 'react-day-picker'
+import type { DailyStats } from '../types/stats'
+
+export function AnalyticStats ({
+  dailyStats,
+  dateRange
+}: {
+  dailyStats: DailyStats[] | undefined
+  dateRange: DateRange
+}) {
+
+}
\ No newline at end of file
diff --git a/ee/tabby-ui/app/(dashboard)/reports/components/analyticYearlyCompletion.tsx b/ee/tabby-ui/app/(dashboard)/reports/components/analyticYearlyCompletion.tsx
index 75b2b8182244..d46c34f2295d 100644
--- a/ee/tabby-ui/app/(dashboard)/reports/components/analyticYearlyCompletion.tsx
+++ b/ee/tabby-ui/app/(dashboard)/reports/components/analyticYearlyCompletion.tsx
@@ -32,12 +32,11 @@ export function AnalyticYearlyCompletion({
     .reverse()
 
   return (
-    <div className="flex h-full flex-col rounded-lg border bg-primary-foreground/30 p-4">
-      <h1 className="text-xl font-bold">Activity</h1>
-      <p className="mt-0.5 text-xs text-muted-foreground">
-        {lastYearCompletions} activities in the last year
-      </p>
-      <div className="mt-5 flex flex-1 items-center justify-center xl:mt-0">
+    <div className="flex h-full flex-col rounded-lg border bg-primary-foreground/30 px-6 py-4">
+      <h3 className="mb-5 text-sm font-medium tracking-tight">
+        <b>{lastYearCompletions}</b> activities in the last year
+      </h3>
+      <div className="flex flex-1 items-center justify-center">
         <ActivityCalendar data={data} />
       </div>
     </div>
diff --git a/ee/tabby-ui/components/ui/icons.tsx b/ee/tabby-ui/components/ui/icons.tsx
index ab7caeff5b09..dc53281e45c1 100644
--- a/ee/tabby-ui/components/ui/icons.tsx
+++ b/ee/tabby-ui/components/ui/icons.tsx
@@ -1296,6 +1296,24 @@ function IconBarChart({ className, ...props }: React.ComponentProps<'svg'>) {
   )
 }
 
+function IconActivity({ className, ...props }: React.ComponentProps<'svg'>) {
+  return (
+    <svg
+      xmlns="http://www.w3.org/2000/svg"
+      viewBox="0 0 24 24"
+      fill="none"
+      stroke="currentColor"
+      strokeWidth="2"
+      strokeLinecap="round"
+      strokeLinejoin="round"
+      className={cn('h-4 w-4', className)}
+      {...props}>
+        <path d="M22 12h-4l-3 9L9 3l-3 9H2"/>
+      </svg>
+  )
+}
+
+
 export {
   IconEdit,
   IconNextChat,
@@ -1361,5 +1379,6 @@ export {
   IconHistory,
   IconClock,
   IconStopWatch,
-  IconBarChart
+  IconBarChart,
+  IconActivity
 }

From 4437aaba75cf10d1c6913f987dd615b57845b57c Mon Sep 17 00:00:00 2001
From: wwayne <wayne.wang0821@gmail.com>
Date: Sun, 7 Apr 2024 19:24:28 +0800
Subject: [PATCH 08/16] adjust for different size

---
 .../reports/components/analytic.tsx           | 42 ++++---------------
 ee/tabby-ui/components/activity-calendar.tsx  |  6 ++-
 2 files changed, 14 insertions(+), 34 deletions(-)

diff --git a/ee/tabby-ui/app/(dashboard)/reports/components/analytic.tsx b/ee/tabby-ui/app/(dashboard)/reports/components/analytic.tsx
index f15ac8bdd31f..803425087f16 100644
--- a/ee/tabby-ui/app/(dashboard)/reports/components/analytic.tsx
+++ b/ee/tabby-ui/app/(dashboard)/reports/components/analytic.tsx
@@ -40,7 +40,7 @@ function AnalyticSummary({
   const totalCompletions = sum(dailyStats?.map(stats => stats.completions))
   return (
     <div className="flex w-full items-center justify-center space-x-6 xl:justify-start">
-      <Card className="flex-1 bg-primary-foreground/30">
+      <Card className="flex-1 bg-primary-foreground/30 self-stretch flex flex-col justify-between md:block">
         <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
           <CardTitle className="text-sm font-medium">
             Accept Rate
@@ -55,7 +55,7 @@ function AnalyticSummary({
         </CardContent>
       </Card>
 
-      <Card className="flex-1 bg-primary-foreground/30">
+      <Card className="flex-1 bg-primary-foreground/30 self-stretch flex flex-col justify-between md:block">
         <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
           <CardTitle className="text-sm font-medium">
             Total completions
@@ -70,7 +70,7 @@ function AnalyticSummary({
         </CardContent>
       </Card>
 
-      <Card className="flex-1 bg-primary-foreground/30">
+      <Card className="flex-1 bg-primary-foreground/30 self-stretch flex flex-col justify-between md:block">
         <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
           <CardTitle className="text-sm font-medium">
             Total acceptances
@@ -147,9 +147,9 @@ export function Analytic() {
 
   return (
     <div className="mx-auto max-w-5xl">
-      <div className="mb-3 flex flex-col items-center justify-between gap-y-3 xl:flex-row xl:gap-y-0">
-        <div className="flex flex-col justify-center xl:justify-start">
-          <h1 className="mb-1.5 scroll-m-20 text-center text-4xl font-extrabold tracking-tight lg:text-5xl xl:text-left">
+      <div className="mb-3 flex items-center justify-between ">
+        <div className="flex flex-col ">
+          <h1 className="mb-1.5 scroll-m-20 text-4xl font-extrabold tracking-tight lg:text-5xl">
             Reports
           </h1>
           <p className="text-muted-foreground">
@@ -161,7 +161,7 @@ export function Analytic() {
           defaultValue={KEY_SELECT_ALL}
           onValueChange={setSelectedMember}
         >
-          <SelectTrigger className="w-[300px] lg:w-[150px]">
+          <SelectTrigger className="w-[150px]">
             <div className="flex w-full items-center truncate ">
               <span className="mr-1.5 text-muted-foreground">Member:</span>
               <div className="overflow-hidden text-ellipsis">
@@ -184,7 +184,7 @@ export function Analytic() {
 
       <LoadingWrapper>
         <div className="mb-10 flex flex-col gap-y-5">
-          <div className="flex items-center justify-between">
+          <div className="flex gap-y-1 md:gap-y-0 md:items-center justify-between flex-col md:flex-row">
             <h1 className="text-xl font-semibold">Usage</h1>
             
             <div className="flex items-center gap-x-3">
@@ -194,7 +194,7 @@ export function Analytic() {
                   setSelectedLanguage(value)
                 }
               >
-                <SelectTrigger className="w-[300px] lg:w-[180px]">
+                <SelectTrigger className="w-[180px]">
                   <div className="flex w-full items-center truncate">
                     <span className="mr-1.5 text-muted-foreground">Language:</span>
                     <div className="overflow-hidden text-ellipsis">
@@ -238,30 +238,6 @@ export function Analytic() {
           <AnalyticYearlyCompletion yearlyStats={yearlyStats} />
         </div>
       </LoadingWrapper>
-
-      {/* <div className="flex flex-col gap-y-6 xl:flex-row xl:gap-x-6 xl:gap-y-0">
-        {false && (
-          <div className="flex-1">
-            <LoadingWrapper
-              loading={fetchingDailyState}
-              fallback={<Skeleton className="h-64 w-full" />}
-            >
-              <AnlyticAcceptance
-                dailyStats={dailyStats}
-                dateRange={dateRange}
-              />
-            </LoadingWrapper>
-          </div>
-        )}
-        <div style={{ flex: 3 }}>
-          <LoadingWrapper
-            loading={fetchingDailyState || fetchingYearlyStats}
-            fallback={<Skeleton className="h-64 w-full" />}
-          >
-            
-          </LoadingWrapper>
-        </div>
-      </div> */}
     </div>
   )
 }
diff --git a/ee/tabby-ui/components/activity-calendar.tsx b/ee/tabby-ui/components/activity-calendar.tsx
index ab01762a29d6..cdb76d60c513 100644
--- a/ee/tabby-ui/components/activity-calendar.tsx
+++ b/ee/tabby-ui/components/activity-calendar.tsx
@@ -22,7 +22,11 @@ export default function ActivityCalendar({
   const size = useWindowSize()
   const width = size.width || 0
   const blockSize =
-    width >= 1600 ? 13 : width >= 1400 ? 10 : width >= 1000 ? 8 : 5
+    width >= 1300
+      ? 13
+      : width >= 1000
+        ? 9
+        : 5
 
   return (
     <ReactActivityCalendar

From c1718f2665694ab4e8cd2111545598e5ad6374bf Mon Sep 17 00:00:00 2001
From: wwayne <wayne.wang0821@gmail.com>
Date: Sun, 7 Apr 2024 19:35:03 +0800
Subject: [PATCH 09/16] update

---
 .../reports/components/analytic.tsx           |  74 +++++++-----
 .../reports/components/analyticAccptance.tsx  | 106 ------------------
 .../components/analyticDailyCompletion.tsx    |   2 +-
 .../reports/components/analyticStats.tsx      |  12 --
 4 files changed, 47 insertions(+), 147 deletions(-)
 delete mode 100644 ee/tabby-ui/app/(dashboard)/reports/components/analyticAccptance.tsx
 delete mode 100644 ee/tabby-ui/app/(dashboard)/reports/components/analyticStats.tsx

diff --git a/ee/tabby-ui/app/(dashboard)/reports/components/analytic.tsx b/ee/tabby-ui/app/(dashboard)/reports/components/analytic.tsx
index 803425087f16..899a46e1d598 100644
--- a/ee/tabby-ui/app/(dashboard)/reports/components/analytic.tsx
+++ b/ee/tabby-ui/app/(dashboard)/reports/components/analytic.tsx
@@ -28,6 +28,7 @@ import { AnalyticDailyCompletion } from './analyticDailyCompletion'
 import { AnalyticYearlyCompletion } from './analyticYearlyCompletion'
 
 import type { DailyStats } from '../types/stats'
+import { Skeleton } from '@/components/ui/skeleton'
 
 const INITIAL_DATE_RANGE = 14
 const KEY_SELECT_ALL = 'all'
@@ -40,7 +41,7 @@ function AnalyticSummary({
   const totalCompletions = sum(dailyStats?.map(stats => stats.completions))
   return (
     <div className="flex w-full items-center justify-center space-x-6 xl:justify-start">
-      <Card className="flex-1 bg-primary-foreground/30 self-stretch flex flex-col justify-between md:block">
+      <Card className="flex flex-1 flex-col justify-between self-stretch bg-primary-foreground/30 md:block">
         <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
           <CardTitle className="text-sm font-medium">
             Accept Rate
@@ -55,7 +56,7 @@ function AnalyticSummary({
         </CardContent>
       </Card>
 
-      <Card className="flex-1 bg-primary-foreground/30 self-stretch flex flex-col justify-between md:block">
+      <Card className="flex flex-1 flex-col justify-between self-stretch bg-primary-foreground/30 md:block">
         <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
           <CardTitle className="text-sm font-medium">
             Total completions
@@ -70,7 +71,7 @@ function AnalyticSummary({
         </CardContent>
       </Card>
 
-      <Card className="flex-1 bg-primary-foreground/30 self-stretch flex flex-col justify-between md:block">
+      <Card className="flex flex-1 flex-col justify-between self-stretch bg-primary-foreground/30 md:block">
         <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
           <CardTitle className="text-sm font-medium">
             Total acceptances
@@ -157,34 +158,49 @@ export function Analytic() {
           </p>
         </div>
 
-        <Select
-          defaultValue={KEY_SELECT_ALL}
-          onValueChange={setSelectedMember}
-        >
-          <SelectTrigger className="w-[150px]">
-            <div className="flex w-full items-center truncate ">
-              <span className="mr-1.5 text-muted-foreground">Member:</span>
-              <div className="overflow-hidden text-ellipsis">
-                <SelectValue />
+        <LoadingWrapper
+          loading={fetchingDailyState}
+          fallback={<Skeleton className="h-8 w-32" />}>
+          <Select
+            defaultValue={KEY_SELECT_ALL}
+            onValueChange={setSelectedMember}
+          >
+            <SelectTrigger className="w-[150px]">
+              <div className="flex w-full items-center truncate ">
+                <span className="mr-1.5 text-muted-foreground">Member:</span>
+                <div className="overflow-hidden text-ellipsis">
+                  <SelectValue />
+                </div>
               </div>
-            </div>
-          </SelectTrigger>
-          <SelectContent align='end'>
-            <SelectGroup>
-              <SelectItem value={KEY_SELECT_ALL}>All</SelectItem>
-              {members.map(member => (
-                <SelectItem value={member.id} key={member.id}>
-                  {member.email}
-                </SelectItem>
-              ))}
-            </SelectGroup>
-          </SelectContent>
-        </Select>
+            </SelectTrigger>
+            <SelectContent align='end'>
+              <SelectGroup>
+                <SelectItem value={KEY_SELECT_ALL}>All</SelectItem>
+                {members.map(member => (
+                  <SelectItem value={member.id} key={member.id}>
+                    {member.email}
+                  </SelectItem>
+                ))}
+              </SelectGroup>
+            </SelectContent>
+          </Select>
+        </LoadingWrapper>
       </div>
 
-      <LoadingWrapper>
+      <LoadingWrapper
+        loading={fetchingDailyState}
+        fallback={
+          <div className="flex flex-col gap-5">
+            <div className="flex justify-between gap-5">
+              <Skeleton className="h-32 flex-1" />
+              <Skeleton className="h-32 flex-1" />
+              <Skeleton className="h-32 flex-1" />
+            </div>
+            <Skeleton className="h-56" />
+          </div>
+        }>
         <div className="mb-10 flex flex-col gap-y-5">
-          <div className="flex gap-y-1 md:gap-y-0 md:items-center justify-between flex-col md:flex-row">
+          <div className="flex flex-col justify-between gap-y-1 md:flex-row md:items-center md:gap-y-0">
             <h1 className="text-xl font-semibold">Usage</h1>
             
             <div className="flex items-center gap-x-3">
@@ -232,7 +248,9 @@ export function Analytic() {
         </div>
       </LoadingWrapper>
       
-      <LoadingWrapper>
+      <LoadingWrapper
+        loading={fetchingYearlyStats}
+        fallback={<Skeleton className="mt-5 h-48" />}>
         <div className="mb-10">
           <h1 className="mb-3 text-xl font-semibold">Activity</h1>
           <AnalyticYearlyCompletion yearlyStats={yearlyStats} />
diff --git a/ee/tabby-ui/app/(dashboard)/reports/components/analyticAccptance.tsx b/ee/tabby-ui/app/(dashboard)/reports/components/analyticAccptance.tsx
deleted file mode 100644
index 61e79f072a42..000000000000
--- a/ee/tabby-ui/app/(dashboard)/reports/components/analyticAccptance.tsx
+++ /dev/null
@@ -1,106 +0,0 @@
-'use client'
-
-import moment from 'moment'
-import type { DateRange } from 'react-day-picker'
-import { Cell, Legend, Pie, PieChart, ResponsiveContainer } from 'recharts'
-
-import type { DailyStats } from '../types/stats'
-
-export function AnlyticAcceptance({
-  dailyStats,
-  dateRange
-}: {
-  dailyStats: DailyStats[] | undefined
-  dateRange: DateRange
-}) {
-  const from = dateRange.from || new Date()
-  const to = dateRange.to || from
-
-  let totalAccpet = 0
-  let totalCompletions = 0
-  dailyStats?.forEach(stats => {
-    totalCompletions += stats.completions
-    totalAccpet += stats.selects
-  })
-  const totalPending = totalCompletions - totalAccpet
-
-  const data = [
-    { name: 'Accept', value: totalAccpet },
-    { name: 'Pending', value: totalPending }
-  ]
-
-  const COLORS = ['#8884d8', '#b9b7e2']
-
-  const RADIAN = Math.PI / 180
-  const renderCustomizedLabel = ({
-    cx,
-    cy,
-    midAngle,
-    innerRadius,
-    outerRadius,
-    percent,
-    name
-  }: {
-    cx: number
-    cy: number
-    midAngle: number
-    innerRadius: number
-    outerRadius: number
-    percent: number
-    name: string
-  }) => {
-    const radius = innerRadius + (outerRadius - innerRadius) * 0.5
-    const x = cx + radius * Math.cos(-midAngle * RADIAN)
-    const y = cy + radius * Math.sin(-midAngle * RADIAN)
-
-    if (name.toLocaleLowerCase() === 'accept') {
-      return (
-        <text
-          x={x}
-          y={y}
-          fill="white"
-          textAnchor={x > cx ? 'start' : 'end'}
-          dominantBaseline="central"
-          fontSize={12}
-        >
-          {`${(percent * 100).toFixed(0)}%`}
-        </text>
-      )
-    }
-    return
-  }
-
-  return (
-    <div className="rounded-lg border bg-primary-foreground/30 p-4">
-      <h1 className="text-xl font-bold">Acceptance</h1>
-      <p className="mt-0.5 text-xs text-muted-foreground">
-        {moment(from).format('D MMM, YYYY')} -{' '}
-        {moment(to).format('D MMM, YYYY')}
-      </p>
-      <ResponsiveContainer width="100%" height={210}>
-        <PieChart width={700} height={400}>
-          <Pie
-            data={data}
-            cx="50%"
-            cy="50%"
-            labelLine={false}
-            label={renderCustomizedLabel}
-            outerRadius={80}
-            fill="#8884d8"
-            dataKey="value"
-          >
-            {data.map((entry, index) => (
-              <Cell
-                key={`cell-${index}`}
-                fill={COLORS[index % COLORS.length]}
-              />
-            ))}
-          </Pie>
-          {totalCompletions !== 0 && (
-            <Legend wrapperStyle={{ fontSize: '12px' }} />
-          )}
-        </PieChart>
-      </ResponsiveContainer>
-    </div>
-  )
-}
diff --git a/ee/tabby-ui/app/(dashboard)/reports/components/analyticDailyCompletion.tsx b/ee/tabby-ui/app/(dashboard)/reports/components/analyticDailyCompletion.tsx
index a8c7dbc6c977..6a53c0a721d7 100644
--- a/ee/tabby-ui/app/(dashboard)/reports/components/analyticDailyCompletion.tsx
+++ b/ee/tabby-ui/app/(dashboard)/reports/components/analyticDailyCompletion.tsx
@@ -110,7 +110,7 @@ export function AnalyticDailyCompletion({
           <Bar dataKey="select" stackId="stats" fill={theme === 'dark' ? '#e8e1d3' : '#54452c'} radius={3} />
           <Bar dataKey="pending" stackId="stats" fill={theme === 'dark' ? '#423929' : '#e8e1d3'} radius={3} />
           <XAxis dataKey="name" fontSize={12} />
-          <YAxis fontSize={12} width={20} />
+          <YAxis fontSize={12} width={20} allowDecimals={false} />
           <Tooltip cursor={{ fill: 'transparent' }} content={<BarTooltip />} />
         </BarChart>
       </ResponsiveContainer>
diff --git a/ee/tabby-ui/app/(dashboard)/reports/components/analyticStats.tsx b/ee/tabby-ui/app/(dashboard)/reports/components/analyticStats.tsx
deleted file mode 100644
index afae66fff980..000000000000
--- a/ee/tabby-ui/app/(dashboard)/reports/components/analyticStats.tsx
+++ /dev/null
@@ -1,12 +0,0 @@
-import type { DateRange } from 'react-day-picker'
-import type { DailyStats } from '../types/stats'
-
-export function AnalyticStats ({
-  dailyStats,
-  dateRange
-}: {
-  dailyStats: DailyStats[] | undefined
-  dateRange: DateRange
-}) {
-
-}
\ No newline at end of file

From 35120f6e66f5477f9861998b87dbe80cdc00ca7f Mon Sep 17 00:00:00 2001
From: wwayne <wayne.wang0821@gmail.com>
Date: Mon, 8 Apr 2024 12:15:28 +0800
Subject: [PATCH 10/16] update

---
 ...arlyCompletion.tsx => annual-activity.tsx} | 43 ++++++++++-
 ...DailyCompletion.tsx => daily-activity.tsx} |  2 +-
 .../components/{analytic.tsx => report.tsx}   | 75 +++++++++----------
 ee/tabby-ui/app/(dashboard)/reports/page.tsx  |  6 +-
 .../reports/{hooks => }/use-all-members.tsx   |  0
 ee/tabby-ui/components/activity-calendar.tsx  | 44 -----------
 .../components/{ => ui}/date-range-picker.tsx |  0
 ee/tabby-ui/components/ui/icons.tsx           |  6 +-
 8 files changed, 80 insertions(+), 96 deletions(-)
 rename ee/tabby-ui/app/(dashboard)/reports/components/{analyticYearlyCompletion.tsx => annual-activity.tsx} (56%)
 rename ee/tabby-ui/app/(dashboard)/reports/components/{analyticDailyCompletion.tsx => daily-activity.tsx} (98%)
 rename ee/tabby-ui/app/(dashboard)/reports/components/{analytic.tsx => report.tsx} (82%)
 rename ee/tabby-ui/app/(dashboard)/reports/{hooks => }/use-all-members.tsx (100%)
 delete mode 100644 ee/tabby-ui/components/activity-calendar.tsx
 rename ee/tabby-ui/components/{ => ui}/date-range-picker.tsx (100%)

diff --git a/ee/tabby-ui/app/(dashboard)/reports/components/analyticYearlyCompletion.tsx b/ee/tabby-ui/app/(dashboard)/reports/components/annual-activity.tsx
similarity index 56%
rename from ee/tabby-ui/app/(dashboard)/reports/components/analyticYearlyCompletion.tsx
rename to ee/tabby-ui/app/(dashboard)/reports/components/annual-activity.tsx
index d46c34f2295d..037123fa6202 100644
--- a/ee/tabby-ui/app/(dashboard)/reports/components/analyticYearlyCompletion.tsx
+++ b/ee/tabby-ui/app/(dashboard)/reports/components/annual-activity.tsx
@@ -1,10 +1,47 @@
-import moment from 'moment'
+'use client'
 
-import ActivityCalendar from '@/components/activity-calendar'
+import moment from 'moment'
+import { useWindowSize } from '@uidotdev/usehooks'
+import { useTheme } from 'next-themes'
+import ReactActivityCalendar from 'react-activity-calendar'
 
 import type { DailyStats } from '../types/stats'
 
-export function AnalyticYearlyCompletion({
+function ActivityCalendar({
+  data
+}: {
+  data: {
+    date: string
+    count: number
+    level: number
+  }[]
+}) {
+  const { theme } = useTheme()
+  const size = useWindowSize()
+  const width = size.width || 0
+  const blockSize =
+    width >= 1300
+      ? 13
+      : width >= 1000
+        ? 9
+        : 5
+
+  return (
+    <ReactActivityCalendar
+      data={data}
+      colorScheme={theme === 'dark' ? 'dark' : 'light'}
+      theme={{
+        light: ['#ebedf0', '#9be9a8', '#40c463', '#30a14e', '#216e39'],
+        dark: ['rgb(45, 51, 59)', '#0e4429', '#006d32', '#26a641', '#39d353']
+      }}
+      blockSize={blockSize}
+      hideTotalCount
+      showWeekdayLabels
+    />
+  )
+}
+
+export function AnnualActivity({
   yearlyStats
 }: {
   yearlyStats: DailyStats[] | undefined
diff --git a/ee/tabby-ui/app/(dashboard)/reports/components/analyticDailyCompletion.tsx b/ee/tabby-ui/app/(dashboard)/reports/components/daily-activity.tsx
similarity index 98%
rename from ee/tabby-ui/app/(dashboard)/reports/components/analyticDailyCompletion.tsx
rename to ee/tabby-ui/app/(dashboard)/reports/components/daily-activity.tsx
index 6a53c0a721d7..52fa36d8c815 100644
--- a/ee/tabby-ui/app/(dashboard)/reports/components/analyticDailyCompletion.tsx
+++ b/ee/tabby-ui/app/(dashboard)/reports/components/daily-activity.tsx
@@ -56,7 +56,7 @@ function BarTooltip({
   return null
 }
 
-export function AnalyticDailyCompletion({
+export function DailyActivity({
   dailyStats,
   dateRange
 }: {
diff --git a/ee/tabby-ui/app/(dashboard)/reports/components/analytic.tsx b/ee/tabby-ui/app/(dashboard)/reports/components/report.tsx
similarity index 82%
rename from ee/tabby-ui/app/(dashboard)/reports/components/analytic.tsx
rename to ee/tabby-ui/app/(dashboard)/reports/components/report.tsx
index 899a46e1d598..aeb9db176bc3 100644
--- a/ee/tabby-ui/app/(dashboard)/reports/components/analytic.tsx
+++ b/ee/tabby-ui/app/(dashboard)/reports/components/report.tsx
@@ -8,10 +8,10 @@ import { DateRange } from 'react-day-picker'
 import { useQuery } from 'urql'
 
 import { Language } from '@/lib/gql/generates/graphql'
-import { useAllMembers } from '../hooks/use-all-members'
+import { useAllMembers } from '../use-all-members'
 import { queryDailyStats, queryDailyStatsInPastYear } from '../query'
 
-import { IconActivity, IconCode, IconCheck } from '@/components/ui/icons'
+import { IconActivity, IconCode, IconCheck, IconUsers } from '@/components/ui/icons'
 import {
   Select,
   SelectContent,
@@ -20,12 +20,12 @@ import {
   SelectTrigger,
   SelectValue
 } from '@/components/ui/select'
-import DatePickerWithRange from '@/components/date-range-picker'
+import DatePickerWithRange from '@/components/ui/date-range-picker'
 import LoadingWrapper from '@/components/loading-wrapper'
 import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/card'
 
-import { AnalyticDailyCompletion } from './analyticDailyCompletion'
-import { AnalyticYearlyCompletion } from './analyticYearlyCompletion'
+import { DailyActivity } from './daily-activity'
+import { AnnualActivity } from './annual-activity'
 
 import type { DailyStats } from '../types/stats'
 import { Skeleton } from '@/components/ui/skeleton'
@@ -33,12 +33,16 @@ import { Skeleton } from '@/components/ui/skeleton'
 const INITIAL_DATE_RANGE = 14
 const KEY_SELECT_ALL = 'all'
 
-function AnalyticSummary({
+function StatsSummary({
   dailyStats
 }: {
   dailyStats: DailyStats[] | undefined
 }) {
   const totalCompletions = sum(dailyStats?.map(stats => stats.completions))
+  const totalAcceptances = sum(dailyStats?.map(stats => stats.selects))
+  const acceptRate = totalAcceptances === 0
+    ? 0
+    : Math.round((totalAcceptances / totalCompletions)* 100)
   return (
     <div className="flex w-full items-center justify-center space-x-6 xl:justify-start">
       <Card className="flex flex-1 flex-col justify-between self-stretch bg-primary-foreground/30 md:block">
@@ -49,10 +53,7 @@ function AnalyticSummary({
           <IconActivity className="text-muted-foreground" />
         </CardHeader>
         <CardContent>
-          <div className="text-2xl font-bold">TBD</div>
-          <p className="text-xs text-muted-foreground">
-            +TBD from last week
-          </p>
+          <div className="text-2xl font-bold">{acceptRate}%</div>
         </CardContent>
       </Card>
 
@@ -65,9 +66,6 @@ function AnalyticSummary({
         </CardHeader>
         <CardContent>
           <div className="text-2xl font-bold">{numeral(totalCompletions).format('0,0')}</div>
-          <p className="text-xs text-muted-foreground">
-            +TBD from last week
-          </p>
         </CardContent>
       </Card>
 
@@ -79,17 +77,14 @@ function AnalyticSummary({
           <IconCheck className="h-4 w-4 text-muted-foreground" />
         </CardHeader>
         <CardContent>
-          <div className="text-2xl font-bold">TBD</div>
-          <p className="text-xs text-muted-foreground">
-            +TBD from last week
-          </p>
+          <div className="text-2xl font-bold">{totalAcceptances}</div>
         </CardContent>
       </Card>
     </div>
   )
 }
 
-export function Analytic() {
+export function Report() {
   const [members] = useAllMembers()
   const [dateRange, setDateRange] = useState<DateRange>({
     from: moment().subtract(INITIAL_DATE_RANGE, 'day').toDate(),
@@ -148,7 +143,7 @@ export function Analytic() {
 
   return (
     <div className="mx-auto max-w-5xl">
-      <div className="mb-3 flex items-center justify-between ">
+      <div className="mb-3 flex items-center justify-between md:items-end">
         <div className="flex flex-col ">
           <h1 className="mb-1.5 scroll-m-20 text-4xl font-extrabold tracking-tight lg:text-5xl">
             Reports
@@ -157,21 +152,19 @@ export function Analytic() {
             Statistics around Tabby IDE / Extensions
           </p>
         </div>
-
-        <LoadingWrapper
-          loading={fetchingDailyState}
-          fallback={<Skeleton className="h-8 w-32" />}>
+        
+        {!fetchingDailyState &&
           <Select
             defaultValue={KEY_SELECT_ALL}
             onValueChange={setSelectedMember}
           >
-            <SelectTrigger className="w-[150px]">
-              <div className="flex w-full items-center truncate ">
-                <span className="mr-1.5 text-muted-foreground">Member:</span>
-                <div className="overflow-hidden text-ellipsis">
+            <SelectTrigger
+              className="w-auto border-none shadow-none">
+                <div className="flex items-center pr-3">
+                  <IconUsers className="mr-1" />
+                  <p className="mr-1.5">Member:</p>
                   <SelectValue />
                 </div>
-              </div>
             </SelectTrigger>
             <SelectContent align='end'>
               <SelectGroup>
@@ -184,9 +177,18 @@ export function Analytic() {
               </SelectGroup>
             </SelectContent>
           </Select>
-        </LoadingWrapper>
+        }
       </div>
 
+      <LoadingWrapper
+        loading={fetchingYearlyStats}
+        fallback={<Skeleton className="mb-8 h-48" />}>
+        <div className="mb-10">
+          <h1 className="mb-3 text-xl font-semibold">Activity</h1>
+          <AnnualActivity yearlyStats={yearlyStats} />
+        </div>
+      </LoadingWrapper>
+
       <LoadingWrapper
         loading={fetchingDailyState}
         fallback={
@@ -200,7 +202,7 @@ export function Analytic() {
           </div>
         }>
         <div className="mb-10 flex flex-col gap-y-5">
-          <div className="flex flex-col justify-between gap-y-1 md:flex-row md:items-center md:gap-y-0">
+          <div className="flex flex-col justify-between gap-y-1 md:flex-row md:items-end md:gap-y-0 ">
             <h1 className="text-xl font-semibold">Usage</h1>
             
             <div className="flex items-center gap-x-3">
@@ -239,23 +241,14 @@ export function Analytic() {
             </div>
           </div>
 
-          <AnalyticSummary dailyStats={dailyStats} />
+          <StatsSummary dailyStats={dailyStats} />
 
-          <AnalyticDailyCompletion
+          <DailyActivity
             dailyStats={dailyStats}
             dateRange={dateRange}
           />
         </div>
       </LoadingWrapper>
-      
-      <LoadingWrapper
-        loading={fetchingYearlyStats}
-        fallback={<Skeleton className="mt-5 h-48" />}>
-        <div className="mb-10">
-          <h1 className="mb-3 text-xl font-semibold">Activity</h1>
-          <AnalyticYearlyCompletion yearlyStats={yearlyStats} />
-        </div>
-      </LoadingWrapper>
     </div>
   )
 }
diff --git a/ee/tabby-ui/app/(dashboard)/reports/page.tsx b/ee/tabby-ui/app/(dashboard)/reports/page.tsx
index 16dd86eb4e97..ba6d8d8083c9 100644
--- a/ee/tabby-ui/app/(dashboard)/reports/page.tsx
+++ b/ee/tabby-ui/app/(dashboard)/reports/page.tsx
@@ -1,11 +1,11 @@
 import { Metadata } from 'next'
 
-import { Analytic } from './components/analytic'
+import { Report } from './components/report'
 
 export const metadata: Metadata = {
-  title: 'Analytics Dashboard'
+  title: 'Reports'
 }
 
 export default function Page() {
-  return <Analytic />
+  return <Report />
 }
diff --git a/ee/tabby-ui/app/(dashboard)/reports/hooks/use-all-members.tsx b/ee/tabby-ui/app/(dashboard)/reports/use-all-members.tsx
similarity index 100%
rename from ee/tabby-ui/app/(dashboard)/reports/hooks/use-all-members.tsx
rename to ee/tabby-ui/app/(dashboard)/reports/use-all-members.tsx
diff --git a/ee/tabby-ui/components/activity-calendar.tsx b/ee/tabby-ui/components/activity-calendar.tsx
deleted file mode 100644
index cdb76d60c513..000000000000
--- a/ee/tabby-ui/components/activity-calendar.tsx
+++ /dev/null
@@ -1,44 +0,0 @@
-'use client'
-
-import dynamic from 'next/dynamic'
-import { useWindowSize } from '@uidotdev/usehooks'
-import { useTheme } from 'next-themes'
-
-// withou using dynamic, we got error "Error: calcTextDimensions() requires browser APIs"
-const ReactActivityCalendar = dynamic(() => import('react-activity-calendar'), {
-  ssr: false
-})
-
-export default function ActivityCalendar({
-  data
-}: {
-  data: {
-    date: string
-    count: number
-    level: number
-  }[]
-}) {
-  const { theme } = useTheme()
-  const size = useWindowSize()
-  const width = size.width || 0
-  const blockSize =
-    width >= 1300
-      ? 13
-      : width >= 1000
-        ? 9
-        : 5
-
-  return (
-    <ReactActivityCalendar
-      data={data}
-      colorScheme={theme === 'dark' ? 'dark' : 'light'}
-      theme={{
-        light: ['#ebedf0', '#9be9a8', '#40c463', '#30a14e', '#216e39'],
-        dark: ['rgb(45, 51, 59)', '#0e4429', '#006d32', '#26a641', '#39d353']
-      }}
-      blockSize={blockSize}
-      hideTotalCount
-      showWeekdayLabels
-    />
-  )
-}
diff --git a/ee/tabby-ui/components/date-range-picker.tsx b/ee/tabby-ui/components/ui/date-range-picker.tsx
similarity index 100%
rename from ee/tabby-ui/components/date-range-picker.tsx
rename to ee/tabby-ui/components/ui/date-range-picker.tsx
diff --git a/ee/tabby-ui/components/ui/icons.tsx b/ee/tabby-ui/components/ui/icons.tsx
index dc53281e45c1..0a886cc735e4 100644
--- a/ee/tabby-ui/components/ui/icons.tsx
+++ b/ee/tabby-ui/components/ui/icons.tsx
@@ -1,3 +1,5 @@
+// FIXME(wwayne): Review each icons and consider re-export from `lucide-react`.
+
 'use client'
 
 import * as React from 'react'
@@ -184,8 +186,6 @@ function IconArrowRight({ className, ...props }: React.ComponentProps<'svg'>) {
   )
 }
 
-// FIXME: Remove to use ChevronRight from lucide-react
-// currently ui/calendar is using ChevronRight as the default option.
 function IconChevronRight({
   className,
   ...props
@@ -207,8 +207,6 @@ function IconChevronRight({
   )
 }
 
-// FIXME: Remove to use ChevronLeft from lucide-react 
-// currently ui/calendar is using ChevronLeft as the default option.
 function IconChevronLeft({ className, ...props }: React.ComponentProps<'svg'>) {
   return (
     <svg

From d46842964f1f270ceef73d47ed71f5a930ad5d67 Mon Sep 17 00:00:00 2001
From: wwayne <wayne.wang0821@gmail.com>
Date: Mon, 8 Apr 2024 12:28:03 +0800
Subject: [PATCH 11/16] update

---
 .../(dashboard)/reports/components/report.tsx    | 16 ++++++++++------
 1 file changed, 10 insertions(+), 6 deletions(-)

diff --git a/ee/tabby-ui/app/(dashboard)/reports/components/report.tsx b/ee/tabby-ui/app/(dashboard)/reports/components/report.tsx
index aeb9db176bc3..77ed467c879b 100644
--- a/ee/tabby-ui/app/(dashboard)/reports/components/report.tsx
+++ b/ee/tabby-ui/app/(dashboard)/reports/components/report.tsx
@@ -143,7 +143,7 @@ export function Report() {
 
   return (
     <div className="mx-auto max-w-5xl">
-      <div className="mb-3 flex items-center justify-between md:items-end">
+      <div className="mb-3 flex items-end justify-between">
         <div className="flex flex-col ">
           <h1 className="mb-1.5 scroll-m-20 text-4xl font-extrabold tracking-tight lg:text-5xl">
             Reports
@@ -153,17 +153,21 @@ export function Report() {
           </p>
         </div>
         
-        {!fetchingDailyState &&
+        <LoadingWrapper
+          loading={fetchingDailyState}
+          fallback={<Skeleton className="h-8 w-32" />}>
           <Select
             defaultValue={KEY_SELECT_ALL}
             onValueChange={setSelectedMember}
           >
             <SelectTrigger
-              className="w-auto border-none shadow-none">
-                <div className="flex items-center pr-3">
+              className="h-auto w-auto border-none py-0 shadow-none">
+                <div className="flex h-6 items-center pr-3">
                   <IconUsers className="mr-1" />
                   <p className="mr-1.5">Member:</p>
-                  <SelectValue />
+                  <div className="max-w-[80px] overflow-hidden text-ellipsis">
+                    <SelectValue />
+                  </div>
                 </div>
             </SelectTrigger>
             <SelectContent align='end'>
@@ -177,7 +181,7 @@ export function Report() {
               </SelectGroup>
             </SelectContent>
           </Select>
-        }
+          </LoadingWrapper>
       </div>
 
       <LoadingWrapper

From 395dd1f482765f4b5317b5b37c8000b3a4aaaf64 Mon Sep 17 00:00:00 2001
From: wwayne <wayne.wang0821@gmail.com>
Date: Mon, 8 Apr 2024 12:38:14 +0800
Subject: [PATCH 12/16] update

---
 ee/tabby-ui/app/(dashboard)/reports/components/report.tsx | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/ee/tabby-ui/app/(dashboard)/reports/components/report.tsx b/ee/tabby-ui/app/(dashboard)/reports/components/report.tsx
index 77ed467c879b..3940224a5b5b 100644
--- a/ee/tabby-ui/app/(dashboard)/reports/components/report.tsx
+++ b/ee/tabby-ui/app/(dashboard)/reports/components/report.tsx
@@ -144,7 +144,7 @@ export function Report() {
   return (
     <div className="mx-auto max-w-5xl">
       <div className="mb-3 flex items-end justify-between">
-        <div className="flex flex-col ">
+        <div className="flex flex-col">
           <h1 className="mb-1.5 scroll-m-20 text-4xl font-extrabold tracking-tight lg:text-5xl">
             Reports
           </h1>
@@ -187,8 +187,8 @@ export function Report() {
       <LoadingWrapper
         loading={fetchingYearlyStats}
         fallback={<Skeleton className="mb-8 h-48" />}>
-        <div className="mb-10">
-          <h1 className="mb-3 text-xl font-semibold">Activity</h1>
+        <div className="mb-8">
+          <h1 className="mb-2 text-xl font-semibold">Activity</h1>
           <AnnualActivity yearlyStats={yearlyStats} />
         </div>
       </LoadingWrapper>
@@ -206,7 +206,7 @@ export function Report() {
           </div>
         }>
         <div className="mb-10 flex flex-col gap-y-5">
-          <div className="flex flex-col justify-between gap-y-1 md:flex-row md:items-end md:gap-y-0 ">
+          <div className="-mb-2 flex flex-col justify-between gap-y-1 md:flex-row md:items-end md:gap-y-0">
             <h1 className="text-xl font-semibold">Usage</h1>
             
             <div className="flex items-center gap-x-3">

From e2235159a0271035edd31214626f39bc6335326e Mon Sep 17 00:00:00 2001
From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com>
Date: Mon, 8 Apr 2024 04:39:35 +0000
Subject: [PATCH 13/16] [autofix.ci] apply automated fixes

---
 .../reports/components/annual-activity.tsx    |  9 +--
 .../reports/components/daily-activity.tsx     | 43 ++++++----
 .../(dashboard)/reports/components/report.tsx | 79 ++++++++++---------
 ee/tabby-ui/components/ui/icons.tsx           |  8 +-
 4 files changed, 75 insertions(+), 64 deletions(-)

diff --git a/ee/tabby-ui/app/(dashboard)/reports/components/annual-activity.tsx b/ee/tabby-ui/app/(dashboard)/reports/components/annual-activity.tsx
index 037123fa6202..437bb422180b 100644
--- a/ee/tabby-ui/app/(dashboard)/reports/components/annual-activity.tsx
+++ b/ee/tabby-ui/app/(dashboard)/reports/components/annual-activity.tsx
@@ -1,7 +1,7 @@
 'use client'
 
-import moment from 'moment'
 import { useWindowSize } from '@uidotdev/usehooks'
+import moment from 'moment'
 import { useTheme } from 'next-themes'
 import ReactActivityCalendar from 'react-activity-calendar'
 
@@ -19,12 +19,7 @@ function ActivityCalendar({
   const { theme } = useTheme()
   const size = useWindowSize()
   const width = size.width || 0
-  const blockSize =
-    width >= 1300
-      ? 13
-      : width >= 1000
-        ? 9
-        : 5
+  const blockSize = width >= 1300 ? 13 : width >= 1000 ? 9 : 5
 
   return (
     <ReactActivityCalendar
diff --git a/ee/tabby-ui/app/(dashboard)/reports/components/daily-activity.tsx b/ee/tabby-ui/app/(dashboard)/reports/components/daily-activity.tsx
index 52fa36d8c815..ea5af8a18a3b 100644
--- a/ee/tabby-ui/app/(dashboard)/reports/components/daily-activity.tsx
+++ b/ee/tabby-ui/app/(dashboard)/reports/components/daily-activity.tsx
@@ -1,10 +1,10 @@
 'use client'
 
 import { eachDayOfInterval } from 'date-fns'
-import moment from 'moment'
 import { sum } from 'lodash-es'
+import moment from 'moment'
 import { useTheme } from 'next-themes'
-
+import type { DateRange } from 'react-day-picker'
 import {
   Bar,
   BarChart,
@@ -13,9 +13,9 @@ import {
   XAxis,
   YAxis
 } from 'recharts'
+
 import { Card, CardContent } from '@/components/ui/card'
 
-import type { DateRange } from 'react-day-picker'
 import type { DailyStats } from '../types/stats'
 
 function BarTooltip({
@@ -23,29 +23,30 @@ function BarTooltip({
   payload,
   label
 }: {
-  active?: boolean;
-  label?: string;
+  active?: boolean
+  label?: string
   payload?: {
-    name: string;
+    name: string
     payload: {
-      completion: number;
-      select: number;
-      pending: number;
-    };
-    
+      completion: number
+      select: number
+      pending: number
+    }
   }[]
 }) {
   if (active && payload && payload.length) {
-    const {completion, select} = payload[0].payload
+    const { completion, select } = payload[0].payload
     if (!completion) return null
     return (
       <Card>
         <CardContent className="flex flex-col gap-y-0.5 px-4 py-2 text-sm">
           <p className="flex items-center">
-            <span className="mr-3 inline-block w-20">Completion:</span><b>{completion}</b>
+            <span className="mr-3 inline-block w-20">Completion:</span>
+            <b>{completion}</b>
           </p>
           <p className="flex items-center">
-          <span className="mr-3 inline-block w-20">Acceptance:</span><b>{select}</b>
+            <span className="mr-3 inline-block w-20">Acceptance:</span>
+            <b>{select}</b>
           </p>
           <p className="text-muted-foreground">{label}</p>
         </CardContent>
@@ -107,8 +108,18 @@ export function DailyActivity({
             bottom: 5
           }}
         >
-          <Bar dataKey="select" stackId="stats" fill={theme === 'dark' ? '#e8e1d3' : '#54452c'} radius={3} />
-          <Bar dataKey="pending" stackId="stats" fill={theme === 'dark' ? '#423929' : '#e8e1d3'} radius={3} />
+          <Bar
+            dataKey="select"
+            stackId="stats"
+            fill={theme === 'dark' ? '#e8e1d3' : '#54452c'}
+            radius={3}
+          />
+          <Bar
+            dataKey="pending"
+            stackId="stats"
+            fill={theme === 'dark' ? '#423929' : '#e8e1d3'}
+            radius={3}
+          />
           <XAxis dataKey="name" fontSize={12} />
           <YAxis fontSize={12} width={20} allowDecimals={false} />
           <Tooltip cursor={{ fill: 'transparent' }} content={<BarTooltip />} />
diff --git a/ee/tabby-ui/app/(dashboard)/reports/components/report.tsx b/ee/tabby-ui/app/(dashboard)/reports/components/report.tsx
index 3940224a5b5b..3449d076e0fd 100644
--- a/ee/tabby-ui/app/(dashboard)/reports/components/report.tsx
+++ b/ee/tabby-ui/app/(dashboard)/reports/components/report.tsx
@@ -8,10 +8,14 @@ import { DateRange } from 'react-day-picker'
 import { useQuery } from 'urql'
 
 import { Language } from '@/lib/gql/generates/graphql'
-import { useAllMembers } from '../use-all-members'
-import { queryDailyStats, queryDailyStatsInPastYear } from '../query'
-
-import { IconActivity, IconCode, IconCheck, IconUsers } from '@/components/ui/icons'
+import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
+import DatePickerWithRange from '@/components/ui/date-range-picker'
+import {
+  IconActivity,
+  IconCheck,
+  IconCode,
+  IconUsers
+} from '@/components/ui/icons'
 import {
   Select,
   SelectContent,
@@ -20,15 +24,14 @@ import {
   SelectTrigger,
   SelectValue
 } from '@/components/ui/select'
-import DatePickerWithRange from '@/components/ui/date-range-picker'
+import { Skeleton } from '@/components/ui/skeleton'
 import LoadingWrapper from '@/components/loading-wrapper'
-import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/card'
-
-import { DailyActivity } from './daily-activity'
-import { AnnualActivity } from './annual-activity'
 
+import { queryDailyStats, queryDailyStatsInPastYear } from '../query'
 import type { DailyStats } from '../types/stats'
-import { Skeleton } from '@/components/ui/skeleton'
+import { useAllMembers } from '../use-all-members'
+import { AnnualActivity } from './annual-activity'
+import { DailyActivity } from './daily-activity'
 
 const INITIAL_DATE_RANGE = 14
 const KEY_SELECT_ALL = 'all'
@@ -40,16 +43,15 @@ function StatsSummary({
 }) {
   const totalCompletions = sum(dailyStats?.map(stats => stats.completions))
   const totalAcceptances = sum(dailyStats?.map(stats => stats.selects))
-  const acceptRate = totalAcceptances === 0
-    ? 0
-    : Math.round((totalAcceptances / totalCompletions)* 100)
+  const acceptRate =
+    totalAcceptances === 0
+      ? 0
+      : Math.round((totalAcceptances / totalCompletions) * 100)
   return (
     <div className="flex w-full items-center justify-center space-x-6 xl:justify-start">
       <Card className="flex flex-1 flex-col justify-between self-stretch bg-primary-foreground/30 md:block">
         <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
-          <CardTitle className="text-sm font-medium">
-            Accept Rate
-          </CardTitle>
+          <CardTitle className="text-sm font-medium">Accept Rate</CardTitle>
           <IconActivity className="text-muted-foreground" />
         </CardHeader>
         <CardContent>
@@ -65,7 +67,9 @@ function StatsSummary({
           <IconCode className="text-muted-foreground" />
         </CardHeader>
         <CardContent>
-          <div className="text-2xl font-bold">{numeral(totalCompletions).format('0,0')}</div>
+          <div className="text-2xl font-bold">
+            {numeral(totalCompletions).format('0,0')}
+          </div>
         </CardContent>
       </Card>
 
@@ -152,25 +156,25 @@ export function Report() {
             Statistics around Tabby IDE / Extensions
           </p>
         </div>
-        
+
         <LoadingWrapper
           loading={fetchingDailyState}
-          fallback={<Skeleton className="h-8 w-32" />}>
+          fallback={<Skeleton className="h-8 w-32" />}
+        >
           <Select
             defaultValue={KEY_SELECT_ALL}
             onValueChange={setSelectedMember}
           >
-            <SelectTrigger
-              className="h-auto w-auto border-none py-0 shadow-none">
-                <div className="flex h-6 items-center pr-3">
-                  <IconUsers className="mr-1" />
-                  <p className="mr-1.5">Member:</p>
-                  <div className="max-w-[80px] overflow-hidden text-ellipsis">
-                    <SelectValue />
-                  </div>
+            <SelectTrigger className="h-auto w-auto border-none py-0 shadow-none">
+              <div className="flex h-6 items-center pr-3">
+                <IconUsers className="mr-1" />
+                <p className="mr-1.5">Member:</p>
+                <div className="max-w-[80px] overflow-hidden text-ellipsis">
+                  <SelectValue />
                 </div>
+              </div>
             </SelectTrigger>
-            <SelectContent align='end'>
+            <SelectContent align="end">
               <SelectGroup>
                 <SelectItem value={KEY_SELECT_ALL}>All</SelectItem>
                 {members.map(member => (
@@ -181,12 +185,13 @@ export function Report() {
               </SelectGroup>
             </SelectContent>
           </Select>
-          </LoadingWrapper>
+        </LoadingWrapper>
       </div>
 
       <LoadingWrapper
         loading={fetchingYearlyStats}
-        fallback={<Skeleton className="mb-8 h-48" />}>
+        fallback={<Skeleton className="mb-8 h-48" />}
+      >
         <div className="mb-8">
           <h1 className="mb-2 text-xl font-semibold">Activity</h1>
           <AnnualActivity yearlyStats={yearlyStats} />
@@ -204,11 +209,12 @@ export function Report() {
             </div>
             <Skeleton className="h-56" />
           </div>
-        }>
+        }
+      >
         <div className="mb-10 flex flex-col gap-y-5">
           <div className="-mb-2 flex flex-col justify-between gap-y-1 md:flex-row md:items-end md:gap-y-0">
             <h1 className="text-xl font-semibold">Usage</h1>
-            
+
             <div className="flex items-center gap-x-3">
               <Select
                 defaultValue={KEY_SELECT_ALL}
@@ -218,7 +224,9 @@ export function Report() {
               >
                 <SelectTrigger className="w-[180px]">
                   <div className="flex w-full items-center truncate">
-                    <span className="mr-1.5 text-muted-foreground">Language:</span>
+                    <span className="mr-1.5 text-muted-foreground">
+                      Language:
+                    </span>
                     <div className="overflow-hidden text-ellipsis">
                       <SelectValue />
                     </div>
@@ -247,10 +255,7 @@ export function Report() {
 
           <StatsSummary dailyStats={dailyStats} />
 
-          <DailyActivity
-            dailyStats={dailyStats}
-            dateRange={dateRange}
-          />
+          <DailyActivity dailyStats={dailyStats} dateRange={dateRange} />
         </div>
       </LoadingWrapper>
     </div>
diff --git a/ee/tabby-ui/components/ui/icons.tsx b/ee/tabby-ui/components/ui/icons.tsx
index 0a886cc735e4..60cee055a3f4 100644
--- a/ee/tabby-ui/components/ui/icons.tsx
+++ b/ee/tabby-ui/components/ui/icons.tsx
@@ -1305,13 +1305,13 @@ function IconActivity({ className, ...props }: React.ComponentProps<'svg'>) {
       strokeLinecap="round"
       strokeLinejoin="round"
       className={cn('h-4 w-4', className)}
-      {...props}>
-        <path d="M22 12h-4l-3 9L9 3l-3 9H2"/>
-      </svg>
+      {...props}
+    >
+      <path d="M22 12h-4l-3 9L9 3l-3 9H2" />
+    </svg>
   )
 }
 
-
 export {
   IconEdit,
   IconNextChat,

From 7667da785416f7f1dda0dae44a054bffd1d3db58 Mon Sep 17 00:00:00 2001
From: Meng Zhang <meng@tabbyml.com>
Date: Sun, 7 Apr 2024 22:01:31 -0700
Subject: [PATCH 14/16] Update
 ee/tabby-ui/app/(dashboard)/reports/components/daily-activity.tsx

---
 .../app/(dashboard)/reports/components/daily-activity.tsx       | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/ee/tabby-ui/app/(dashboard)/reports/components/daily-activity.tsx b/ee/tabby-ui/app/(dashboard)/reports/components/daily-activity.tsx
index ea5af8a18a3b..dee3da7ef44b 100644
--- a/ee/tabby-ui/app/(dashboard)/reports/components/daily-activity.tsx
+++ b/ee/tabby-ui/app/(dashboard)/reports/components/daily-activity.tsx
@@ -97,7 +97,7 @@ export function DailyActivity({
   })
   return (
     <div className="rounded-lg border bg-primary-foreground/30 px-6 py-4">
-      <h3 className="mb-5 text-sm font-medium tracking-tight">Completions</h3>
+      <h3 className="mb-5 text-sm font-medium tracking-tight">Daily Statistics</h3>
       <ResponsiveContainer width="100%" height={300}>
         <BarChart
           data={chartData}

From 307d8f621c3f513752208495fc490e2600eaf1f3 Mon Sep 17 00:00:00 2001
From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com>
Date: Mon, 8 Apr 2024 05:02:28 +0000
Subject: [PATCH 15/16] [autofix.ci] apply automated fixes

---
 .../app/(dashboard)/reports/components/daily-activity.tsx     | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/ee/tabby-ui/app/(dashboard)/reports/components/daily-activity.tsx b/ee/tabby-ui/app/(dashboard)/reports/components/daily-activity.tsx
index dee3da7ef44b..90a8963f82ca 100644
--- a/ee/tabby-ui/app/(dashboard)/reports/components/daily-activity.tsx
+++ b/ee/tabby-ui/app/(dashboard)/reports/components/daily-activity.tsx
@@ -97,7 +97,9 @@ export function DailyActivity({
   })
   return (
     <div className="rounded-lg border bg-primary-foreground/30 px-6 py-4">
-      <h3 className="mb-5 text-sm font-medium tracking-tight">Daily Statistics</h3>
+      <h3 className="mb-5 text-sm font-medium tracking-tight">
+        Daily Statistics
+      </h3>
       <ResponsiveContainer width="100%" height={300}>
         <BarChart
           data={chartData}

From 9b31c72111076621ccfd7d3abe1accbfe949e63a Mon Sep 17 00:00:00 2001
From: wwayne <wayne.wang0821@gmail.com>
Date: Mon, 8 Apr 2024 14:59:45 +0800
Subject: [PATCH 16/16] update

---
 ee/tabby-ui/app/(dashboard)/reports/components/report.tsx | 8 ++++----
 ee/tabby-ui/lib/tabby/gql.ts                              | 4 ++++
 2 files changed, 8 insertions(+), 4 deletions(-)

diff --git a/ee/tabby-ui/app/(dashboard)/reports/components/report.tsx b/ee/tabby-ui/app/(dashboard)/reports/components/report.tsx
index 3449d076e0fd..6e341cc93bc3 100644
--- a/ee/tabby-ui/app/(dashboard)/reports/components/report.tsx
+++ b/ee/tabby-ui/app/(dashboard)/reports/components/report.tsx
@@ -147,9 +147,9 @@ export function Report() {
 
   return (
     <div className="mx-auto max-w-5xl">
-      <div className="mb-3 flex items-end justify-between">
+      <div className="mb-3 flex flex-col items-center justify-between gap-y-2 md:flex-row md:items-end md:gap-y-0">
         <div className="flex flex-col">
-          <h1 className="mb-1.5 scroll-m-20 text-4xl font-extrabold tracking-tight lg:text-5xl">
+          <h1 className="mb-1.5 scroll-m-20 text-center text-4xl font-extrabold tracking-tight md:text-left lg:text-5xl">
             Reports
           </h1>
           <p className="text-muted-foreground">
@@ -166,10 +166,10 @@ export function Report() {
             onValueChange={setSelectedMember}
           >
             <SelectTrigger className="h-auto w-auto border-none py-0 shadow-none">
-              <div className="flex h-6 items-center pr-3">
+              <div className="flex h-6 items-center">
                 <IconUsers className="mr-1" />
                 <p className="mr-1.5">Member:</p>
-                <div className="max-w-[80px] overflow-hidden text-ellipsis">
+                <div className="w-[80px] overflow-hidden text-ellipsis text-left">
                   <SelectValue />
                 </div>
               </div>
diff --git a/ee/tabby-ui/lib/tabby/gql.ts b/ee/tabby-ui/lib/tabby/gql.ts
index f41350f469c4..8283c63e9022 100644
--- a/ee/tabby-ui/lib/tabby/gql.ts
+++ b/ee/tabby-ui/lib/tabby/gql.ts
@@ -97,6 +97,10 @@ const client = new Client({
   requestPolicy: 'cache-and-network',
   exchanges: [
     cacheExchange({
+      keys: {
+        CompletionStats: () => null,
+        ServerInfo: () => null
+      },
       resolvers: {
         Query: {
           invitations: relayPagination(),