diff --git a/apps/web/src/components/report/chart/ReportLineChart.tsx b/apps/web/src/components/report/chart/ReportLineChart.tsx index 6c7cf931..749d200d 100644 --- a/apps/web/src/components/report/chart/ReportLineChart.tsx +++ b/apps/web/src/components/report/chart/ReportLineChart.tsx @@ -25,7 +25,6 @@ export function ReportLineChart({ interval, data }: ReportLineChartProps) { const { editMode } = useChartContext(); const [visibleSeries, setVisibleSeries] = useState([]); const formatDate = useFormatDateInterval(interval); - console.log(JSON.stringify(data.series[0]?.data, null, 2)); const ref = useRef(false); useEffect(() => { diff --git a/apps/web/src/components/report/sidebar/ReportEventFilters.tsx b/apps/web/src/components/report/sidebar/ReportEventFilters.tsx index 71ef0a43..15c1bf73 100644 --- a/apps/web/src/components/report/sidebar/ReportEventFilters.tsx +++ b/apps/web/src/components/report/sidebar/ReportEventFilters.tsx @@ -2,6 +2,7 @@ import type { Dispatch } from 'react'; import { ColorSquare } from '@/components/ColorSquare'; import { Dropdown } from '@/components/Dropdown'; import { Button } from '@/components/ui/button'; +import { ComboboxAdvanced } from '@/components/ui/combobox-advanced'; import { ComboboxMulti } from '@/components/ui/combobox-multi'; import { CommandDialog, @@ -187,60 +188,23 @@ function Filter({ filter, event }: FilterProps) { value: key as IChartEventFilter['operator'], label: value, }))} - label="Segment" + label="Operator" > - ({ - value: item?.toString() ?? '__filter_value_null__', - label: getLabel(item?.toString() ?? '__filter_value_null__'), - }))} + selected={filter.value} setSelected={(setFn) => { - if (typeof setFn === 'function') { - const newValues = setFn( - filter.value.map((item) => ({ - value: item?.toString() ?? '__filter_value_null__', - label: getLabel(item?.toString() ?? '__filter_value_null__'), - })) - ); - changeFilterValue(newValues.map((item) => item.value)); - } else { - changeFilterValue(setFn.map((item) => item.value)); - } + changeFilterValue( + typeof setFn === 'function' ? setFn(filter.value) : setFn + ); }} + placeholder="Select..." /> - {/* */} - {/* { - dispatch( - changeEvent({ - ...event, - filters: event.filters.map((item) => { - if (item.id === filter.id) { - return { - ...item, - value: e.currentTarget.value, - }; - } - - return item; - }), - }), - ); - }} - /> */} ); } diff --git a/apps/web/src/components/report/sidebar/ReportSidebar.tsx b/apps/web/src/components/report/sidebar/ReportSidebar.tsx index 49279e65..00b57b3e 100644 --- a/apps/web/src/components/report/sidebar/ReportSidebar.tsx +++ b/apps/web/src/components/report/sidebar/ReportSidebar.tsx @@ -6,12 +6,14 @@ import { ReportEvents } from './ReportEvents'; export function ReportSidebar() { return ( -
+
- - - +
+ + + +
); } diff --git a/apps/web/src/components/ui/combobox-advanced.tsx b/apps/web/src/components/ui/combobox-advanced.tsx new file mode 100644 index 00000000..91d00eac --- /dev/null +++ b/apps/web/src/components/ui/combobox-advanced.tsx @@ -0,0 +1,114 @@ +import * as React from 'react'; +import { Badge } from '@/components/ui/badge'; +import { Command, CommandGroup, CommandItem } from '@/components/ui/command'; + +import { Checkbox } from './checkbox'; +import { Input } from './input'; + +type IValue = string | number | boolean | null; +type IItem = Record<'value' | 'label', IValue>; + +interface ComboboxAdvancedProps { + selected: IValue[]; + setSelected: React.Dispatch>; + items: IItem[]; + placeholder: string; +} + +export function ComboboxAdvanced({ + items, + selected, + setSelected, + placeholder, +}: ComboboxAdvancedProps) { + const [open, setOpen] = React.useState(false); + const [inputValue, setInputValue] = React.useState(''); + + const selectables = items + .filter((item) => !selected.find((s) => s === item.value)) + .filter( + (item) => + (typeof item.label === 'string' && + item.label.toLowerCase().includes(inputValue.toLowerCase())) || + (typeof item.value === 'string' && + item.value.toLowerCase().includes(inputValue.toLowerCase())) + ); + + const renderItem = (item: IItem) => { + const checked = !!selected.find((s) => s === item.value); + return ( + { + e.preventDefault(); + e.stopPropagation(); + }} + onSelect={() => { + setInputValue(''); + setSelected((prev) => { + if (prev.includes(item.value)) { + return prev.filter((s) => s !== item.value); + } + return [...prev, item.value]; + }); + }} + className={'cursor-pointer flex items-center gap-2'} + > + + {item?.label ?? item?.value} + + ); + }; + + const renderUnknownItem = (value: string | number | null | boolean) => { + const item = items.find((item) => item.value === value); + return item ? renderItem(item) : renderItem({ value, label: value }); + }; + + return ( + + +
+ {open && ( +
+ +
+ setInputValue(event.target.value)} + /> +
+ {inputValue === '' + ? selected.map(renderUnknownItem) + : renderUnknownItem(inputValue)} + {selectables.map(renderItem)} +
+
+ )} +
+
+ ); +} diff --git a/apps/web/src/components/ui/sheet.tsx b/apps/web/src/components/ui/sheet.tsx index cad567d8..b2c7032e 100644 --- a/apps/web/src/components/ui/sheet.tsx +++ b/apps/web/src/components/ui/sheet.tsx @@ -1,6 +1,7 @@ import * as React from 'react'; import { cn } from '@/utils/cn'; import * as SheetPrimitive from '@radix-ui/react-dialog'; +import { ScrollArea } from '@radix-ui/react-scroll-area'; import { cva } from 'class-variance-authority'; import type { VariantProps } from 'class-variance-authority'; import { X } from 'lucide-react'; @@ -29,7 +30,7 @@ const SheetOverlay = React.forwardRef< SheetOverlay.displayName = SheetPrimitive.Overlay.displayName; const sheetVariants = cva( - 'fixed z-50 gap-4 bg-background p-6 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', + 'fixed z-50 gap-4 bg-background 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: { @@ -62,7 +63,9 @@ const SheetContent = React.forwardRef< className={cn(sheetVariants({ side }), className)} {...props} > - {children} +
+ {children} +
Close diff --git a/apps/web/src/server/api/routers/chart.ts b/apps/web/src/server/api/routers/chart.ts index 9c302dbb..24689adb 100644 --- a/apps/web/src/server/api/routers/chart.ts +++ b/apps/web/src/server/api/routers/chart.ts @@ -299,8 +299,31 @@ function getChartSql({ where.push(`name = '${name}'`); if (filters.length > 0) { filters.forEach((filter) => { - const { name, value } = filter; - switch (filter.operator) { + const { name, value, operator } = filter; + switch (operator) { + case 'contains': { + if (name.includes('.*.') || name.endsWith('[*]')) { + // TODO: Make sure this works + // where.push( + // `properties @? '$.${name + // .replace(/^properties\./, '') + // .replace(/\.\*\./g, '[*].')} ? (@ like_regex "${value[0]}")'` + // ); + } else { + where.push( + `(${value + .map( + (val) => + `${propertyNameToSql(name)} like '%${String(val).replace( + /'/g, + "''" + )}%'` + ) + .join(' OR ')})` + ); + } + break; + } case 'is': { if (name.includes('.*.') || name.endsWith('[*]')) { where.push(