fix timepicker
This commit is contained in:
@@ -1,24 +1,27 @@
|
||||
import { pushModal } from '@/modals';
|
||||
import type {
|
||||
IReport,
|
||||
IChartRange,
|
||||
IChartType,
|
||||
IInterval,
|
||||
IReport,
|
||||
} from '@openpanel/validation';
|
||||
import { SaveIcon } from 'lucide-react';
|
||||
import { useState } from 'react';
|
||||
import { ReportChart } from '../report-chart';
|
||||
import { ReportChartType } from '../report/ReportChartType';
|
||||
import { ReportInterval } from '../report/ReportInterval';
|
||||
import { ReportChart } from '../report-chart';
|
||||
import { TimeWindowPicker } from '../time-window-picker';
|
||||
import { Button } from '../ui/button';
|
||||
import { pushModal } from '@/modals';
|
||||
|
||||
export function ChatReport({
|
||||
lazy,
|
||||
...props
|
||||
}: { report: IReport & { startDate: string; endDate: string }; lazy: boolean }) {
|
||||
}: {
|
||||
report: IReport & { startDate: string; endDate: string };
|
||||
lazy: boolean;
|
||||
}) {
|
||||
const [chartType, setChartType] = useState<IChartType>(
|
||||
props.report.chartType,
|
||||
props.report.chartType
|
||||
);
|
||||
const [startDate, setStartDate] = useState<string>(props.report.startDate);
|
||||
const [endDate, setEndDate] = useState<string>(props.report.endDate);
|
||||
@@ -35,47 +38,48 @@ export function ChatReport({
|
||||
};
|
||||
return (
|
||||
<div className="card">
|
||||
<div className="text-center text-sm font-mono font-medium pt-4">
|
||||
<div className="pt-4 text-center font-medium font-mono text-sm">
|
||||
{props.report.name}
|
||||
</div>
|
||||
<div className="p-4">
|
||||
<ReportChart lazy={lazy} report={report} />
|
||||
</div>
|
||||
<div className="row justify-between gap-1 border-t border-border p-2">
|
||||
<div className="row justify-between gap-1 border-border border-t p-2">
|
||||
<div className="col md:row gap-1">
|
||||
<TimeWindowPicker
|
||||
className="min-w-0"
|
||||
onChange={setRange}
|
||||
value={report.range}
|
||||
onStartDateChange={setStartDate}
|
||||
onEndDateChange={setEndDate}
|
||||
endDate={report.endDate}
|
||||
onChange={setRange}
|
||||
onEndDateChange={setEndDate}
|
||||
onIntervalChange={setInterval}
|
||||
onStartDateChange={setStartDate}
|
||||
startDate={report.startDate}
|
||||
value={report.range}
|
||||
/>
|
||||
<ReportInterval
|
||||
chartType={chartType}
|
||||
className="min-w-0"
|
||||
interval={interval}
|
||||
range={range}
|
||||
chartType={chartType}
|
||||
onChange={setInterval}
|
||||
range={range}
|
||||
/>
|
||||
<ReportChartType
|
||||
value={chartType}
|
||||
onChange={(type) => {
|
||||
setChartType(type);
|
||||
}}
|
||||
value={chartType}
|
||||
/>
|
||||
</div>
|
||||
<Button
|
||||
icon={SaveIcon}
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
pushModal('SaveReport', {
|
||||
report,
|
||||
disableRedirect: true,
|
||||
});
|
||||
}}
|
||||
size="sm"
|
||||
variant="outline"
|
||||
>
|
||||
Save report
|
||||
</Button>
|
||||
|
||||
@@ -2,17 +2,25 @@ import { useOverviewOptions } from '@/components/overview/useOverviewOptions';
|
||||
import { TimeWindowPicker } from '@/components/time-window-picker';
|
||||
|
||||
export function OverviewRange() {
|
||||
const { range, setRange, setStartDate, setEndDate, endDate, startDate } =
|
||||
useOverviewOptions();
|
||||
const {
|
||||
range,
|
||||
setRange,
|
||||
setStartDate,
|
||||
setEndDate,
|
||||
endDate,
|
||||
startDate,
|
||||
setInterval,
|
||||
} = useOverviewOptions();
|
||||
|
||||
return (
|
||||
<TimeWindowPicker
|
||||
onChange={setRange}
|
||||
value={range}
|
||||
onStartDateChange={setStartDate}
|
||||
onEndDateChange={setEndDate}
|
||||
endDate={endDate}
|
||||
onChange={setRange}
|
||||
onEndDateChange={setEndDate}
|
||||
onIntervalChange={setInterval}
|
||||
onStartDateChange={setStartDate}
|
||||
startDate={startDate}
|
||||
value={range}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import { ReportChart } from '@/components/report-chart';
|
||||
import type { IServiceReport } from '@openpanel/db';
|
||||
import { GanttChartSquareIcon, ShareIcon } from 'lucide-react';
|
||||
import { useEffect } from 'react';
|
||||
import EditReportName from '../report/edit-report-name';
|
||||
import { ReportChartType } from '@/components/report/ReportChartType';
|
||||
import { ReportInterval } from '@/components/report/ReportInterval';
|
||||
import { ReportLineType } from '@/components/report/ReportLineType';
|
||||
@@ -14,18 +17,13 @@ import {
|
||||
setReport,
|
||||
} from '@/components/report/reportSlice';
|
||||
import { ReportSidebar } from '@/components/report/sidebar/ReportSidebar';
|
||||
import { ReportChart } from '@/components/report-chart';
|
||||
import { TimeWindowPicker } from '@/components/time-window-picker';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Sheet, SheetContent, SheetTrigger } from '@/components/ui/sheet';
|
||||
import { useAppParams } from '@/hooks/use-app-params';
|
||||
import { pushModal } from '@/modals';
|
||||
import { useDispatch, useSelector } from '@/redux';
|
||||
import { bind } from 'bind-event-listener';
|
||||
import { GanttChartSquareIcon, ShareIcon } from 'lucide-react';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
import type { IServiceReport } from '@openpanel/db';
|
||||
import EditReportName from '../report/edit-report-name';
|
||||
|
||||
interface ReportEditorProps {
|
||||
report: IServiceReport | null;
|
||||
@@ -54,15 +52,15 @@ export default function ReportEditor({
|
||||
return (
|
||||
<Sheet>
|
||||
<div>
|
||||
<div className="p-4 flex items-center justify-between">
|
||||
<div className="flex items-center justify-between p-4">
|
||||
<EditReportName />
|
||||
{initialReport?.id && (
|
||||
<Button
|
||||
variant="outline"
|
||||
icon={ShareIcon}
|
||||
onClick={() =>
|
||||
pushModal('ShareReportModal', { reportId: initialReport.id })
|
||||
}
|
||||
variant="outline"
|
||||
>
|
||||
Share
|
||||
</Button>
|
||||
@@ -71,9 +69,9 @@ export default function ReportEditor({
|
||||
<div className="grid grid-cols-2 gap-2 p-4 pt-0 md:grid-cols-6">
|
||||
<SheetTrigger asChild>
|
||||
<Button
|
||||
className="self-start"
|
||||
icon={GanttChartSquareIcon}
|
||||
variant="cta"
|
||||
className="self-start"
|
||||
>
|
||||
Pick events
|
||||
</Button>
|
||||
@@ -88,23 +86,26 @@ export default function ReportEditor({
|
||||
/>
|
||||
<TimeWindowPicker
|
||||
className="min-w-0 flex-1"
|
||||
endDate={report.endDate}
|
||||
onChange={(value) => {
|
||||
dispatch(changeDateRanges(value));
|
||||
}}
|
||||
value={report.range}
|
||||
onStartDateChange={(date) => dispatch(changeStartDate(date))}
|
||||
onEndDateChange={(date) => dispatch(changeEndDate(date))}
|
||||
endDate={report.endDate}
|
||||
onIntervalChange={(interval) =>
|
||||
dispatch(changeInterval(interval))
|
||||
}
|
||||
onStartDateChange={(date) => dispatch(changeStartDate(date))}
|
||||
startDate={report.startDate}
|
||||
value={report.range}
|
||||
/>
|
||||
<ReportInterval
|
||||
chartType={report.chartType}
|
||||
className="min-w-0 flex-1"
|
||||
endDate={report.endDate}
|
||||
interval={report.interval}
|
||||
onChange={(newInterval) => dispatch(changeInterval(newInterval))}
|
||||
range={report.range}
|
||||
chartType={report.chartType}
|
||||
startDate={report.startDate}
|
||||
endDate={report.endDate}
|
||||
/>
|
||||
<ReportLineType className="min-w-0 flex-1" />
|
||||
</div>
|
||||
@@ -114,7 +115,7 @@ export default function ReportEditor({
|
||||
</div>
|
||||
<div className="flex flex-col gap-4 p-4" id="report-editor">
|
||||
{report.ready && (
|
||||
<ReportChart report={{ ...report, projectId }} isEditMode />
|
||||
<ReportChart isEditMode report={{ ...report, projectId }} />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
import { timeWindows } from '@openpanel/constants';
|
||||
import type { IChartRange, IInterval } from '@openpanel/validation';
|
||||
import { bind } from 'bind-event-listener';
|
||||
import { endOfDay, format, startOfDay } from 'date-fns';
|
||||
import { CalendarIcon } from 'lucide-react';
|
||||
import { useCallback, useEffect, useRef } from 'react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import {
|
||||
DropdownMenu,
|
||||
@@ -11,24 +17,18 @@ import {
|
||||
} from '@/components/ui/dropdown-menu';
|
||||
import { pushModal, useOnPushModal } from '@/modals';
|
||||
import { cn } from '@/utils/cn';
|
||||
import { bind } from 'bind-event-listener';
|
||||
import { CalendarIcon } from 'lucide-react';
|
||||
import { useCallback, useEffect, useRef } from 'react';
|
||||
|
||||
import { shouldIgnoreKeypress } from '@/utils/should-ignore-keypress';
|
||||
import { timeWindows } from '@openpanel/constants';
|
||||
import type { IChartRange } from '@openpanel/validation';
|
||||
import { endOfDay, format, startOfDay } from 'date-fns';
|
||||
|
||||
type Props = {
|
||||
interface Props {
|
||||
value: IChartRange;
|
||||
onChange: (value: IChartRange) => void;
|
||||
onStartDateChange: (date: string) => void;
|
||||
onEndDateChange: (date: string) => void;
|
||||
onIntervalChange: (interval: IInterval) => void;
|
||||
endDate: string | null;
|
||||
startDate: string | null;
|
||||
className?: string;
|
||||
};
|
||||
}
|
||||
export function TimeWindowPicker({
|
||||
value,
|
||||
onChange,
|
||||
@@ -36,6 +36,7 @@ export function TimeWindowPicker({
|
||||
onStartDateChange,
|
||||
endDate,
|
||||
onEndDateChange,
|
||||
onIntervalChange,
|
||||
className,
|
||||
}: Props) {
|
||||
const isDateRangerPickerOpen = useRef(false);
|
||||
@@ -46,10 +47,11 @@ export function TimeWindowPicker({
|
||||
|
||||
const handleCustom = useCallback(() => {
|
||||
pushModal('DateRangerPicker', {
|
||||
onChange: ({ startDate, endDate }) => {
|
||||
onChange: ({ startDate, endDate, interval }) => {
|
||||
onStartDateChange(format(startOfDay(startDate), 'yyyy-MM-dd HH:mm:ss'));
|
||||
onEndDateChange(format(endOfDay(endDate), 'yyyy-MM-dd HH:mm:ss'));
|
||||
onChange('custom');
|
||||
onIntervalChange(interval);
|
||||
},
|
||||
startDate: startDate ? new Date(startDate) : undefined,
|
||||
endDate: endDate ? new Date(endDate) : undefined,
|
||||
@@ -69,7 +71,7 @@ export function TimeWindowPicker({
|
||||
}
|
||||
|
||||
const match = Object.values(timeWindows).find(
|
||||
(tw) => event.key === tw.shortcut.toLowerCase(),
|
||||
(tw) => event.key === tw.shortcut.toLowerCase()
|
||||
);
|
||||
if (match?.key === 'custom') {
|
||||
handleCustom();
|
||||
@@ -84,9 +86,9 @@ export function TimeWindowPicker({
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
icon={CalendarIcon}
|
||||
className={cn('justify-start', className)}
|
||||
icon={CalendarIcon}
|
||||
variant="outline"
|
||||
>
|
||||
{timeWindow?.label}
|
||||
</Button>
|
||||
|
||||
@@ -9,7 +9,6 @@ import {
|
||||
DayPicker,
|
||||
getDefaultClassNames,
|
||||
} from 'react-day-picker';
|
||||
|
||||
import { Button, buttonVariants } from '@/components/ui/button';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
@@ -29,99 +28,93 @@ function Calendar({
|
||||
|
||||
return (
|
||||
<DayPicker
|
||||
showOutsideDays={showOutsideDays}
|
||||
captionLayout={captionLayout}
|
||||
className={cn(
|
||||
'bg-background group/calendar p-3 [--cell-size:--spacing(8)] [[data-slot=card-content]_&]:bg-transparent [[data-slot=popover-content]_&]:bg-transparent',
|
||||
'group/calendar p-3 [--cell-size:--spacing(8)] [[data-slot=card-content]_&]:bg-transparent [[data-slot=popover-content]_&]:bg-transparent',
|
||||
String.raw`rtl:**:[.rdp-button\_next>svg]:rotate-180`,
|
||||
String.raw`rtl:**:[.rdp-button\_previous>svg]:rotate-180`,
|
||||
className,
|
||||
className
|
||||
)}
|
||||
captionLayout={captionLayout}
|
||||
formatters={{
|
||||
formatMonthDropdown: (date) =>
|
||||
date.toLocaleString('default', { month: 'short' }),
|
||||
...formatters,
|
||||
}}
|
||||
classNames={{
|
||||
root: cn('w-fit', defaultClassNames.root),
|
||||
months: cn(
|
||||
'flex gap-4 flex-col sm:flex-row relative',
|
||||
defaultClassNames.months,
|
||||
'relative flex flex-col gap-4 sm:flex-row',
|
||||
defaultClassNames.months
|
||||
),
|
||||
month: cn('flex flex-col w-full gap-4', defaultClassNames.month),
|
||||
month: cn('flex w-full flex-col gap-4', defaultClassNames.month),
|
||||
nav: cn(
|
||||
'flex items-center gap-1 w-full absolute top-0 inset-x-0 justify-between',
|
||||
defaultClassNames.nav,
|
||||
'absolute inset-x-0 top-0 flex w-full items-center justify-between gap-1',
|
||||
defaultClassNames.nav
|
||||
),
|
||||
button_previous: cn(
|
||||
buttonVariants({ variant: buttonVariant }),
|
||||
'size-(--cell-size) aria-disabled:opacity-50 p-0 select-none',
|
||||
defaultClassNames.button_previous,
|
||||
'size-(--cell-size) select-none p-0 aria-disabled:opacity-50',
|
||||
defaultClassNames.button_previous
|
||||
),
|
||||
button_next: cn(
|
||||
buttonVariants({ variant: buttonVariant }),
|
||||
'size-(--cell-size) aria-disabled:opacity-50 p-0 select-none',
|
||||
defaultClassNames.button_next,
|
||||
'size-(--cell-size) select-none p-0 aria-disabled:opacity-50',
|
||||
defaultClassNames.button_next
|
||||
),
|
||||
month_caption: cn(
|
||||
'flex items-center justify-center h-(--cell-size) w-full px-(--cell-size)',
|
||||
defaultClassNames.month_caption,
|
||||
'flex h-(--cell-size) w-full items-center justify-center px-(--cell-size)',
|
||||
defaultClassNames.month_caption
|
||||
),
|
||||
dropdowns: cn(
|
||||
'w-full flex items-center text-sm font-medium justify-center h-(--cell-size) gap-1.5',
|
||||
defaultClassNames.dropdowns,
|
||||
'flex h-(--cell-size) w-full items-center justify-center gap-1.5 font-medium text-sm',
|
||||
defaultClassNames.dropdowns
|
||||
),
|
||||
dropdown_root: cn(
|
||||
'relative has-focus:border-ring border border-input shadow-xs has-focus:ring-ring/50 has-focus:ring-[3px] rounded-md',
|
||||
defaultClassNames.dropdown_root,
|
||||
'relative rounded-md border border-input shadow-xs has-focus:border-ring has-focus:ring-[3px] has-focus:ring-ring/50',
|
||||
defaultClassNames.dropdown_root
|
||||
),
|
||||
dropdown: cn(
|
||||
'absolute bg-popover inset-0 opacity-0',
|
||||
defaultClassNames.dropdown,
|
||||
'absolute inset-0 bg-popover opacity-0',
|
||||
defaultClassNames.dropdown
|
||||
),
|
||||
caption_label: cn(
|
||||
'select-none font-medium',
|
||||
captionLayout === 'label'
|
||||
? 'text-sm'
|
||||
: 'rounded-md pl-2 pr-1 flex items-center gap-1 text-sm h-8 [&>svg]:text-muted-foreground [&>svg]:size-3.5',
|
||||
defaultClassNames.caption_label,
|
||||
: 'flex h-8 items-center gap-1 rounded-md pr-1 pl-2 text-sm [&>svg]:size-3.5 [&>svg]:text-muted-foreground',
|
||||
defaultClassNames.caption_label
|
||||
),
|
||||
table: 'w-full border-collapse',
|
||||
weekdays: cn('flex', defaultClassNames.weekdays),
|
||||
weekday: cn(
|
||||
'text-muted-foreground rounded-md flex-1 font-normal text-[0.8rem] select-none',
|
||||
defaultClassNames.weekday,
|
||||
'flex-1 select-none rounded-md font-normal text-[0.8rem] text-muted-foreground',
|
||||
defaultClassNames.weekday
|
||||
),
|
||||
week: cn('flex w-full mt-2', defaultClassNames.week),
|
||||
week: cn('mt-2 flex w-full', defaultClassNames.week),
|
||||
week_number_header: cn(
|
||||
'select-none w-(--cell-size)',
|
||||
defaultClassNames.week_number_header,
|
||||
'w-(--cell-size) select-none',
|
||||
defaultClassNames.week_number_header
|
||||
),
|
||||
week_number: cn(
|
||||
'text-[0.8rem] select-none text-muted-foreground',
|
||||
defaultClassNames.week_number,
|
||||
'select-none text-[0.8rem] text-muted-foreground',
|
||||
defaultClassNames.week_number
|
||||
),
|
||||
day: cn(
|
||||
'relative w-full h-full p-0 text-center [&:first-child[data-selected=true]_button]:rounded-l-md [&:last-child[data-selected=true]_button]:rounded-r-md group/day aspect-square select-none',
|
||||
defaultClassNames.day,
|
||||
'group/day relative aspect-square h-full w-full select-none p-0 text-center [&:first-child[data-selected=true]_button]:rounded-l-md [&:last-child[data-selected=true]_button]:rounded-r-md',
|
||||
defaultClassNames.day
|
||||
),
|
||||
range_start: cn(
|
||||
'rounded-l-md bg-accent',
|
||||
defaultClassNames.range_start,
|
||||
defaultClassNames.range_start
|
||||
),
|
||||
range_middle: cn('rounded-none', defaultClassNames.range_middle),
|
||||
range_end: cn('rounded-r-md bg-accent', defaultClassNames.range_end),
|
||||
today: cn(
|
||||
'bg-accent text-accent-foreground rounded-md data-[selected=true]:rounded-none',
|
||||
defaultClassNames.today,
|
||||
'rounded-md bg-accent text-accent-foreground data-[selected=true]:rounded-none',
|
||||
defaultClassNames.today
|
||||
),
|
||||
outside: cn(
|
||||
'text-muted-foreground aria-selected:text-muted-foreground',
|
||||
defaultClassNames.outside,
|
||||
defaultClassNames.outside
|
||||
),
|
||||
disabled: cn(
|
||||
'text-muted-foreground opacity-50',
|
||||
defaultClassNames.disabled,
|
||||
defaultClassNames.disabled
|
||||
),
|
||||
hidden: cn('invisible', defaultClassNames.hidden),
|
||||
...classNames,
|
||||
@@ -130,9 +123,9 @@ function Calendar({
|
||||
Root: ({ className, rootRef, ...props }) => {
|
||||
return (
|
||||
<div
|
||||
className={cn(className)}
|
||||
data-slot="calendar"
|
||||
ref={rootRef}
|
||||
className={cn(className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
@@ -169,6 +162,12 @@ function Calendar({
|
||||
},
|
||||
...components,
|
||||
}}
|
||||
formatters={{
|
||||
formatMonthDropdown: (date) =>
|
||||
date.toLocaleString('default', { month: 'short' }),
|
||||
...formatters,
|
||||
}}
|
||||
showOutsideDays={showOutsideDays}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
@@ -184,29 +183,31 @@ function CalendarDayButton({
|
||||
|
||||
const ref = React.useRef<HTMLButtonElement>(null);
|
||||
React.useEffect(() => {
|
||||
if (modifiers.focused) ref.current?.focus();
|
||||
if (modifiers.focused) {
|
||||
ref.current?.focus();
|
||||
}
|
||||
}, [modifiers.focused]);
|
||||
|
||||
return (
|
||||
<Button
|
||||
ref={ref}
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className={cn(
|
||||
'flex aspect-square size-auto w-full min-w-(--cell-size) flex-col gap-1 font-normal leading-none data-[range-end=true]:rounded-md data-[range-middle=true]:rounded-none data-[range-start=true]:rounded-md data-[range-end=true]:rounded-r-md data-[range-start=true]:rounded-l-md data-[range-end=true]:bg-primary data-[range-middle=true]:bg-accent data-[range-start=true]:bg-primary data-[selected-single=true]:bg-primary data-[range-end=true]:text-primary-foreground data-[range-middle=true]:text-accent-foreground data-[range-start=true]:text-primary-foreground data-[selected-single=true]:text-primary-foreground group-data-[focused=true]/day:relative group-data-[focused=true]/day:z-10 group-data-[focused=true]/day:border-ring group-data-[focused=true]/day:ring-[3px] group-data-[focused=true]/day:ring-ring/50 dark:hover:text-accent-foreground [&>span]:text-xs [&>span]:opacity-70',
|
||||
defaultClassNames.day,
|
||||
className
|
||||
)}
|
||||
data-day={day.date.toLocaleDateString()}
|
||||
data-range-end={modifiers.range_end}
|
||||
data-range-middle={modifiers.range_middle}
|
||||
data-range-start={modifiers.range_start}
|
||||
data-selected-single={
|
||||
modifiers.selected &&
|
||||
!modifiers.range_start &&
|
||||
!modifiers.range_end &&
|
||||
!modifiers.range_middle
|
||||
}
|
||||
data-range-start={modifiers.range_start}
|
||||
data-range-end={modifiers.range_end}
|
||||
data-range-middle={modifiers.range_middle}
|
||||
className={cn(
|
||||
'data-[selected-single=true]:bg-primary data-[selected-single=true]:text-primary-foreground data-[range-middle=true]:bg-accent data-[range-middle=true]:text-accent-foreground data-[range-start=true]:bg-primary data-[range-start=true]:text-primary-foreground data-[range-end=true]:bg-primary data-[range-end=true]:text-primary-foreground group-data-[focused=true]/day:border-ring group-data-[focused=true]/day:ring-ring/50 dark:hover:text-accent-foreground flex aspect-square size-auto w-full min-w-(--cell-size) flex-col gap-1 leading-none font-normal group-data-[focused=true]/day:relative group-data-[focused=true]/day:z-10 group-data-[focused=true]/day:ring-[3px] data-[range-end=true]:rounded-md data-[range-end=true]:rounded-r-md data-[range-middle=true]:rounded-none data-[range-start=true]:rounded-md data-[range-start=true]:rounded-l-md [&>span]:text-xs [&>span]:opacity-70',
|
||||
defaultClassNames.day,
|
||||
className,
|
||||
)}
|
||||
ref={ref}
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -1,20 +1,24 @@
|
||||
import { getDefaultIntervalByDates } from '@openpanel/constants';
|
||||
import type { IInterval } from '@openpanel/validation';
|
||||
import { endOfDay, subMonths } from 'date-fns';
|
||||
import { CheckIcon, XIcon } from 'lucide-react';
|
||||
import { useState } from 'react';
|
||||
import { popModal } from '.';
|
||||
import { ModalContent } from './Modal/Container';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Calendar } from '@/components/ui/calendar';
|
||||
import { useBreakpoint } from '@/hooks/use-breakpoint';
|
||||
import { subMonths } from 'date-fns';
|
||||
import { useState } from 'react';
|
||||
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { formatDate } from '@/utils/date';
|
||||
import { CheckIcon, XIcon } from 'lucide-react';
|
||||
import { popModal } from '.';
|
||||
import { ModalContent, ModalHeader } from './Modal/Container';
|
||||
|
||||
type Props = {
|
||||
onChange: (payload: { startDate: Date; endDate: Date }) => void;
|
||||
interface Props {
|
||||
onChange: (payload: {
|
||||
startDate: Date;
|
||||
endDate: Date;
|
||||
interval: IInterval;
|
||||
}) => void;
|
||||
startDate?: Date;
|
||||
endDate?: Date;
|
||||
};
|
||||
}
|
||||
export default function DateRangerPicker({
|
||||
onChange,
|
||||
startDate: initialStartDate,
|
||||
@@ -25,20 +29,20 @@ export default function DateRangerPicker({
|
||||
const [endDate, setEndDate] = useState(initialEndDate);
|
||||
|
||||
return (
|
||||
<ModalContent className="p-4 md:p-8 min-w-fit">
|
||||
<ModalContent className="min-w-fit p-4 md:p-8">
|
||||
<Calendar
|
||||
captionLayout="dropdown"
|
||||
initialFocus
|
||||
mode="range"
|
||||
className="mx-auto min-h-[310px] p-0 [&_table]:mx-auto [&_table]:w-auto"
|
||||
defaultMonth={subMonths(
|
||||
startDate ? new Date(startDate) : new Date(),
|
||||
isBelowSm ? 0 : 1,
|
||||
isBelowSm ? 0 : 1
|
||||
)}
|
||||
selected={{
|
||||
from: startDate,
|
||||
to: endDate,
|
||||
hidden={{
|
||||
after: endOfDay(new Date()),
|
||||
}}
|
||||
toDate={new Date()}
|
||||
initialFocus
|
||||
mode="range"
|
||||
numberOfMonths={isBelowSm ? 1 : 2}
|
||||
onSelect={(range) => {
|
||||
if (range?.from) {
|
||||
setStartDate(range.from);
|
||||
@@ -47,33 +51,39 @@ export default function DateRangerPicker({
|
||||
setEndDate(range.to);
|
||||
}
|
||||
}}
|
||||
numberOfMonths={isBelowSm ? 1 : 2}
|
||||
className="mx-auto min-h-[310px] [&_table]:mx-auto [&_table]:w-auto p-0"
|
||||
selected={{
|
||||
from: startDate,
|
||||
to: endDate,
|
||||
}}
|
||||
/>
|
||||
<div className="col flex-col-reverse md:row gap-2">
|
||||
<div className="col md:row flex-col-reverse gap-2">
|
||||
<Button
|
||||
icon={XIcon}
|
||||
onClick={() => popModal()}
|
||||
type="button"
|
||||
variant="outline"
|
||||
onClick={() => popModal()}
|
||||
icon={XIcon}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
|
||||
{startDate && endDate && (
|
||||
<Button
|
||||
type="button"
|
||||
className="md:ml-auto"
|
||||
icon={startDate && endDate ? CheckIcon : XIcon}
|
||||
onClick={() => {
|
||||
popModal();
|
||||
if (startDate && endDate) {
|
||||
onChange({
|
||||
startDate: startDate,
|
||||
endDate: endDate,
|
||||
startDate,
|
||||
endDate,
|
||||
interval: getDefaultIntervalByDates(
|
||||
startDate.toISOString(),
|
||||
endDate.toISOString()
|
||||
)!,
|
||||
});
|
||||
}
|
||||
}}
|
||||
icon={startDate && endDate ? CheckIcon : XIcon}
|
||||
type="button"
|
||||
>
|
||||
{startDate && endDate
|
||||
? `Select ${formatDate(startDate)} - ${formatDate(endDate)}`
|
||||
|
||||
@@ -27,7 +27,7 @@ type GscChartData = { date: string; clicks: number; impressions: number };
|
||||
|
||||
const { TooltipProvider, Tooltip: GscTooltip } = createChartTooltip<
|
||||
GscChartData,
|
||||
Record<string, never>
|
||||
Record<string, unknown>
|
||||
>(({ data }) => {
|
||||
const item = data[0];
|
||||
if (!item) {
|
||||
@@ -267,7 +267,7 @@ function GscViewsChart({
|
||||
const yAxisProps = useYAxisProps();
|
||||
|
||||
return (
|
||||
<TooltipProvider data={[]}>
|
||||
<TooltipProvider>
|
||||
<ResponsiveContainer height={160} width="100%">
|
||||
<ComposedChart data={data}>
|
||||
<defs>
|
||||
@@ -328,7 +328,7 @@ function GscTimeseriesChart({
|
||||
const yAxisProps = useYAxisProps();
|
||||
|
||||
return (
|
||||
<TooltipProvider data={data}>
|
||||
<TooltipProvider>
|
||||
<ResponsiveContainer height={160} width="100%">
|
||||
<ComposedChart data={data}>
|
||||
<defs>
|
||||
|
||||
@@ -1,13 +1,6 @@
|
||||
import {
|
||||
getDefaultIntervalByRange,
|
||||
intervals,
|
||||
timeWindows,
|
||||
} from '@openpanel/constants';
|
||||
import type { IChartRange, IInterval } from '@openpanel/validation';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { createFileRoute, useNavigate } from '@tanstack/react-router';
|
||||
import { SearchIcon } from 'lucide-react';
|
||||
import { parseAsString, parseAsStringEnum, useQueryState } from 'nuqs';
|
||||
import { useMemo, useState } from 'react';
|
||||
import {
|
||||
CartesianGrid,
|
||||
@@ -23,8 +16,11 @@ import {
|
||||
createChartTooltip,
|
||||
} from '@/components/charts/chart-tooltip';
|
||||
import { FullPageEmptyState } from '@/components/full-page-empty-state';
|
||||
import { OverviewInterval } from '@/components/overview/overview-interval';
|
||||
import { OverviewMetricCard } from '@/components/overview/overview-metric-card';
|
||||
import { OverviewRange } from '@/components/overview/overview-range';
|
||||
import { OverviewWidgetTable } from '@/components/overview/overview-widget-table';
|
||||
import { useOverviewOptions } from '@/components/overview/useOverviewOptions';
|
||||
import { GscCannibalization } from '@/components/page/gsc-cannibalization';
|
||||
import { GscCtrBenchmark } from '@/components/page/gsc-ctr-benchmark';
|
||||
import { GscPositionChart } from '@/components/page/gsc-position-chart';
|
||||
@@ -32,14 +28,12 @@ import { PagesInsights } from '@/components/page/pages-insights';
|
||||
import { PageContainer } from '@/components/page-container';
|
||||
import { PageHeader } from '@/components/page-header';
|
||||
import { Pagination } from '@/components/pagination';
|
||||
import { ReportInterval } from '@/components/report/ReportInterval';
|
||||
import {
|
||||
useYAxisProps,
|
||||
X_AXIS_STYLE_PROPS,
|
||||
} from '@/components/report-chart/common/axis';
|
||||
import { SerieIcon } from '@/components/report-chart/common/serie-icon';
|
||||
import { Skeleton } from '@/components/skeleton';
|
||||
import { TimeWindowPicker } from '@/components/time-window-picker';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { useAppParams } from '@/hooks/use-app-params';
|
||||
@@ -94,27 +88,13 @@ function SeoPage() {
|
||||
const { projectId, organizationId } = useAppParams();
|
||||
const trpc = useTRPC();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const [range, setRange] = useQueryState(
|
||||
'range',
|
||||
parseAsStringEnum(Object.keys(timeWindows) as IChartRange[]).withDefault(
|
||||
'30d' as IChartRange
|
||||
)
|
||||
);
|
||||
const [startDate, setStartDate] = useQueryState('start', parseAsString);
|
||||
const [endDate, setEndDate] = useQueryState('end', parseAsString);
|
||||
const [interval, setInterval] = useQueryState(
|
||||
'interval',
|
||||
parseAsStringEnum(Object.keys(intervals) as IInterval[]).withDefault(
|
||||
(getDefaultIntervalByRange(range) ?? 'day') as IInterval
|
||||
)
|
||||
);
|
||||
const { range, startDate, endDate, interval } = useOverviewOptions();
|
||||
|
||||
const dateInput = {
|
||||
range,
|
||||
interval,
|
||||
startDate: startDate ?? undefined,
|
||||
endDate: endDate ?? undefined,
|
||||
startDate,
|
||||
endDate,
|
||||
};
|
||||
|
||||
const connectionQuery = useQuery(
|
||||
@@ -265,31 +245,8 @@ function SeoPage() {
|
||||
<PageHeader
|
||||
actions={
|
||||
<>
|
||||
<ReportInterval
|
||||
chartType="linear"
|
||||
endDate={endDate}
|
||||
interval={interval ?? 'day'}
|
||||
onChange={(v) => setInterval(v)}
|
||||
range={range}
|
||||
startDate={startDate}
|
||||
/>
|
||||
<TimeWindowPicker
|
||||
endDate={endDate}
|
||||
onChange={(v) => {
|
||||
if (v !== 'custom') {
|
||||
setStartDate(null);
|
||||
setEndDate(null);
|
||||
}
|
||||
setInterval(
|
||||
(getDefaultIntervalByRange(v) ?? 'day') as IInterval
|
||||
);
|
||||
setRange(v);
|
||||
}}
|
||||
onEndDateChange={setEndDate}
|
||||
onStartDateChange={setStartDate}
|
||||
startDate={startDate}
|
||||
value={range}
|
||||
/>
|
||||
<OverviewRange />
|
||||
<OverviewInterval />
|
||||
</>
|
||||
}
|
||||
description={`Search performance for ${connection.siteUrl}`}
|
||||
|
||||
@@ -24,11 +24,13 @@ const zGscDateInput = z.object({
|
||||
projectId: z.string(),
|
||||
range: zRange,
|
||||
interval: zTimeInterval.optional().default('day'),
|
||||
startDate: z.string().nullish(),
|
||||
endDate: z.string().nullish(),
|
||||
});
|
||||
|
||||
async function resolveDates(
|
||||
projectId: string,
|
||||
input: { range: string; startDate?: string; endDate?: string }
|
||||
input: { range: string; startDate?: string | null; endDate?: string | null }
|
||||
) {
|
||||
const { timezone } = await getSettingsForProject(projectId);
|
||||
const { startDate, endDate } = getChartStartEndDate(
|
||||
|
||||
Reference in New Issue
Block a user