Update annotation date picker and place annotation actions beside chart legend
This commit is contained in:
1 parent
fed1ff76f6
commit
5f92ec5c49
7 files changed
+43
-70
No files matched your search
+1
-1
@@ -83,7 +83,7 @@
|
||||
"@tanstack/react-query": "^5.102.8",
|
||||
"@umami/api-client": "workspace:*",
|
||||
"@umami/mcp": "workspace:*",
|
||||
"@umami/react-zen": "^0.253.0",
|
||||
"@umami/react-zen": "^0.254.0",
|
||||
"bcryptjs": "^3.0.2",
|
||||
"chalk": "^5.6.2",
|
||||
"chart.js": "^4.5.1",
|
||||
|
||||
Generated
+5
-5
@@ -62,8 +62,8 @@ importers:
|
||||
specifier: workspace:*
|
||||
version: link:packages/mcp
|
||||
'@umami/react-zen':
|
||||
specifier: ^0.253.0
|
||||
version: 0.253.0(@date-fns/tz@1.5.0)(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(date-fns@4.4.0)(immer@11.1.18)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(tailwindcss@4.1.18)(use-sync-external-store@1.6.0(react@19.3.0))
|
||||
specifier: ^0.254.0
|
||||
version: 0.254.0(@date-fns/tz@1.5.0)(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(date-fns@4.4.0)(immer@11.1.18)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(tailwindcss@4.1.18)(use-sync-external-store@1.6.0(react@19.3.0))
|
||||
bcryptjs:
|
||||
specifier: ^3.0.2
|
||||
version: 3.0.3
|
||||
@@ -2774,8 +2774,8 @@ packages:
|
||||
'@umami/api-client@0.81.0':
|
||||
resolution: {integrity: sha512-cwkQbe8U/m+zEgfe+nNExU0AoY8BSd+x4Qs7i5nfR2gGrktfoH6Y375e237OFYpmvhJr5BJdML1Vzuu6v7+kmw==}
|
||||
|
||||
'@umami/react-zen@0.253.0':
|
||||
resolution: {integrity: sha512-ilprZZ+CM+ryHYcNFJKh2+DJFdnyby2y8UPa458e9hG5dXTtv5qpnhVcr6xSTxBfZqoMSC992vcXbWsn+PhjRg==}
|
||||
'@umami/react-zen@0.254.0':
|
||||
resolution: {integrity: sha512-1Ip3C3UuL+ltBTjGmLLB5y1IgxwNBtxYY8z/jkYyfVIpX3mNMP6NKdQIqZGHgrbgcq7hj88dslfD9Gc2SzJk1w==}
|
||||
peerDependencies:
|
||||
react: ^18.0.0 || ^19.0.0
|
||||
react-dom: ^18.0.0 || ^19.0.0
|
||||
@@ -7955,7 +7955,7 @@ snapshots:
|
||||
|
||||
'@umami/api-client@0.81.0': {}
|
||||
|
||||
'@umami/react-zen@0.253.0(@date-fns/tz@1.5.0)(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(date-fns@4.4.0)(immer@11.1.18)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(tailwindcss@4.1.18)(use-sync-external-store@1.6.0(react@19.3.0))':
|
||||
'@umami/react-zen@0.254.0(@date-fns/tz@1.5.0)(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(date-fns@4.4.0)(immer@11.1.18)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(tailwindcss@4.1.18)(use-sync-external-store@1.6.0(react@19.3.0))':
|
||||
dependencies:
|
||||
'@base-ui/react': 1.8.0(@date-fns/tz@1.5.0)(@types/react@19.3.0)(date-fns@4.4.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)
|
||||
'@react-spring/web': 9.7.5(react-dom@19.3.0(react@19.3.0))(react@19.3.0)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { isSameDay } from 'date-fns';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { type ReactNode, useCallback, useMemo } from 'react';
|
||||
import type { ChartAnnotation } from '@/components/charts/ChartAnnotationMarkers';
|
||||
import { LoadingPanel } from '@/components/common/LoadingPanel';
|
||||
import {
|
||||
@@ -19,11 +19,13 @@ export function WebsiteChart({
|
||||
compareMode,
|
||||
showAnnotations,
|
||||
onAnnotationMoreClick,
|
||||
legendActions,
|
||||
}: {
|
||||
websiteId: string;
|
||||
compareMode?: boolean;
|
||||
showAnnotations?: boolean;
|
||||
onAnnotationMoreClick?: (range: AnnotationRange) => void;
|
||||
legendActions?: ReactNode;
|
||||
}) {
|
||||
const { timezone, localFromUtc, localToUtc } = useTimezone();
|
||||
const { dateRange, dateCompare } = useDateRange({ timezone: timezone });
|
||||
@@ -135,6 +137,7 @@ export function WebsiteChart({
|
||||
<PageviewsChart
|
||||
key={value}
|
||||
data={chartData}
|
||||
legendActions={legendActions}
|
||||
minDate={startDate}
|
||||
maxDate={endDate}
|
||||
unit={unit}
|
||||
|
||||
@@ -4,8 +4,8 @@ import { useState } from 'react';
|
||||
import { ExpandedViewModal } from '@/app/(main)/websites/[websiteId]/ExpandedViewModal';
|
||||
import { Panel } from '@/components/common/Panel';
|
||||
import { DialogButton } from '@/components/input/DialogButton';
|
||||
import type { AnnotationRange } from '@/lib/annotations';
|
||||
import { UnitFilter } from '@/components/input/UnitFilter';
|
||||
import type { AnnotationRange } from '@/lib/annotations';
|
||||
import { AnnotationsButton } from './annotations/AnnotationsButton';
|
||||
import { AnnotationsModal } from './annotations/AnnotationsModal';
|
||||
import { WebsiteChart } from './WebsiteChart';
|
||||
@@ -28,10 +28,8 @@ export function WebsitePage({ websiteId }: { websiteId: string }) {
|
||||
websiteId={websiteId}
|
||||
showAnnotations
|
||||
onAnnotationMoreClick={setAnnotationRange}
|
||||
legendActions={<AnnotationsButton websiteId={websiteId} />}
|
||||
/>
|
||||
<Row justifyContent="end">
|
||||
<AnnotationsButton websiteId={websiteId} />
|
||||
</Row>
|
||||
</Panel>
|
||||
<WebsitePanels websiteId={websiteId} />
|
||||
<ExpandedViewModal websiteId={websiteId} />
|
||||
|
||||
@@ -1,23 +1,17 @@
|
||||
import {
|
||||
Button,
|
||||
Calendar as ZenCalendar,
|
||||
Column,
|
||||
DialogTrigger,
|
||||
DatePicker,
|
||||
Form,
|
||||
FormButtons,
|
||||
FormField,
|
||||
FormSubmitButton,
|
||||
Icon,
|
||||
Label,
|
||||
Popover,
|
||||
Text,
|
||||
TextField,
|
||||
} from '@umami/react-zen';
|
||||
import { startOfDay } from 'date-fns';
|
||||
import { useMemo, useState } from 'react';
|
||||
import { useLocale, useMessages, useTimezone, useUpdateQuery } from '@/components/hooks';
|
||||
import { Calendar } from '@/components/icons';
|
||||
import { formatDate } from '@/lib/date';
|
||||
|
||||
export interface Annotation {
|
||||
id: string;
|
||||
@@ -27,51 +21,6 @@ export interface Annotation {
|
||||
note: string;
|
||||
}
|
||||
|
||||
function AnnotationDatePicker({
|
||||
date,
|
||||
label,
|
||||
locale,
|
||||
onChange,
|
||||
}: {
|
||||
date: Date;
|
||||
label: string;
|
||||
locale: string;
|
||||
onChange: (date: Date) => void;
|
||||
}) {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
const handleChange = (value: Date) => {
|
||||
onChange(value);
|
||||
setIsOpen(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<Column gap="1">
|
||||
<Label>{label}</Label>
|
||||
<DialogTrigger overlayType="popover" isOpen={isOpen} onOpenChange={setIsOpen}>
|
||||
<Button
|
||||
variant="outline"
|
||||
aria-label={label}
|
||||
style={{ width: 'fit-content', cursor: 'pointer', flexDirection: 'row-reverse' }}
|
||||
>
|
||||
<Icon size="sm">
|
||||
<Calendar />
|
||||
</Icon>
|
||||
{formatDate(date, 'PP', locale)}
|
||||
</Button>
|
||||
<Popover
|
||||
side="bottom"
|
||||
align="start"
|
||||
sideOffset={8}
|
||||
className="min-w-[340px] bg-surface-overlay border border-edge-muted rounded-lg shadow-lg p-4"
|
||||
>
|
||||
<ZenCalendar value={date} onChange={handleChange} />
|
||||
</Popover>
|
||||
</DialogTrigger>
|
||||
</Column>
|
||||
);
|
||||
}
|
||||
|
||||
export function AnnotationEditForm({
|
||||
annotation,
|
||||
websiteId,
|
||||
@@ -127,11 +76,16 @@ export function AnnotationEditForm({
|
||||
|
||||
return (
|
||||
<Column gap>
|
||||
<AnnotationDatePicker
|
||||
<DatePicker
|
||||
triggerProps={{ style: { width: '100%', maxWidth: 280 } }}
|
||||
label={t(labels.date)}
|
||||
date={date}
|
||||
value={date}
|
||||
locale={locale}
|
||||
onChange={setDate}
|
||||
onChange={value => {
|
||||
if (value) {
|
||||
setDate(value);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
||||
<Column gap="1">
|
||||
|
||||
@@ -13,7 +13,6 @@ import { formatAnnotationDate } from '@/components/charts/ChartAnnotationMarkers
|
||||
import { Empty } from '@/components/common/Empty';
|
||||
import { IconLabel } from '@/components/common/IconLabel';
|
||||
import { Pager } from '@/components/common/Pager';
|
||||
import { FilterButtons } from '@/components/input/FilterButtons';
|
||||
import {
|
||||
useLocale,
|
||||
useMessages,
|
||||
@@ -24,6 +23,7 @@ import {
|
||||
useWebsiteAnnotationsQuery,
|
||||
} from '@/components/hooks';
|
||||
import { Edit, Plus } from '@/components/icons';
|
||||
import { FilterButtons } from '@/components/input/FilterButtons';
|
||||
import { type AnnotationRange, getAnnotationDateRangeValue } from '@/lib/annotations';
|
||||
import { AnnotationDeleteButton } from './AnnotationDeleteButton';
|
||||
import { type Annotation, AnnotationEditForm } from './AnnotationEditForm';
|
||||
@@ -99,7 +99,7 @@ export function AnnotationsModal({
|
||||
const rows: Annotation[] = data?.data || [];
|
||||
|
||||
return (
|
||||
<Column gap>
|
||||
<Column gap flexGrow={1}>
|
||||
<Row alignItems="center" justifyContent="space-between">
|
||||
<Heading size="xl">{t(labels.notes)}</Heading>
|
||||
{!share && (
|
||||
@@ -155,7 +155,7 @@ export function AnnotationsModal({
|
||||
<Pager page={page} pageSize={PAGE_SIZE} count={data.count} onPageChange={setPage} />
|
||||
)}
|
||||
{onClose && (
|
||||
<Row justifyContent="flex-end">
|
||||
<Row justifyContent="flex-end" style={{ marginTop: 'auto' }}>
|
||||
<Button onPress={onClose}>{t(labels.close)}</Button>
|
||||
</Row>
|
||||
)}
|
||||
|
||||
@@ -5,7 +5,7 @@ import ChartJS, {
|
||||
type LegendItem,
|
||||
type UpdateMode,
|
||||
} from 'chart.js/auto';
|
||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { type ReactNode, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import {
|
||||
type AnnotationMarker,
|
||||
type ChartAnnotation,
|
||||
@@ -26,6 +26,7 @@ export interface ChartProps extends BoxProps {
|
||||
onTooltip?: (model: any) => void;
|
||||
hiddenLabels?: Set<string>;
|
||||
onLegendClick?: (label: string, willBeHidden: boolean) => void;
|
||||
legendActions?: ReactNode;
|
||||
annotations?: ChartAnnotation[];
|
||||
onAnnotationClick?: (annotations: ChartAnnotation[]) => void;
|
||||
onAnnotationMoreClick?: (annotations: ChartAnnotation[]) => void;
|
||||
@@ -53,6 +54,7 @@ export function Chart({
|
||||
chartOptions,
|
||||
hiddenLabels,
|
||||
onLegendClick,
|
||||
legendActions,
|
||||
annotations,
|
||||
onAnnotationClick,
|
||||
onAnnotationMoreClick,
|
||||
@@ -234,7 +236,23 @@ export function Chart({
|
||||
)}
|
||||
</div>
|
||||
</Box>
|
||||
{legendActions ? (
|
||||
<div
|
||||
style={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: '1fr auto 1fr',
|
||||
alignItems: 'center',
|
||||
gap: 8,
|
||||
}}
|
||||
>
|
||||
<div style={{ gridColumn: 2 }}>
|
||||
<Legend items={legendItems} onClick={handleLegendClick} />
|
||||
</div>
|
||||
<div style={{ justifySelf: 'end' }}>{legendActions}</div>
|
||||
</div>
|
||||
) : (
|
||||
<Legend items={legendItems} onClick={handleLegendClick} />
|
||||
)}
|
||||
</Column>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user