Skip to content
This repository has been archived by the owner on Oct 31, 2024. It is now read-only.

Commit

Permalink
Add category filter to add connection tab (#70)
Browse files Browse the repository at this point in the history
* Add message when filtered data is empty array

* Integrate category filter to add connection tab

* Fix lint

* Show filter when we have more than one category

---------

Co-authored-by: Rodrigo Arze Leon <[email protected]>
  • Loading branch information
Rodri77 and Rodrigo Arze Leon authored Oct 28, 2024
1 parent 769fcae commit f656890
Show file tree
Hide file tree
Showing 3 changed files with 107 additions and 57 deletions.
119 changes: 68 additions & 51 deletions packages/engine-frontend/components/IntegrationSearch.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
'use client'

import {Loader, Search} from 'lucide-react'
import React from 'react'
import {Input} from '@openint/ui'
import {useState} from 'react'
import {Input, parseCategory} from '@openint/ui'
import {CheckboxFilter} from '@openint/ui/components/CheckboxFilter'
import {ConnectionCard} from '@openint/ui/domain-components/ConnectionCard'
import type {ConnectorConfig} from '../hocs/WithConnectConfig'
import type {ConnectEventType} from '../hocs/WithConnectorConnect'
Expand All @@ -25,7 +26,8 @@ export function IntegrationSearch({
type: ConnectEventType
}) => void
}) {
const [searchText, setSearchText] = React.useState('')
const [searchText, setSearchText] = useState('')
const [categoryFilter, setCategoryFilter] = useState<string[]>([])

const listIntegrationsRes = _trpcReact.listConfiguredIntegrations.useQuery({
connector_config_ids: connectorConfigs.map((ccfg) => ccfg.id),
Expand All @@ -36,83 +38,98 @@ export function IntegrationSearch({
ccfg: connectorConfigs.find((ccfg) => ccfg.id === int.connector_config_id)!,
}))

const categories = Array.from(
new Set(connectorConfigs.flatMap((ccfg) => ccfg.verticals)),
)

const intsByCategory = ints?.reduce(
(acc, int) => {
int.ccfg.verticals.forEach((vertical) => {
acc[vertical] = (acc[vertical] || []).concat(int)
if (categoryFilter.length === 0 || categoryFilter.includes(vertical)) {
acc[vertical] = (acc[vertical] || []).concat(int)
}
})
return acc
},
{} as Record<string, typeof ints>,
)

const onApplyFilter = (selected: string[]) => {
setCategoryFilter(selected)
}

return (
<div className={className}>
{/* Search integrations */}
<div className="mb-2 bg-background/95 pt-4 backdrop-blur supports-[backdrop-filter]:bg-background/60">
<form>
<div className="relative">
<div className="flex flex-row gap-2">
<div className="relative w-[450px]">
{/* top-2.5 is not working for some reason due to tailwind setup */}
<Search className="absolute left-2 top-2 h-4 w-4 text-muted-foreground" />
<Search className="absolute left-2 top-2.5 h-4 w-4 text-muted-foreground" />
<Input
placeholder="Search"
className="pl-8"
value={searchText}
onChange={(e) => setSearchText(e.target.value)}
/>
</div>
</form>
{categories.length > 1 && (
<CheckboxFilter options={categories} onApply={onApplyFilter} />
)}
</div>
</div>
{/* Search results */}
{listIntegrationsRes.isLoading ? (
<div className="flex h-full items-center justify-center">
<div className="flex h-full min-h-[500px] items-center justify-center">
<Loader className="size-5 animate-spin text-[#8A5DF6]" />
</div>
) : (
<div className="space-y-6 overflow-y-auto py-4">
{Object.entries(intsByCategory ?? {}).map(
([category, categoryInts]) => (
<div key={category}>
<h3 className="mb-2 text-lg font-semibold">
{category.length < 5
? category.toUpperCase()
: category
.split('-')
.map(
(word) =>
word.charAt(0).toUpperCase() + word.slice(1),
)
.join(' ')}
</h3>
<div className="flex flex-row gap-4">
{categoryInts.map((int) => (
<WithConnectorConnect
key={int.id}
connectorConfig={{
id: int.connector_config_id,
connector: int.ccfg.connector,
}}
onEvent={(e) => {
onEvent?.({
type: e.type,
integration: {
connectorConfigId: int.connector_config_id,
id: int.id,
},
})
}}>
{({openConnect}) => (
<ConnectionCard
onClick={openConnect}
logo={int.ccfg.connector.logoUrl ?? ''}
name={int.name}
/>
)}
</WithConnectorConnect>
))}
{(ints && ints.length > 0) ||
Object.keys(intsByCategory ?? {}).length > 0 ? (
Object.entries(intsByCategory ?? {}).map(
([category, categoryInts]) => (
<div key={category}>
<h3 className="mb-2 text-lg font-semibold">
{parseCategory(category)}
</h3>
<div className="flex flex-row gap-4">
{categoryInts.map((int) => (
<WithConnectorConnect
key={int.id}
connectorConfig={{
id: int.connector_config_id,
connector: int.ccfg.connector,
}}
onEvent={(e) => {
onEvent?.({
type: e.type,
integration: {
connectorConfigId: int.connector_config_id,
id: int.id,
},
})
}}>
{({openConnect}) => (
<ConnectionCard
onClick={openConnect}
logo={int.ccfg.connector.logoUrl ?? ''}
name={int.name}
/>
)}
</WithConnectorConnect>
))}
</div>
</div>
</div>
),
),
)
) : (
<div>
<p className="text-lg font-semibold">
No available connectors, please check that you have configured
connectors available or review your filter values.
</p>
</div>
)}
</div>
)}
Expand Down
36 changes: 30 additions & 6 deletions packages/ui/components/CheckboxFilter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@ import {useState} from 'react'
import {Button} from '../shadcn/Button'
import {Checkbox} from '../shadcn/Checkbox'
import {Popover, PopoverContent, PopoverTrigger} from '../shadcn/Popover'
import {parseCategory} from '../utils'

export function CheckboxFilter({
options,
onApply,
}: {
options: string[]
onApply: () => void
onApply: (selected: string[]) => void
}) {
const [checkedState, setCheckedState] = useState<Record<string, boolean>>(
options.reduce(
Expand Down Expand Up @@ -37,7 +38,7 @@ export function CheckboxFilter({
<span className="ml-2">Category</span>
</Button>
</PopoverTrigger>
<PopoverContent className="w-40 bg-white">
<PopoverContent className="w-48 bg-white">
<div className="flex flex-col gap-2">
{options.map((option) => (
<div
Expand All @@ -58,7 +59,7 @@ export function CheckboxFilter({
<span
className={`block h-4 w-4 rounded-sm ${
checkedState[option]
? 'bg-[#8A7DFF] text-white'
? 'bg-button text-button-foreground'
: 'bg-transparent'
}`}>
{checkedState[option] && (
Expand All @@ -80,14 +81,37 @@ export function CheckboxFilter({
htmlFor={option}
className="cursor-pointer text-sm font-semibold">
{' '}
{option}
{parseCategory(option)}
</label>
</div>
))}
{/* Added a visible divider here */}
<div className="my-2 w-full border-t border-[#E6E6E6]" />
<div className="col-span-3 flex justify-end">
<Button onClick={onApply} size="sm">
<div className="col-span-3 flex justify-end gap-2">
<Button
onClick={() => {
setCheckedState(
options.reduce(
(acc, option) => {
acc[option] = false
return acc
},
{} as Record<string, boolean>,
),
)
onApply([])
}}
size="sm"
variant="secondary">
Clear
</Button>
<Button
onClick={() =>
onApply(
Object.keys(checkedState).filter((key) => checkedState[key]),
)
}
size="sm">
Apply
</Button>
</div>
Expand Down
9 changes: 9 additions & 0 deletions packages/ui/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,12 @@ export function getValidChildren(children: React.ReactNode) {
React.isValidElement(child),
) as React.ReactElement[]
}

export function parseCategory(category: string) {
return category.length < 5
? category.toUpperCase()
: category
.split('-')
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
.join(' ')
}

0 comments on commit f656890

Please sign in to comment.