Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feat]: Add new API endpoint for daily time log report charts #3533

Merged
merged 5 commits into from
Jan 22, 2025
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 68 additions & 0 deletions apps/web/app/[locale]/dashboard/app-url/[teamId]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
"use client"
import { fullWidthState } from '@/app/stores/fullWidth';
import { withAuthentication } from '@/lib/app/authenticator';
import { MainLayout } from '@/lib/layout';
import { cn } from '@/lib/utils';
import { useOrganizationTeams } from '@app/hooks/features/useOrganizationTeams';
import { useAtomValue } from 'jotai';
import { useTranslations } from 'next-intl';
import { useParams } from 'next/navigation';
import React, { useMemo } from 'react';
import { ArrowLeftIcon } from '@radix-ui/react-icons';
import { useRouter } from 'next/navigation';
import { Breadcrumb, Container } from '@/lib/components';

function AppUrls() {
const { activeTeam, isTrackingEnabled } = useOrganizationTeams();
const router = useRouter();
const t = useTranslations();
const fullWidth = useAtomValue(fullWidthState);
const paramsUrl = useParams<{ locale: string }>();
const currentLocale = paramsUrl?.locale;

Innocent-Akim marked this conversation as resolved.
Show resolved Hide resolved

const breadcrumbPath = useMemo(
() => [
{ title: JSON.parse(t('pages.home.BREADCRUMB')), href: '/' },
{ title: activeTeam?.name || '', href: '/' },
{ title: 'Apps & URLs', href: `/${currentLocale}/dashboard/app-url` }
],
[activeTeam?.name, currentLocale, t]
);

if (!activeTeam) {
return (
<div className="flex justify-center items-center h-screen">
<p className="text-gray-500">Team not found</p>
</div>
);
}

return (
<MainLayout
className="items-start pb-1 !overflow-hidden w-full"
childrenClassName="w-full"
showTimer={isTrackingEnabled}
mainHeaderSlot={
<div className="flex flex-col py-4 bg-gray-100 dark:bg-dark--theme">
<Container fullWidth={fullWidth} className={cn('flex gap-4 items-center w-full')}>
<div className="flex items-center pt-6 w-full dark:bg-dark--theme">
<button
onClick={() => router.back()}
className="p-1 rounded-full transition-colors hover:bg-gray-100"
>
<ArrowLeftIcon className="text-dark dark:text-[#6b7280] h-6 w-6" />
</button>
<Breadcrumb paths={breadcrumbPath} className="text-sm" />
</div>
</Container>
</div>
}
></MainLayout>
Innocent-Akim marked this conversation as resolved.
Show resolved Hide resolved
);
}

export default withAuthentication(AppUrls, {
displayName: 'Apps & URLs',
showPageSkeleton: true
});
11 changes: 0 additions & 11 deletions apps/web/app/[locale]/dashboard/app-url/page.tsx

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,232 @@
'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) => {
try {
setDateRange(range);
onDateRangeChange?.(range);
} catch (error) {
console.error('Error handling date range change:', error);
}
};

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] text-center flex items-center',
!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-1 space-y-1 border-l max-w-36">
{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-1">
<Calendar
className="min-w-[240px]"
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-1 border-t">
<Button
variant="outline"
onClick={() => {
handleDateRangeChange(undefined);
setIsPopoverOpen(false);
}}
>
Clear
</Button>
<Button
onClick={() => {
setIsPopoverOpen(false);
}}
>
Apply
</Button>
</div>
</PopoverContent>
</Popover>
Innocent-Akim marked this conversation as resolved.
Show resolved Hide resolved
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,14 @@ const CustomTooltip = ({ active, payload, label }: TooltipProps) => {
export function TeamStatsChart() {
return (
<div className="flex flex-col">
<div className="h-[300px] w-full">
<div className="h-[250px] w-full">
<ResponsiveContainer width="100%" height="100%">
<LineChart data={chartData} margin={{ top: 20, right: 0, bottom: 20, left: 0 }}>
<LineChart data={chartData} margin={{ top: 20, right: 20, bottom: 20, left: 20 }}>
<CartesianGrid
vertical={true}
strokeDasharray="3 3"
horizontal={true}
className="stroke-gray-200"
className="stroke-gray-200 dark:stroke-gray-700"
vertical={true}
/>
<XAxis
dataKey="date"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export function TeamStatsGrid() {
<Card key={stat.title} className="p-6">
<div className="flex flex-col">
<span className="text-sm font-medium text-gray-500">{stat.title}</span>
<span className={`text-2xl font-semibold mt-2 ${stat.color || "text-gray-900"}`}>
<span className={`text-2xl font-semibold mt-2 ${stat.color || "text-gray-900 dark:text-white"}`}>
{stat.value}
</span>
{stat.progress && (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ function TeamDashboard() {
() => [
{ title: JSON.parse(t('pages.home.BREADCRUMB')), href: '/' },
{ title: activeTeam?.name || '', href: '/' },
{ title: 'team-dashboard', href: `/${currentLocale}/dashboard/team-dashboard` }
{ title: 'Team-Dashboard', href: `/${currentLocale}/dashboard/team-dashboard` }
],
[activeTeam?.name, currentLocale, t]
);
Expand All @@ -42,16 +42,16 @@ function TeamDashboard() {
mainHeaderSlot={
<div className="flex flex-col py-4 bg-gray-100 dark:bg-dark--theme">
<Container fullWidth={fullWidth} className={cn('flex flex-col gap-4 w-full')}>
<div className="flex pt-6 w-full dark:bg-dark--theme">
<div className="flex items-center pt-6 dark:bg-dark--theme">
<button
onClick={() => router.back()}
className="p-1 rounded-full transition-colors hover:bg-gray-100"
>
<ArrowLeftIcon className="text-dark dark:text-[#6b7280] h-6 w-6" />
</button>{' '}
</button>
<Breadcrumb paths={breadcrumbPath} className="text-sm" />
</div>
<div className="flex flex-col gap-4 px-6 pt-4 w-full">
<div className="flex flex-col gap-4 px-4 pt-4 w-full">
<DashboardHeader />
<TeamStatsGrid />
<Card className="p-6 w-full">
Expand All @@ -60,8 +60,7 @@ function TeamDashboard() {
</div>
</Container>
</div>
}
>
}>
<Container fullWidth={fullWidth} className={cn('flex flex-col gap-8 py-6 w-full')}>
<Card className="p-6 w-full">
<TeamStatsTable />
Expand Down
Loading
Loading