-
-
Notifications
You must be signed in to change notification settings - Fork 0
feat: Add advanced filters panel to incident list #126
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
34ee3ef
b8660fc
98b5fa1
6348eab
b21eaf3
a585969
56c350c
6122efb
4606bce
9f21fd4
0c5b0de
48617da
1da7d03
c80bc6c
23f8f86
d6da9db
19df7d2
374d403
919a823
a4b6bc0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| import {Card} from 'components/Card'; | ||
|
|
||
| import {ServiceTierSchema, SeveritySchema} from '../types'; | ||
|
|
||
| import {DateRangeFilter} from './filters/DateRangeFilter'; | ||
| import {PillFilter} from './filters/PillFilter'; | ||
| import {TagFilter} from './filters/TagFilter'; | ||
| import {UserFilter} from './filters/UserFilter'; | ||
|
|
||
| export {FilterTrigger} from './filters/FilterTrigger'; | ||
|
|
||
| export function FilterPanel() { | ||
| return ( | ||
| <Card className="flex flex-col gap-space-md" data-testid="advanced-filters"> | ||
| <div className="grid grid-cols-1 sm:grid-cols-2 xl:grid-cols-3 gap-space-md"> | ||
| <PillFilter | ||
| label="Severity" | ||
| filterKey="severity" | ||
| options={SeveritySchema.options} | ||
| /> | ||
| <PillFilter | ||
| label="Service Tier" | ||
| filterKey="service_tier" | ||
| options={ServiceTierSchema.options} | ||
| /> | ||
| <TagFilter label="Impact Type" filterKey="impact_type" tagType="IMPACT_TYPE" /> | ||
| <TagFilter | ||
| label="Affected Service" | ||
| filterKey="affected_service" | ||
| tagType="AFFECTED_SERVICE" | ||
| /> | ||
| <TagFilter | ||
| label="Affected Region" | ||
| filterKey="affected_region" | ||
| tagType="AFFECTED_REGION" | ||
| /> | ||
| <TagFilter label="Root Cause" filterKey="root_cause" tagType="ROOT_CAUSE" /> | ||
| <UserFilter label="Captain" filterKey="captain" /> | ||
| <UserFilter label="Reporter" filterKey="reporter" /> | ||
| <DateRangeFilter /> | ||
| </div> | ||
| </Card> | ||
| ); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,154 @@ | ||
| import {useState} from 'react'; | ||
| import {useNavigate} from '@tanstack/react-router'; | ||
| import {Button} from 'components/Button'; | ||
| import {Calendar} from 'components/Calendar'; | ||
| import {Popover, PopoverContent, PopoverTrigger} from 'components/Popover'; | ||
| import {Tag} from 'components/Tag'; | ||
| import {XIcon} from 'lucide-react'; | ||
|
|
||
| import {useActiveFilters} from '../useActiveFilters'; | ||
|
|
||
| function formatDateDisplay(dateStr: string | undefined): string { | ||
| if (!dateStr) return ''; | ||
| const date = new Date(dateStr.includes('T') ? dateStr : dateStr + 'T00:00:00'); | ||
| return date.toLocaleDateString('en-US', { | ||
| month: 'short', | ||
| day: 'numeric', | ||
| year: 'numeric', | ||
| }); | ||
| } | ||
|
|
||
| function toDateString(date: Date): string { | ||
| const y = date.getFullYear(); | ||
| const m = String(date.getMonth() + 1).padStart(2, '0'); | ||
| const d = String(date.getDate()).padStart(2, '0'); | ||
| return `${y}-${m}-${d}`; | ||
| } | ||
|
|
||
| export function DateRangeFilter() { | ||
| const navigate = useNavigate(); | ||
| const {search} = useActiveFilters(); | ||
| const after = search.created_after as string | undefined; | ||
| const before = search.created_before as string | undefined; | ||
| const [editing, setEditing] = useState<'after' | 'before' | null>(null); | ||
|
|
||
| const afterDate = after ? new Date(after + 'T00:00:00') : undefined; | ||
| const beforeDate = before ? new Date(before + 'T00:00:00') : undefined; | ||
|
|
||
| const update = (key: 'created_after' | 'created_before', value: string | undefined) => { | ||
| navigate({ | ||
| to: '/', | ||
| search: prev => ({...prev, [key]: value}), | ||
| replace: true, | ||
| }); | ||
| }; | ||
|
|
||
| const handleDateSelect = ( | ||
| key: 'created_after' | 'created_before', | ||
| date: Date | undefined | ||
| ) => { | ||
| update(key, date ? toDateString(date) : undefined); | ||
| setEditing(null); | ||
| }; | ||
|
|
||
| return ( | ||
| <div className="col-span-1"> | ||
| <div className="mb-space-md"> | ||
| <h3 className="text-size-md text-content-secondary font-semibold"> | ||
| Created Date | ||
| </h3> | ||
| </div> | ||
| <div className="gap-space-xs flex flex-wrap items-center"> | ||
| <Popover | ||
| open={editing === 'after'} | ||
| onOpenChange={o => setEditing(o ? 'after' : null)} | ||
| > | ||
| <PopoverTrigger asChild> | ||
| {after ? ( | ||
| <Tag | ||
| className="cursor-pointer select-none" | ||
| action={ | ||
| <Button | ||
| variant="close" | ||
| size={null} | ||
| onClick={e => { | ||
| e.stopPropagation(); | ||
| update('created_after', undefined); | ||
| }} | ||
| aria-label="Clear start date" | ||
| > | ||
| <XIcon className="h-3.5 w-3.5" /> | ||
| </Button> | ||
| } | ||
| > | ||
| {formatDateDisplay(after)} | ||
| </Tag> | ||
| ) : ( | ||
| <button | ||
| type="button" | ||
| className="text-size-sm text-content-disabled cursor-pointer select-none italic" | ||
| > | ||
| Any | ||
| </button> | ||
| )} | ||
| </PopoverTrigger> | ||
| <PopoverContent className="w-auto overflow-hidden p-0" align="start"> | ||
| <Calendar | ||
| mode="single" | ||
| selected={afterDate} | ||
| defaultMonth={afterDate} | ||
| captionLayout="dropdown" | ||
| showOutsideDays={false} | ||
| onSelect={d => handleDateSelect('created_after', d)} | ||
| /> | ||
| </PopoverContent> | ||
| </Popover> | ||
| <span className="text-content-disabled text-size-sm">to</span> | ||
| <Popover | ||
| open={editing === 'before'} | ||
| onOpenChange={o => setEditing(o ? 'before' : null)} | ||
| > | ||
| <PopoverTrigger asChild> | ||
| {before ? ( | ||
| <Tag | ||
| className="cursor-pointer select-none" | ||
| action={ | ||
| <Button | ||
| variant="close" | ||
| size={null} | ||
| onClick={e => { | ||
| e.stopPropagation(); | ||
| update('created_before', undefined); | ||
| }} | ||
| aria-label="Clear end date" | ||
| > | ||
| <XIcon className="h-3.5 w-3.5" /> | ||
| </Button> | ||
| } | ||
| > | ||
| {formatDateDisplay(before)} | ||
| </Tag> | ||
| ) : ( | ||
| <button | ||
| type="button" | ||
| className="text-size-sm text-content-disabled cursor-pointer select-none italic" | ||
| > | ||
| Any | ||
| </button> | ||
| )} | ||
| </PopoverTrigger> | ||
| <PopoverContent className="w-auto overflow-hidden p-0" align="start"> | ||
| <Calendar | ||
| mode="single" | ||
| selected={beforeDate} | ||
| defaultMonth={beforeDate} | ||
| captionLayout="dropdown" | ||
| showOutsideDays={false} | ||
| onSelect={d => handleDateSelect('created_before', d)} | ||
| /> | ||
| </PopoverContent> | ||
| </Popover> | ||
| </div> | ||
| </div> | ||
| ); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| import {useNavigate} from '@tanstack/react-router'; | ||
| import {Button} from 'components/Button'; | ||
| import {SlidersHorizontalIcon} from 'lucide-react'; | ||
|
|
||
| import {useActiveFilters} from '../useActiveFilters'; | ||
|
|
||
| export function FilterTrigger({open, onToggle}: {open: boolean; onToggle: () => void}) { | ||
| const navigate = useNavigate(); | ||
| const {activeCount} = useActiveFilters(); | ||
|
|
||
| return ( | ||
| <div className="flex items-center gap-space-md"> | ||
| {activeCount > 0 && ( | ||
| <button | ||
| type="button" | ||
| className="text-content-accent text-size-sm cursor-pointer hover:underline" | ||
| onClick={() => { | ||
| navigate({ | ||
| to: '/', | ||
| search: {}, | ||
| replace: true, | ||
| }); | ||
| }} | ||
|
Comment on lines
+19
to
+23
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Bug: Clicking "Clear all filters" incorrectly removes the Suggested FixUpdate the Prompt for AI Agent |
||
| data-testid="clear-all-filters" | ||
| > | ||
| Clear all filters | ||
| </button> | ||
| )} | ||
| <Button | ||
| variant="secondary" | ||
| size="sm" | ||
| onClick={onToggle} | ||
| aria-expanded={open} | ||
| data-testid="advanced-filters-toggle" | ||
| > | ||
| <SlidersHorizontalIcon className="h-3.5 w-3.5" /> | ||
| {open ? 'Hide filters' : 'Show filters'} | ||
| {activeCount > 0 && ( | ||
| <span className="bg-background-accent-vibrant text-content-on-vibrant-light ml-space-2xs inline-flex h-4 min-w-4 items-center justify-center rounded-full px-1 text-xs leading-none"> | ||
| {activeCount} | ||
| </span> | ||
| )} | ||
| </Button> | ||
| </div> | ||
| ); | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.