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

Release #3534

Merged
merged 13 commits into from
Jan 22, 2025
Merged

Release #3534

Show file tree
Hide file tree
Changes from all 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
2 changes: 1 addition & 1 deletion apps/mobile/app.template.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
[
"expo-image-picker",
{
"photosPermission": "The app accesses your photos to let you share them with your friends."
"photosPermission": "The app accesses your photos to let you share them with your team members."
}
],
"sentry-expo",
Expand Down
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;


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
});
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,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>
);
}
Loading
Loading