-
Notifications
You must be signed in to change notification settings - Fork 52
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[Feat]: Add new API endpoint for daily time log report charts (#3533)
* feat: implement time tracking analytics improvements * fix: code rabbit * fix: code rabbit * refactor: improve report activity state management - Move report activity state to global store using timeLogsRapportChartState - Remove local state management in useReportActivity hook - Improve error handling and data reset logic - Add better error logging - Simplify state updates and data flow * feat: Add input validation for required fields.
- Loading branch information
1 parent
25d0024
commit 1be1d7c
Showing
20 changed files
with
615 additions
and
274 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
|
||
|
||
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> | ||
); | ||
} | ||
|
||
export default withAuthentication(AppUrls, { | ||
displayName: 'Apps & URLs', | ||
showPageSkeleton: true | ||
}); |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
234 changes: 234 additions & 0 deletions
234
apps/web/app/[locale]/dashboard/team-dashboard/[teamId]/components/date-range-picker.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,234 @@ | ||
'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'; | ||
import { useTranslations } from 'next-intl'; | ||
|
||
interface DateRangePickerProps { | ||
className?: string; | ||
onDateRangeChange?: (range: DateRange | undefined) => void; | ||
} | ||
|
||
export function DateRangePicker({ className, onDateRangeChange }: DateRangePickerProps) { | ||
const t=useTranslations(); | ||
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: t('common.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: t('common.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: t('common.THIS_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: t('common.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:t('common.THIS_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: t('common.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) : t('common.SELECT')} | ||
<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 dark:text-gray-100', | ||
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); | ||
}} | ||
> | ||
{t('common.CLEAR')} | ||
</Button> | ||
<Button | ||
onClick={() => { | ||
setIsPopoverOpen(false); | ||
}} | ||
> | ||
{t('common.APPLY')} | ||
</Button> | ||
</div> | ||
</PopoverContent> | ||
</Popover> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes.
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.