Skip to content

Commit

Permalink
[Feat]: Team Dashboard UI Enhancements (#3530)
Browse files Browse the repository at this point in the history
* feat: enhance team dashboard UI and functionality

* refactor: optimize team dashboard code structure and performance

* feat(dashboard): enhance date range picker

* fix: package-lock

* fix: bug
  • Loading branch information
Innocent-Akim authored Jan 20, 2025
1 parent a958c03 commit f40a8bf
Show file tree
Hide file tree
Showing 12 changed files with 4,678 additions and 3,459 deletions.
11 changes: 11 additions & 0 deletions apps/web/app/[locale]/dashboard/app-url/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import React from 'react'

function AppUrls() {
return (
<div>

</div>
)
}

export default AppUrls
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
"use client";

import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { DateRangePicker } from "./date-range-picker";

export function DashboardHeader() {
return (
<div className="flex justify-between items-center">
<h1 className="text-2xl font-semibold">Team Dashboard</h1>
<div className="flex gap-4 items-center">
<DateRangePicker />
<Select defaultValue="filter">
<SelectTrigger className="w-[100px]">
<SelectValue placeholder="Filter" />
</SelectTrigger>
<SelectContent>
<SelectItem value="filter">Filter</SelectItem>
<SelectItem value="today">Today</SelectItem>
<SelectItem value="week">This Week</SelectItem>
<SelectItem value="month">This Month</SelectItem>
</SelectContent>
</Select>
<Select defaultValue="export">
<SelectTrigger className="w-[100px]">
<SelectValue placeholder="Export" />
</SelectTrigger>
<SelectContent>
<SelectItem value="export">Export</SelectItem>
<SelectItem value="csv">CSV</SelectItem>
<SelectItem value="pdf">PDF</SelectItem>
<SelectItem value="excel">Excel</SelectItem>
</SelectContent>
</Select>
</div>
</div>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
"use client";

import * as React from "react";
import { Button } from "@/components/ui/button";
import { Calendar } from "@/components/ui/calendar";
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
import { ChevronDown } from "lucide-react";
import { cn } from "@/lib/utils";
import { format, startOfWeek, endOfWeek, startOfMonth, endOfMonth, subDays, subWeeks, subMonths, isSameMonth, isSameYear, isEqual } from "date-fns";
import { DateRange } from "react-day-picker";

interface DateRangePickerProps {
className?: string;
onDateRangeChange?: (range: DateRange | undefined) => void;
}

export function DateRangePicker({ className, onDateRangeChange }: DateRangePickerProps) {
const [dateRange, setDateRange] = React.useState<DateRange | undefined>({
from: new Date(),
to: new Date(),
});
const [isPopoverOpen, setIsPopoverOpen] = React.useState(false);
const [currentMonth, setCurrentMonth] = React.useState<Date>(new Date());

const handleDateRangeChange = (range: DateRange | undefined) => {
setDateRange(range);
onDateRangeChange?.(range);
};

const predefinedRanges = [
{
label: "Today",
action: () => {
const today = new Date();
handleDateRangeChange({ from: today, to: today });
},
isSelected: (range: DateRange | undefined) => {
if (!range?.from || !range?.to) return false;
const today = new Date();
return isEqual(range.from, today) && isEqual(range.to, today);
}
},
{
label: "Yesterday",
action: () => {
const yesterday = subDays(new Date(), 1);
handleDateRangeChange({ from: yesterday, to: yesterday });
},
isSelected: (range: DateRange | undefined) => {
if (!range?.from || !range?.to) return false;
const yesterday = subDays(new Date(), 1);
return isEqual(range.from, yesterday) && isEqual(range.to, yesterday);
}
},
{
label: "Current Week",
action: () => {
const today = new Date();
handleDateRangeChange({
from: startOfWeek(today, { weekStartsOn: 1 }),
to: endOfWeek(today, { weekStartsOn: 1 })
});
},
isSelected: (range: DateRange | undefined) => {
if (!range?.from || !range?.to) return false;
const today = new Date();
const weekStart = startOfWeek(today, { weekStartsOn: 1 });
const weekEnd = endOfWeek(today, { weekStartsOn: 1 });
return isEqual(range.from, weekStart) && isEqual(range.to, weekEnd);
}
},
{
label: "Last Week",
action: () => {
const lastWeek = subWeeks(new Date(), 1);
handleDateRangeChange({
from: startOfWeek(lastWeek, { weekStartsOn: 1 }),
to: endOfWeek(lastWeek, { weekStartsOn: 1 })
});
},
isSelected: (range: DateRange | undefined) => {
if (!range?.from || !range?.to) return false;
const lastWeek = subWeeks(new Date(), 1);
const weekStart = startOfWeek(lastWeek, { weekStartsOn: 1 });
const weekEnd = endOfWeek(lastWeek, { weekStartsOn: 1 });
return isEqual(range.from, weekStart) && isEqual(range.to, weekEnd);
}
},
{
label: "Current Month",
action: () => {
const today = new Date();
handleDateRangeChange({
from: startOfMonth(today),
to: endOfMonth(today)
});
},
isSelected: (range: DateRange | undefined) => {
if (!range?.from || !range?.to) return false;
const today = new Date();
const monthStart = startOfMonth(today);
const monthEnd = endOfMonth(today);
return isEqual(range.from, monthStart) && isEqual(range.to, monthEnd);
}
},
{
label: "Last Month",
action: () => {
const lastMonth = subMonths(new Date(), 1);
handleDateRangeChange({
from: startOfMonth(lastMonth),
to: endOfMonth(lastMonth)
});
},
isSelected: (range: DateRange | undefined) => {
if (!range?.from || !range?.to) return false;
const lastMonth = subMonths(new Date(), 1);
const monthStart = startOfMonth(lastMonth);
const monthEnd = endOfMonth(lastMonth);
return isEqual(range.from, monthStart) && isEqual(range.to, monthEnd);
}
},
];

const formatDateRange = (range: DateRange) => {
if (!range.from) return "Select date range";
if (!range.to) return format(range.from, "d MMM yyyy");

if (isSameYear(range.from, range.to)) {
if (isSameMonth(range.from, range.to)) {
return `${format(range.from, "d")} - ${format(range.to, "d MMM yyyy")}`;
}
return `${format(range.from, "d MMM")} - ${format(range.to, "d MMM yyyy")}`;
}
return `${format(range.from, "d MMM yyyy")} - ${format(range.to, "d MMM yyyy")}`;
};

return (
<Popover open={isPopoverOpen} onOpenChange={setIsPopoverOpen}>
<PopoverTrigger asChild>
<Button
variant="outline"
className={cn(
"justify-between gap-2 px-3 py-2 min-w-[240px]",
!dateRange && "text-muted-foreground",
className
)}
>
{dateRange ? formatDateRange(dateRange) : "Select date range"}
<ChevronDown className="w-4 h-4" />
</Button>
</PopoverTrigger>
<PopoverContent
onClick={(e) => e.stopPropagation()}
onMouseDown={(e) => e.stopPropagation()}
onChange={(e) => e.stopPropagation()}
className="p-0 w-auto"
align="center"
>
<div className="flex flex-row-reverse">
<div className="p-3 space-y-3 border-l max-w-40">
{predefinedRanges.map((range) => (
<Button
key={range.label}
variant={range.isSelected(dateRange) ? "default" : "ghost"}
className={cn(
"justify-start w-full font-normal",
range.isSelected(dateRange) && "bg-primary text-primary-foreground hover:bg-primary/90"
)}
onClick={() => {
range.action();
}}
>
{range.label}
</Button>
))}
</div>
<div className="p-3">
<Calendar
mode="range"
selected={dateRange}
onSelect={handleDateRangeChange}
numberOfMonths={2}
month={currentMonth}
onMonthChange={setCurrentMonth}
showOutsideDays={false}
fixedWeeks
ISOWeek
initialFocus
/>
</div>
</div>
<div className="flex gap-2 justify-end p-3 border-t">
<Button
variant="outline"
onClick={() => {
handleDateRangeChange(undefined);
setIsPopoverOpen(false);
}}
>
Clear
</Button>
<Button
onClick={() => {
setIsPopoverOpen(false);
}}
>
Apply
</Button>
</div>
</PopoverContent>
</Popover>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
"use client";

import { Line, LineChart, ResponsiveContainer, Tooltip, XAxis, YAxis, CartesianGrid } from "recharts";
import { Button } from "@/components/ui/button";
import { chartData } from "../data/mock-data";

interface TooltipProps {
active?: boolean;
payload?: {
name: string;
value: number;
color: string;
}[];
label?: string;
}
const CustomTooltip = ({ active, payload, label }: TooltipProps) => {
if (active && payload && payload.length) {
return (
<div className="p-4 bg-white rounded-lg border shadow-lg">
<p className="mb-2 font-medium">{label}</p>
{payload.map((item, index) => (
<p key={index} className="text-sm" style={{ color: item.color }}>
{item.name}: {item.value}
</p>
))}
</div>
);
}
return null;
};

export function TeamStatsChart() {
return (
<div className="flex flex-col">
<div className="h-[300px] w-full">
<ResponsiveContainer width="100%" height="100%">
<LineChart data={chartData} margin={{ top: 20, right: 0, bottom: 20, left: 0 }}>
<CartesianGrid
vertical={true}
horizontal={true}
className="stroke-gray-200"
/>
<XAxis
dataKey="date"
stroke="#888888"
fontSize={12}
tickLine={false}
axisLine={false}
padding={{ left: 10, right: 10 }}
/>
<YAxis
stroke="#888888"
fontSize={12}
tickLine={false}
axisLine={false}
tickFormatter={(value) => `${value}`}
padding={{ top: 10, bottom: 10 }}
tickCount={8}
/>
<Tooltip content={<CustomTooltip />} />
<Line
type="monotone"
dataKey="tracked"
stroke="#2563eb"
strokeWidth={2}
dot={{ fill: "#2563eb", r: 4 }}
activeDot={{ r: 6, fill: "#2563eb" }}
/>
<Line
type="monotone"
dataKey="manual"
stroke="#dc2626"
strokeWidth={2}
dot={{ fill: "#dc2626", r: 4 }}
activeDot={{ r: 6, fill: "#dc2626" }}
/>
<Line
type="monotone"
dataKey="idle"
stroke="#eab308"
strokeWidth={2}
dot={{ fill: "#eab308", r: 4 }}
activeDot={{ r: 6, fill: "#eab308" }}
/>
</LineChart>
</ResponsiveContainer>
</div>
<div className="flex gap-3 justify-center -mt-2">
<Button
size="sm"
variant="outline"
className="gap-2 px-3 py-1.5 h-8 text-xs font-normal hover:bg-transparent hover:text-inherit"
>
<div className="w-2 h-2 bg-blue-500 rounded-full" />
Tracked
</Button>
<Button
size="sm"
variant="outline"
className="gap-2 px-3 py-1.5 h-8 text-xs font-normal hover:bg-transparent hover:text-inherit"
>
<div className="w-2 h-2 bg-red-500 rounded-full" />
Manual
</Button>
<Button
size="sm"
variant="outline"
className="gap-2 px-3 py-1.5 h-8 text-xs font-normal hover:bg-transparent hover:text-inherit"
>
<div className="w-2 h-2 bg-yellow-500 rounded-full" />
Idle
</Button>
</div>
</div>
);
}
Loading

0 comments on commit f40a8bf

Please sign in to comment.