From faafb71d88811d16db52f89731747325adf99b04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carl-Gerhard=20Lindesva=CC=88rd?= Date: Sat, 9 Mar 2024 21:53:40 +0100 Subject: [PATCH] projects overview and event list improvements --- apps/web/package.json | 2 + .../[projectId]/events/event-details.tsx | 28 +- .../[projectId]/events/event-edit.tsx | 1 + .../[projectId]/events/event-list-item.tsx | 2 +- .../[projectId]/events/event-list.tsx | 4 +- .../[organizationId]/[projectId]/layout.tsx | 3 +- .../[projectId]/reports/report-editor.tsx | 2 +- .../src/app/(app)/[organizationId]/page.tsx | 33 +- apps/web/src/components/chart-ssr.tsx | 68 ++++ .../src/components/projects/project-card.tsx | 68 ++++ .../report/sidebar/ReportSidebar.tsx | 18 +- apps/web/src/components/ui/sheet.tsx | 5 +- apps/web/src/hooks/useNumerFormatter.ts | 14 +- packages/db/src/services/project.service.ts | 35 +- packages/validation/src/index.ts | 16 +- pnpm-lock.yaml | 345 ++++++++++++++++++ 16 files changed, 585 insertions(+), 59 deletions(-) create mode 100644 apps/web/src/components/chart-ssr.tsx create mode 100644 apps/web/src/components/projects/project-card.tsx diff --git a/apps/web/package.json b/apps/web/package.json index 4e877e61..3c793cae 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -45,10 +45,12 @@ "@trpc/next": "^10.45.1", "@trpc/react-query": "^10.45.1", "@trpc/server": "^10.45.1", + "@types/d3": "^7.4.3", "bcrypt": "^5.1.1", "class-variance-authority": "^0.7.0", "clsx": "^2.1.0", "cmdk": "^0.2.1", + "d3": "^7.8.5", "date-fns": "^3.3.1", "embla-carousel-react": "8.0.0-rc22", "flag-icons": "^7.1.0", diff --git a/apps/web/src/app/(app)/[organizationId]/[projectId]/events/event-details.tsx b/apps/web/src/app/(app)/[organizationId]/[projectId]/events/event-details.tsx index 67709043..8d980e78 100644 --- a/apps/web/src/app/(app)/[organizationId]/[projectId]/events/event-details.tsx +++ b/apps/web/src/app/(app)/[organizationId]/[projectId]/events/event-details.tsx @@ -1,17 +1,18 @@ 'use client'; import type { Dispatch, SetStateAction } from 'react'; -import { ChartSwitch, ChartSwitchShortcut } from '@/components/report/chart'; -import { Chart } from '@/components/report/chart/Chart'; +import { ChartSwitchShortcut } from '@/components/report/chart'; import { KeyValue } from '@/components/ui/key-value'; -import { ScrollArea } from '@/components/ui/scroll-area'; import { Sheet, SheetContent, SheetHeader, SheetTitle, } from '@/components/ui/sheet'; -import { useEventQueryFilters } from '@/hooks/useEventQueryFilters'; +import { + useEventQueryFilters, + useEventQueryNamesFilter, +} from '@/hooks/useEventQueryFilters'; import { round } from 'mathjs'; import type { IServiceCreateEventPayload } from '@mixan/db'; @@ -24,6 +25,8 @@ interface Props { export function EventDetails({ event, open, setOpen }: Props) { const { name } = event; const [, setFilter] = useEventQueryFilters({ shallow: false }); + const [, setEvents] = useEventQueryNamesFilter({ shallow: false }); + const common = [ { name: 'Duration', @@ -138,8 +141,8 @@ export function EventDetails({ event, open, setOpen }: Props) { return ( - -
+ +
{name.replace('_', ' ')} @@ -181,7 +184,18 @@ export function EventDetails({ event, open, setOpen }: Props) {
-
Similar events
+
+
Similar events
+ +
diff --git a/apps/web/src/app/(app)/[organizationId]/[projectId]/events/event-list.tsx b/apps/web/src/app/(app)/[organizationId]/[projectId]/events/event-list.tsx index de643096..bb9d28f4 100644 --- a/apps/web/src/app/(app)/[organizationId]/[projectId]/events/event-list.tsx +++ b/apps/web/src/app/(app)/[organizationId]/[projectId]/events/event-list.tsx @@ -56,7 +56,7 @@ export function EventList({ data, count }: EventListProps) { ) : ( <> -
+
-
+
{data.map((item, index, list) => ( {showDateHeader(item.createdAt, list[index - 1]?.createdAt) && ( diff --git a/apps/web/src/app/(app)/[organizationId]/[projectId]/layout.tsx b/apps/web/src/app/(app)/[organizationId]/[projectId]/layout.tsx index 8815ce48..a4fd55ef 100644 --- a/apps/web/src/app/(app)/[organizationId]/[projectId]/layout.tsx +++ b/apps/web/src/app/(app)/[organizationId]/[projectId]/layout.tsx @@ -1,6 +1,7 @@ import { getCurrentOrganizations, getDashboardsByOrganization, + getDashboardsByProjectId, } from '@mixan/db'; import { LayoutSidebar } from './layout-sidebar'; @@ -19,7 +20,7 @@ export default async function AppLayout({ }: AppLayoutProps) { const [organizations, dashboards] = await Promise.all([ getCurrentOrganizations(), - getDashboardsByOrganization(organizationId), + getDashboardsByProjectId(projectId), ]); return ( diff --git a/apps/web/src/app/(app)/[organizationId]/[projectId]/reports/report-editor.tsx b/apps/web/src/app/(app)/[organizationId]/[projectId]/reports/report-editor.tsx index 00c1b74e..4b81c153 100644 --- a/apps/web/src/app/(app)/[organizationId]/[projectId]/reports/report-editor.tsx +++ b/apps/web/src/app/(app)/[organizationId]/[projectId]/reports/report-editor.tsx @@ -102,7 +102,7 @@ export default function ReportEditor({ )}
- + diff --git a/apps/web/src/app/(app)/[organizationId]/page.tsx b/apps/web/src/app/(app)/[organizationId]/page.tsx index b94ffb8e..79b31737 100644 --- a/apps/web/src/app/(app)/[organizationId]/page.tsx +++ b/apps/web/src/app/(app)/[organizationId]/page.tsx @@ -1,7 +1,11 @@ import { LogoSquare } from '@/components/Logo'; +import { ProjectCard } from '@/components/projects/project-card'; import { notFound, redirect } from 'next/navigation'; -import { getOrganizationBySlug, getProjectWithMostEvents } from '@mixan/db'; +import { + getOrganizationBySlug, + getProjectsByOrganizationSlug, +} from '@mixan/db'; import { CreateProject } from './create-project'; @@ -12,9 +16,9 @@ interface PageProps { } export default async function Page({ params: { organizationId } }: PageProps) { - const [organization, project] = await Promise.all([ + const [organization, projects] = await Promise.all([ getOrganizationBySlug(organizationId), - getProjectWithMostEvents(organizationId), + getProjectsByOrganizationSlug(organizationId), ]); if (!organization) { @@ -36,15 +40,26 @@ export default async function Page({ params: { organizationId } }: PageProps) { ); } - if (project) { - return redirect(`/${organizationId}/${project.id}`); + if (projects.length === 0) { + return ( +
+
+ +
+
+ ); + } + + if (projects.length === 1 && projects[0]) { + return redirect(`/${organizationId}/${projects[0].id}`); } return ( -
-
- -
+
+

Select project

+ {projects.map((item) => ( + + ))}
); } diff --git a/apps/web/src/components/chart-ssr.tsx b/apps/web/src/components/chart-ssr.tsx new file mode 100644 index 00000000..c84833e2 --- /dev/null +++ b/apps/web/src/components/chart-ssr.tsx @@ -0,0 +1,68 @@ +import * as d3 from 'd3'; + +export function ChartSSR({ + data, + dots = false, +}: { + dots?: boolean; + data: { value: number; date: Date }[]; +}) { + const xScale = d3 + .scaleTime() + .domain([data[0]!.date, data[data.length - 1]!.date]) + .range([0, 100]); + const yScale = d3 + .scaleLinear() + .domain([0, d3.max(data.map((d) => d.value)) ?? 0]) + .range([100, 0]); + + const line = d3 + .line<(typeof data)[number]>() + .curve(d3.curveMonotoneX) + .x((d) => xScale(d.date)) + .y((d) => yScale(d.value)); + + const d = line(data); + + if (!d) { + return null; + } + + return ( +
+ {/* Chart area */} + + + {/* Line */} + + + {/* Circles */} + {dots && + data.map((d) => ( + + ))} + + +
+ ); +} diff --git a/apps/web/src/components/projects/project-card.tsx b/apps/web/src/components/projects/project-card.tsx new file mode 100644 index 00000000..7918eae6 --- /dev/null +++ b/apps/web/src/components/projects/project-card.tsx @@ -0,0 +1,68 @@ +import { shortNumber } from '@/hooks/useNumerFormatter'; +import Link from 'next/link'; + +import type { IServiceProject } from '@mixan/db'; +import { chQuery } from '@mixan/db'; + +import { ChartSSR } from '../chart-ssr'; + +export async function ProjectCard({ + id, + name, + organizationSlug, +}: IServiceProject) { + const [chart, [data]] = await Promise.all([ + chQuery<{ value: number; date: string }>( + `SELECT countDistinct(profile_id) as value, toStartOfDay(created_at) as date FROM events WHERE project_id = '${id}' AND name = 'session_start' AND created_at >= now() - interval '1 month' GROUP BY date ORDER BY date ASC` + ), + chQuery<{ total: number; month: number; day: number }>( + ` + SELECT + ( + SELECT count(DISTINCT profile_id) as count FROM events WHERE project_id = '${id}' + ) as total, + ( + SELECT count(DISTINCT profile_id) as count FROM events WHERE project_id = '${id}' AND created_at >= now() - interval '1 month' + ) as month, + ( + SELECT count(DISTINCT profile_id) as count FROM events WHERE project_id = '${id}' AND created_at >= now() - interval '1 day' + ) as day + ` + ), + ]); + + return ( + +
{name}
+
+ ({ ...d, date: new Date(d.date) }))} /> +
+
+
Visitors
+
+
+
Total
+ + {shortNumber('en')(data?.total)} + +
+
+
Month
+ + {shortNumber('en')(data?.month)} + +
+
+
24h
+ + {shortNumber('en')(data?.day)} + +
+
+
+ + ); +} diff --git a/apps/web/src/components/report/sidebar/ReportSidebar.tsx b/apps/web/src/components/report/sidebar/ReportSidebar.tsx index 5e64107e..2a4b8011 100644 --- a/apps/web/src/components/report/sidebar/ReportSidebar.tsx +++ b/apps/web/src/components/report/sidebar/ReportSidebar.tsx @@ -1,5 +1,5 @@ import { Button } from '@/components/ui/button'; -import { SheetClose } from '@/components/ui/sheet'; +import { SheetClose, SheetFooter } from '@/components/ui/sheet'; import { useSelector } from '@/redux'; import { ReportBreakdowns } from './ReportBreakdowns'; @@ -11,15 +11,17 @@ export function ReportSidebar() { const showForumula = chartType !== 'funnel'; const showBreakdown = chartType !== 'funnel'; return ( -
- - {showForumula && } - {showBreakdown && } -
+ <> +
+ + {showForumula && } + {showBreakdown && } +
+ -
-
+ + ); } diff --git a/apps/web/src/components/ui/sheet.tsx b/apps/web/src/components/ui/sheet.tsx index 0b51d951..87fe3a5e 100644 --- a/apps/web/src/components/ui/sheet.tsx +++ b/apps/web/src/components/ui/sheet.tsx @@ -31,14 +31,14 @@ const SheetOverlay = React.forwardRef< SheetOverlay.displayName = SheetPrimitive.Overlay.displayName; const sheetVariants = cva( - 'overflow-y-auto fixed z-50 gap-4 bg-background p-6 rounded-lg shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500', + 'flex flex-col max-sm:w-[calc(100%-theme(spacing.8))] overflow-y-auto fixed z-50 gap-4 bg-background p-6 rounded-lg shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500', { variants: { side: { top: 'inset-x-4 top-4 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top', bottom: 'inset-x-4 bottom-4 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom', - left: 'top-4 bottom-4 left-4 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm', + left: 'top-4 bottom-4 left-4 w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm', right: 'top-4 bottom-4 right-4 w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm', }, @@ -101,6 +101,7 @@ const SheetFooter = ({
{ +export const formatNumber = + (locale: string) => (value: number | null | undefined) => { if (isNil(value)) { return 'N/A'; } @@ -19,7 +17,9 @@ export function useNumber() { maximumSignificantDigits: 20, }).format(value); }; - const short = (value: number | null | undefined) => { + +export const shortNumber = + (locale: string) => (value: number | null | undefined) => { if (isNil(value)) { return 'N/A'; } @@ -28,6 +28,10 @@ export function useNumber() { }).format(value); }; +export function useNumber() { + const locale = 'en-gb'; + const format = formatNumber(locale); + const short = shortNumber(locale); return { format, short, diff --git a/packages/db/src/services/project.service.ts b/packages/db/src/services/project.service.ts index 45d26bf0..47cc2a14 100644 --- a/packages/db/src/services/project.service.ts +++ b/packages/db/src/services/project.service.ts @@ -1,30 +1,35 @@ +import type { Project } from '../prisma-client'; import { db } from '../prisma-client'; -export type IServiceProject = Awaited>; +export type IServiceProject = ReturnType; -export function getProjectById(id: string) { - return db.project.findUnique({ +function transform({ organization_slug, ...project }: Project) { + return { + organizationSlug: organization_slug, + ...project, + }; +} + +export async function getProjectById(id: string) { + const res = await db.project.findUnique({ where: { id, }, }); + + if (!res) { + return null; + } + + return transform(res); } -export function getProjectsByOrganizationSlug(slug: string) { - return db.project.findMany({ +export async function getProjectsByOrganizationSlug(slug: string) { + const res = await db.project.findMany({ where: { organization_slug: slug, }, }); -} -export async function getProjectWithMostEvents(slug: string) { - return db.project.findFirst({ - where: { - organization_slug: slug, - }, - orderBy: { - eventsCount: 'desc', - }, - }); + return res.map(transform); } diff --git a/packages/validation/src/index.ts b/packages/validation/src/index.ts index 176555f6..0dbaa74c 100644 --- a/packages/validation/src/index.ts +++ b/packages/validation/src/index.ts @@ -60,16 +60,16 @@ export const zMetric = z.enum(objectToZodEnums(metrics)); export const zRange = z.enum(objectToZodEnums(timeRanges)); export const zChartInput = z.object({ - name: z.string(), - chartType: zChartType, - lineType: zLineType, - interval: zTimeInterval, + name: z.string().default(''), + chartType: zChartType.default('linear'), + lineType: zLineType.default('monotone'), + interval: zTimeInterval.default('day'), events: zChartEvents, - breakdowns: zChartBreakdowns, - range: zRange, - previous: z.boolean(), + breakdowns: zChartBreakdowns.default([]), + range: zRange.default('1m'), + previous: z.boolean().default(false), formula: z.string().optional(), - metric: zMetric, + metric: zMetric.default('sum'), unit: z.string().optional(), previousIndicatorInverted: z.boolean().optional(), projectId: z.string(), diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 848e6ec3..ddbf4c51 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -413,6 +413,9 @@ importers: '@trpc/server': specifier: ^10.45.1 version: 10.45.1 + '@types/d3': + specifier: ^7.4.3 + version: 7.4.3 bcrypt: specifier: ^5.1.1 version: 5.1.1 @@ -425,6 +428,9 @@ importers: cmdk: specifier: ^0.2.1 version: 0.2.1(@types/react@18.2.56)(react-dom@18.2.0)(react@18.2.0) + d3: + specifier: ^7.8.5 + version: 7.8.5 date-fns: specifier: ^3.3.1 version: 3.3.1 @@ -6226,14 +6232,79 @@ packages: resolution: {integrity: sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg==} dev: false + /@types/d3-axis@3.0.6: + resolution: {integrity: sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==} + dependencies: + '@types/d3-selection': 3.0.10 + dev: false + + /@types/d3-brush@3.0.6: + resolution: {integrity: sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==} + dependencies: + '@types/d3-selection': 3.0.10 + dev: false + + /@types/d3-chord@3.0.6: + resolution: {integrity: sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg==} + dev: false + /@types/d3-color@3.1.3: resolution: {integrity: sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==} dev: false + /@types/d3-contour@3.0.6: + resolution: {integrity: sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==} + dependencies: + '@types/d3-array': 3.2.1 + '@types/geojson': 7946.0.14 + dev: false + + /@types/d3-delaunay@6.0.4: + resolution: {integrity: sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==} + dev: false + + /@types/d3-dispatch@3.0.6: + resolution: {integrity: sha512-4fvZhzMeeuBJYZXRXrRIQnvUYfyXwYmLsdiN7XXmVNQKKw1cM8a5WdID0g1hVFZDqT9ZqZEY5pD44p24VS7iZQ==} + dev: false + + /@types/d3-drag@3.0.7: + resolution: {integrity: sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==} + dependencies: + '@types/d3-selection': 3.0.10 + dev: false + + /@types/d3-dsv@3.0.7: + resolution: {integrity: sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==} + dev: false + /@types/d3-ease@3.0.2: resolution: {integrity: sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==} dev: false + /@types/d3-fetch@3.0.7: + resolution: {integrity: sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==} + dependencies: + '@types/d3-dsv': 3.0.7 + dev: false + + /@types/d3-force@3.0.9: + resolution: {integrity: sha512-IKtvyFdb4Q0LWna6ymywQsEYjK/94SGhPrMfEr1TIc5OBeziTi+1jcCvttts8e0UWZIxpasjnQk9MNk/3iS+kA==} + dev: false + + /@types/d3-format@3.0.4: + resolution: {integrity: sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==} + dev: false + + /@types/d3-geo@3.1.0: + resolution: {integrity: sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==} + dependencies: + '@types/geojson': 7946.0.14 + dev: false + + /@types/d3-hierarchy@3.1.6: + resolution: {integrity: sha512-qlmD/8aMk5xGorUvTUWHCiumvgaUXYldYjNVOWtYoTYY/L+WwIEAmJxUmTgr9LoGNG0PPAOmqMDJVDPc7DOpPw==} + dev: false + /@types/d3-interpolate@3.0.4: resolution: {integrity: sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==} dependencies: @@ -6244,18 +6315,42 @@ packages: resolution: {integrity: sha512-P2dlU/q51fkOc/Gfl3Ul9kicV7l+ra934qBFXCFhrZMOL6du1TM0pm1ThYvENukyOn5h9v+yMJ9Fn5JK4QozrQ==} dev: false + /@types/d3-polygon@3.0.2: + resolution: {integrity: sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA==} + dev: false + + /@types/d3-quadtree@3.0.6: + resolution: {integrity: sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==} + dev: false + + /@types/d3-random@3.0.3: + resolution: {integrity: sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ==} + dev: false + + /@types/d3-scale-chromatic@3.0.3: + resolution: {integrity: sha512-laXM4+1o5ImZv3RpFAsTRn3TEkzqkytiOY0Dz0sq5cnd1dtNlk6sHLon4OvqaiJb28T0S/TdsBI3Sjsy+keJrw==} + dev: false + /@types/d3-scale@4.0.8: resolution: {integrity: sha512-gkK1VVTr5iNiYJ7vWDI+yUFFlszhNMtVeneJ6lUTKPjprsvLLI9/tgEGiXJOnlINJA8FyA88gfnQsHbybVZrYQ==} dependencies: '@types/d3-time': 3.0.3 dev: false + /@types/d3-selection@3.0.10: + resolution: {integrity: sha512-cuHoUgS/V3hLdjJOLTT691+G2QoqAjCVLmr4kJXR4ha56w1Zdu8UUQ5TxLRqudgNjwXeQxKMq4j+lyf9sWuslg==} + dev: false + /@types/d3-shape@3.1.6: resolution: {integrity: sha512-5KKk5aKGu2I+O6SONMYSNflgiP0WfZIQvVUMan50wHsLG1G94JlxEVnCpQARfTtzytuY0p/9PXXZb3I7giofIA==} dependencies: '@types/d3-path': 3.1.0 dev: false + /@types/d3-time-format@4.0.3: + resolution: {integrity: sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==} + dev: false + /@types/d3-time@3.0.3: resolution: {integrity: sha512-2p6olUZ4w3s+07q3Tm2dbiMZy5pCDfYwtLXXHUnVzXgQlZ/OyPtUz6OL382BkOuGlLXqfT+wqv8Fw2v8/0geBw==} dev: false @@ -6264,6 +6359,54 @@ packages: resolution: {integrity: sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==} dev: false + /@types/d3-transition@3.0.8: + resolution: {integrity: sha512-ew63aJfQ/ms7QQ4X7pk5NxQ9fZH/z+i24ZfJ6tJSfqxJMrYLiK01EAs2/Rtw/JreGUsS3pLPNV644qXFGnoZNQ==} + dependencies: + '@types/d3-selection': 3.0.10 + dev: false + + /@types/d3-zoom@3.0.8: + resolution: {integrity: sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==} + dependencies: + '@types/d3-interpolate': 3.0.4 + '@types/d3-selection': 3.0.10 + dev: false + + /@types/d3@7.4.3: + resolution: {integrity: sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==} + dependencies: + '@types/d3-array': 3.2.1 + '@types/d3-axis': 3.0.6 + '@types/d3-brush': 3.0.6 + '@types/d3-chord': 3.0.6 + '@types/d3-color': 3.1.3 + '@types/d3-contour': 3.0.6 + '@types/d3-delaunay': 6.0.4 + '@types/d3-dispatch': 3.0.6 + '@types/d3-drag': 3.0.7 + '@types/d3-dsv': 3.0.7 + '@types/d3-ease': 3.0.2 + '@types/d3-fetch': 3.0.7 + '@types/d3-force': 3.0.9 + '@types/d3-format': 3.0.4 + '@types/d3-geo': 3.1.0 + '@types/d3-hierarchy': 3.1.6 + '@types/d3-interpolate': 3.0.4 + '@types/d3-path': 3.1.0 + '@types/d3-polygon': 3.0.2 + '@types/d3-quadtree': 3.0.6 + '@types/d3-random': 3.0.3 + '@types/d3-scale': 4.0.8 + '@types/d3-scale-chromatic': 3.0.3 + '@types/d3-selection': 3.0.10 + '@types/d3-shape': 3.1.6 + '@types/d3-time': 3.0.3 + '@types/d3-time-format': 4.0.3 + '@types/d3-timer': 3.0.2 + '@types/d3-transition': 3.0.8 + '@types/d3-zoom': 3.0.8 + dev: false + /@types/eslint@8.56.2: resolution: {integrity: sha512-uQDwm1wFHmbBbCZCqAlq6Do9LYwByNZHWzXppSnay9SuwJ+VRbjkbLABer54kcPnMSlG6Fdiy2yaFXm/z9Z5gw==} dependencies: @@ -6298,6 +6441,10 @@ packages: '@types/qs': 6.9.11 '@types/serve-static': 1.15.5 + /@types/geojson@7946.0.14: + resolution: {integrity: sha512-WCfD5Ht3ZesJUsONdhvm84dmzWOiOzOAqOncN0++w0lBw1o8OuDNJF2McvvCef/yBqb/HYRahp1BYtODFQ8bRg==} + dev: false + /@types/hast@2.3.10: resolution: {integrity: sha512-McWspRw8xx8J9HurkVBfYj0xKoE25tOFlHGdx4MJ5xORQrMGZNqJhVQWaIbm6Oyla5kYOXtDiopzKRJzEOkwJw==} dependencies: @@ -7892,16 +8039,92 @@ packages: internmap: 2.0.3 dev: false + /d3-axis@3.0.0: + resolution: {integrity: sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==} + engines: {node: '>=12'} + dev: false + + /d3-brush@3.0.0: + resolution: {integrity: sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==} + engines: {node: '>=12'} + dependencies: + d3-dispatch: 3.0.1 + d3-drag: 3.0.0 + d3-interpolate: 3.0.1 + d3-selection: 3.0.0 + d3-transition: 3.0.1(d3-selection@3.0.0) + dev: false + + /d3-chord@3.0.1: + resolution: {integrity: sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==} + engines: {node: '>=12'} + dependencies: + d3-path: 3.1.0 + dev: false + /d3-color@3.1.0: resolution: {integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==} engines: {node: '>=12'} dev: false + /d3-contour@4.0.2: + resolution: {integrity: sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==} + engines: {node: '>=12'} + dependencies: + d3-array: 3.2.4 + dev: false + + /d3-delaunay@6.0.4: + resolution: {integrity: sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==} + engines: {node: '>=12'} + dependencies: + delaunator: 5.0.1 + dev: false + + /d3-dispatch@3.0.1: + resolution: {integrity: sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==} + engines: {node: '>=12'} + dev: false + + /d3-drag@3.0.0: + resolution: {integrity: sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==} + engines: {node: '>=12'} + dependencies: + d3-dispatch: 3.0.1 + d3-selection: 3.0.0 + dev: false + + /d3-dsv@3.0.1: + resolution: {integrity: sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==} + engines: {node: '>=12'} + hasBin: true + dependencies: + commander: 7.2.0 + iconv-lite: 0.6.3 + rw: 1.3.3 + dev: false + /d3-ease@3.0.1: resolution: {integrity: sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==} engines: {node: '>=12'} dev: false + /d3-fetch@3.0.1: + resolution: {integrity: sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==} + engines: {node: '>=12'} + dependencies: + d3-dsv: 3.0.1 + dev: false + + /d3-force@3.0.0: + resolution: {integrity: sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==} + engines: {node: '>=12'} + dependencies: + d3-dispatch: 3.0.1 + d3-quadtree: 3.0.1 + d3-timer: 3.0.1 + dev: false + /d3-format@3.1.0: resolution: {integrity: sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==} engines: {node: '>=12'} @@ -7913,6 +8136,18 @@ packages: d3-array: 2.12.1 dev: false + /d3-geo@3.1.0: + resolution: {integrity: sha512-JEo5HxXDdDYXCaWdwLRt79y7giK8SbhZJbFWXqbRTolCHFI5jRqteLzCsq51NKbUoX0PjBVSohxrx+NoOUujYA==} + engines: {node: '>=12'} + dependencies: + d3-array: 3.2.4 + dev: false + + /d3-hierarchy@3.1.2: + resolution: {integrity: sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==} + engines: {node: '>=12'} + dev: false + /d3-interpolate@3.0.1: resolution: {integrity: sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==} engines: {node: '>=12'} @@ -7925,6 +8160,29 @@ packages: engines: {node: '>=12'} dev: false + /d3-polygon@3.0.1: + resolution: {integrity: sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==} + engines: {node: '>=12'} + dev: false + + /d3-quadtree@3.0.1: + resolution: {integrity: sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==} + engines: {node: '>=12'} + dev: false + + /d3-random@3.0.1: + resolution: {integrity: sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==} + engines: {node: '>=12'} + dev: false + + /d3-scale-chromatic@3.0.0: + resolution: {integrity: sha512-Lx9thtxAKrO2Pq6OO2Ua474opeziKr279P/TKZsMAhYyNDD3EnCffdbgeSYN5O7m2ByQsxtuP2CSDczNUIZ22g==} + engines: {node: '>=12'} + dependencies: + d3-color: 3.1.0 + d3-interpolate: 3.0.1 + dev: false + /d3-scale@4.0.2: resolution: {integrity: sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==} engines: {node: '>=12'} @@ -7936,6 +8194,11 @@ packages: d3-time-format: 4.1.0 dev: false + /d3-selection@3.0.0: + resolution: {integrity: sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==} + engines: {node: '>=12'} + dev: false + /d3-shape@3.2.0: resolution: {integrity: sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==} engines: {node: '>=12'} @@ -7962,6 +8225,67 @@ packages: engines: {node: '>=12'} dev: false + /d3-transition@3.0.1(d3-selection@3.0.0): + resolution: {integrity: sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==} + engines: {node: '>=12'} + peerDependencies: + d3-selection: 2 - 3 + dependencies: + d3-color: 3.1.0 + d3-dispatch: 3.0.1 + d3-ease: 3.0.1 + d3-interpolate: 3.0.1 + d3-selection: 3.0.0 + d3-timer: 3.0.1 + dev: false + + /d3-zoom@3.0.0: + resolution: {integrity: sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==} + engines: {node: '>=12'} + dependencies: + d3-dispatch: 3.0.1 + d3-drag: 3.0.0 + d3-interpolate: 3.0.1 + d3-selection: 3.0.0 + d3-transition: 3.0.1(d3-selection@3.0.0) + dev: false + + /d3@7.8.5: + resolution: {integrity: sha512-JgoahDG51ncUfJu6wX/1vWQEqOflgXyl4MaHqlcSruTez7yhaRKR9i8VjjcQGeS2en/jnFivXuaIMnseMMt0XA==} + engines: {node: '>=12'} + dependencies: + d3-array: 3.2.4 + d3-axis: 3.0.0 + d3-brush: 3.0.0 + d3-chord: 3.0.1 + d3-color: 3.1.0 + d3-contour: 4.0.2 + d3-delaunay: 6.0.4 + d3-dispatch: 3.0.1 + d3-drag: 3.0.0 + d3-dsv: 3.0.1 + d3-ease: 3.0.1 + d3-fetch: 3.0.1 + d3-force: 3.0.0 + d3-format: 3.1.0 + d3-geo: 3.1.0 + d3-hierarchy: 3.1.2 + d3-interpolate: 3.0.1 + d3-path: 3.1.0 + d3-polygon: 3.0.1 + d3-quadtree: 3.0.1 + d3-random: 3.0.1 + d3-scale: 4.0.2 + d3-scale-chromatic: 3.0.0 + d3-selection: 3.0.0 + d3-shape: 3.2.0 + d3-time: 3.1.0 + d3-time-format: 4.1.0 + d3-timer: 3.0.1 + d3-transition: 3.0.1(d3-selection@3.0.0) + d3-zoom: 3.0.0 + dev: false + /dag-map@1.0.2: resolution: {integrity: sha512-+LSAiGFwQ9dRnRdOeaj7g47ZFJcOUPukAP8J3A3fuZ1g9Y44BG+P1sgApjLXTQPOzC4+7S9Wr8kXsfpINM4jpw==} dev: false @@ -8113,6 +8437,12 @@ packages: slash: 3.0.0 dev: false + /delaunator@5.0.1: + resolution: {integrity: sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==} + dependencies: + robust-predicates: 3.0.2 + dev: false + /delayed-stream@1.0.0: resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} engines: {node: '>=0.4.0'} @@ -9776,6 +10106,13 @@ packages: safer-buffer: 2.1.2 dev: false + /iconv-lite@0.6.3: + resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} + engines: {node: '>=0.10.0'} + dependencies: + safer-buffer: 2.1.2 + dev: false + /ieee754@1.2.1: resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} dev: false @@ -13275,6 +13612,10 @@ packages: dependencies: glob: 7.2.3 + /robust-predicates@3.0.2: + resolution: {integrity: sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==} + dev: false + /rollup@4.12.0: resolution: {integrity: sha512-wz66wn4t1OHIJw3+XU7mJJQV/2NAfw5OAk6G6Hoo3zcvz/XOfQ52Vgi+AN4Uxoxi0KBBwk2g8zPrTDA4btSB/Q==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} @@ -13303,6 +13644,10 @@ packages: dependencies: queue-microtask: 1.2.3 + /rw@1.3.3: + resolution: {integrity: sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==} + dev: false + /safe-array-concat@1.1.0: resolution: {integrity: sha512-ZdQ0Jeb9Ofti4hbt5lX3T2JcAamT9hfzYU1MNB+z/jaEbB6wfFfPIR/zEORmZqobkCCJhSjodobH6WHNmJ97dg==} engines: {node: '>=0.4'}