11import styles from "./filter.module.css" ;
22import { SearchIcon , XIcon } from "lucide-react" ;
3- import {
4- capitalizeAll ,
5- dimensionNames ,
6- filterNames ,
7- filterNamesInverted ,
8- filterTypes ,
9- type DimensionFilter ,
10- type FilterType ,
11- } from "../../api" ;
3+
4+ import { dimensionNames , filterNames , filterNamesInverted , type DimensionFilter , type FilterType } from "../../api" ;
5+
126import { Dialog } from "../dialog" ;
13- import { cls } from "../../utils" ;
7+ import { capitalizeAll , cls } from "../../utils" ;
148import { useRef , useState } from "react" ;
159
1610export const SelectFilters = ( {
@@ -27,9 +21,14 @@ export const SelectFilters = ({
2721 < div className = { styles . filterField } >
2822 < span > { dimensionNames [ filter . dimension ] } </ span >
2923 < span className = { styles . filterType } >
30- { filter . inversed ? filterNamesInverted [ filter . filterType ] : filterNames [ filter . filterType ] }
24+ { filters ?. [ filter . dimension ] . displayType ?.( filter ) ??
25+ ( filter . inversed ? filterNamesInverted [ filter . filterType ] : filterNames [ filter . filterType ] ) }
3126 </ span >
32- { filter . filterType === "is_null" ? null : < span className = { styles . filterValue } > { filter . value } </ span > }
27+ { filter . filterType === "is_null" ? null : (
28+ < span className = { styles . filterValue } >
29+ { filters ?. [ filter . dimension ] . displayValue ?.( filter ) ?? filter . value }
30+ </ span >
31+ ) }
3332 </ div >
3433 < button type = "button" onClick = { ( ) => onChange ( value . filter ( ( _ , j ) => i !== j ) ) } className = { styles . remove } >
3534 < XIcon size = { 20 } />
@@ -43,35 +42,109 @@ export const SelectFilters = ({
4342 ) ;
4443} ;
4544
45+ const filters = {
46+ platform : {
47+ invertable : true ,
48+ filterTypes : [ "equal" , "contains" ] ,
49+ } ,
50+ browser : {
51+ invertable : true ,
52+ filterTypes : [ "equal" , "contains" , "starts_with" , "ends_with" ] ,
53+ } ,
54+ url : {
55+ invertable : true ,
56+ filterTypes : [ "equal" , "contains" , "starts_with" , "ends_with" ] ,
57+ } ,
58+ fqdn : {
59+ invertable : true ,
60+ filterTypes : [ "equal" , "contains" , "starts_with" , "ends_with" ] ,
61+ } ,
62+ path : {
63+ invertable : true ,
64+ filterTypes : [ "equal" , "contains" , "starts_with" , "ends_with" ] ,
65+ } ,
66+ referrer : {
67+ invertable : true ,
68+ filterTypes : [ "equal" , "contains" ] ,
69+ } ,
70+ city : {
71+ invertable : true ,
72+ filterTypes : [ "equal" , "contains" ] ,
73+ } ,
74+ country : {
75+ invertable : true ,
76+ filterTypes : [ "equal" , "contains" ] ,
77+ } ,
78+ mobile : {
79+ custom : true ,
80+ displayValue : ( filter : DimensionFilter ) => ( filter . filterType === "is_true" ? "Mobile" : "Desktop" ) ,
81+ displayType : ( filter : DimensionFilter ) => ( filter . inversed ? "is not" : "is" ) ,
82+ render : ( ) => (
83+ < label >
84+ Device Type
85+ < select name = "mobile" >
86+ < option value = "true" > Mobile</ option >
87+ < option value = "false" > Desktop</ option >
88+ </ select >
89+ </ label >
90+ ) ,
91+ getFilter : ( data : FormData ) => {
92+ return {
93+ dimension : "mobile" ,
94+ filterType : data . get ( "mobile" ) === "true" ? "is_true" : "is_false" ,
95+ value : undefined ,
96+ } ;
97+ } ,
98+ } ,
99+ } as Record <
100+ keyof typeof dimensionNames ,
101+ {
102+ filterTypes : FilterType [ ] ;
103+ invertable ?: boolean ;
104+ custom ?: boolean ;
105+ render ?: ( ) => JSX . Element ;
106+ getFilter ?: ( data : FormData ) => DimensionFilter ;
107+ displayValue ?: ( filter : DimensionFilter ) => string ;
108+ displayType ?: ( filter : DimensionFilter ) => string ;
109+ }
110+ > ;
111+
112+ type filterDimension = keyof typeof filters ;
113+
46114const FilterDialog = ( {
47115 onAdd,
48116} : {
49117 onAdd : ( filter : DimensionFilter ) => void ;
50118} ) => {
51119 const closeRef = useRef < HTMLButtonElement > ( null ) ;
52- const [ dimension , setDimension ] = useState < keyof typeof dimensionNames > ( "url" ) ;
53- const [ filterType , setFilterType ] = useState < FilterType | `${FilterType } -inverted`> ( "equal" ) ;
54- const [ value , setValue ] = useState ( "" ) ;
120+ const [ dimension , setDimension ] = useState < filterDimension > ( "url" ) ;
121+ const filter = filters [ dimension ] ;
55122
56123 const handleSubmit = ( e : React . FormEvent ) => {
57124 e . preventDefault ( ) ;
125+ const data = new FormData ( e . currentTarget as HTMLFormElement ) ;
126+
127+ if ( filter . getFilter ) {
128+ onAdd ( filter . getFilter ( data ) ) ;
129+ closeRef . current ?. click ( ) ;
130+ return ;
131+ }
132+
58133 onAdd ( {
59134 dimension,
60- inversed : filterType . endsWith ( "- inverted") ,
61- filterType : filterType . replace ( "-inverted" , " ") as FilterType ,
62- value : value . length ? value : undefined ,
135+ inversed : filter . invertable && data . get ( " inverted") === "on" ,
136+ filterType : data . get ( "filterType ") as FilterType ,
137+ value : data . get ( " value" ) as string ,
63138 } ) ;
64139 setDimension ( "url" ) ;
65- setFilterType ( "equal" ) ;
66- setValue ( "" ) ;
67140 closeRef . current ?. click ( ) ;
68141 } ;
69142
70- const dimensions = Object . entries ( dimensionNames ) as [ keyof typeof dimensionNames , string ] [ ] ;
71-
72143 return (
73144 < Dialog
74145 title = "Add Filter"
146+ description = "Filter the data by a specific dimension"
147+ hideDescription
75148 trigger = {
76149 < button type = "button" >
77150 < h2 > Add Filter</ h2 >
@@ -82,41 +155,53 @@ const FilterDialog = ({
82155 < form onSubmit = { handleSubmit } >
83156 < label >
84157 Dimension
85- < select
86- name = "dimension"
87- value = { dimension }
88- onChange = { ( e ) => setDimension ( e . target . value as keyof typeof dimensionNames ) }
89- >
90- { dimensions . map ( ( [ dimension , name ] ) => (
158+ < select name = "dimension" value = { dimension } onChange = { ( e ) => setDimension ( e . target . value as filterDimension ) } >
159+ { Object . keys ( filters ) . map ( ( dimension ) => (
91160 < option key = { dimension } value = { dimension } >
92- { dimensionNames [ dimension ] }
161+ { dimensionNames [ dimension as filterDimension ] }
93162 </ option >
94163 ) ) }
95164 </ select >
96165 </ label >
97- < label >
98- Filter Type
99- < select
100- name = "filterType"
101- value = { filterType }
102- onChange = { ( e ) => setFilterType ( e . target . value as FilterType | `${FilterType } -inverted`) }
103- >
104- { filterTypes . map ( ( filterType ) => (
105- < >
106- < option key = { filterType } value = { filterType } >
107- { capitalizeAll ( filterNames [ filterType ] ) }
108- </ option >
109- < option key = { `${ filterType } -inverted` } value = { `${ filterType } -inverted` } >
110- { capitalizeAll ( filterNamesInverted [ filterType ] ) }
111- </ option >
112- </ >
113- ) ) }
114- </ select >
115- </ label >
116- < label >
117- Value
118- < input type = "text" name = "value" value = { value } onChange = { ( e ) => setValue ( e . target . value ) } />
119- </ label >
166+
167+ { filter . custom && filter . render ?.( ) }
168+
169+ { ! filter . custom && (
170+ < div className = { styles . formInvertable } >
171+ < label >
172+ Filter Type
173+ < select name = "filterType" >
174+ { filter . filterTypes . map ( ( filterType ) => (
175+ < option key = { filterType } value = { filterType } >
176+ { capitalizeAll ( filterNames [ filterType ] ) }
177+ </ option >
178+ ) ) }
179+ </ select >
180+ </ label >
181+ { filter . invertable && (
182+ < div className = { styles . inverted } >
183+ < fieldset name = "test" >
184+ < label >
185+ < input defaultChecked type = "radio" aria-invalid = "false" />
186+ Show Matches
187+ </ label >
188+ < label >
189+ < input type = "radio" name = "inverted" aria-invalid = "true" />
190+ Exclude Matches
191+ </ label >
192+ </ fieldset >
193+ </ div >
194+ ) }
195+ </ div >
196+ ) }
197+
198+ { ! filter . custom && (
199+ < label >
200+ Value
201+ < input type = "text" name = "value" />
202+ </ label >
203+ ) }
204+
120205 < div className = "grid" >
121206 < Dialog . Close asChild ref = { closeRef } >
122207 < button className = "secondary outline" type = "button" >
0 commit comments