mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 09:25:06 +03:00
Chore/update notification v2 filter popover (#20375)
* Update notification v2 popover * Update notifications empty state language based on filters * Fix typo * Update apps/studio/state/notifications.ts Co-authored-by: Alaister Young <alaister@users.noreply.github.com> --------- Co-authored-by: Alaister Young <alaister@users.noreply.github.com>
This commit is contained in:
1 parent
36476cee5d
commit
9d9cb374e6
7 files changed
+309
-101
No files matched your search
+99
-5
@@ -1,16 +1,110 @@
|
||||
import { Settings2Icon } from 'lucide-react'
|
||||
import { useState } from 'react'
|
||||
import { Button, PopoverContent_Shadcn_, PopoverTrigger_Shadcn_, Popover_Shadcn_ } from 'ui'
|
||||
import {
|
||||
Button,
|
||||
Checkbox_Shadcn_,
|
||||
PopoverContent_Shadcn_,
|
||||
PopoverTrigger_Shadcn_,
|
||||
Popover_Shadcn_,
|
||||
ScrollArea,
|
||||
Separator,
|
||||
} from 'ui'
|
||||
import { CriticalIcon, WarningIcon } from './NotificationsPopover.constants'
|
||||
import { useOrganizationsQuery } from 'data/organizations/organizations-query'
|
||||
import { useProjectsQuery } from 'data/projects/projects-query'
|
||||
import { useNotificationsStateSnapshot } from 'state/notifications'
|
||||
|
||||
export const NotificationsFilter = () => {
|
||||
export const NotificationsFilter = ({ activeTab }: { activeTab: 'inbox' | 'archived' }) => {
|
||||
const [open, setOpen] = useState(false)
|
||||
const snap = useNotificationsStateSnapshot()
|
||||
|
||||
const { data: organizations } = useOrganizationsQuery()
|
||||
const { data: projects } = useProjectsQuery()
|
||||
|
||||
return (
|
||||
<Popover_Shadcn_ modal={false} open={open} onOpenChange={setOpen}>
|
||||
<PopoverTrigger_Shadcn_ asChild>
|
||||
<Button type="text" icon={<Settings2Icon size={14} />} className="px-1" />
|
||||
<Button
|
||||
type={snap.numFiltersApplied > 0 ? 'default' : 'text'}
|
||||
icon={<Settings2Icon size={14} />}
|
||||
className="px-1"
|
||||
>
|
||||
{snap.numFiltersApplied > 0 &&
|
||||
`${snap.numFiltersApplied} filter${snap.numFiltersApplied > 1 ? 's' : ''} applied`}
|
||||
</Button>
|
||||
</PopoverTrigger_Shadcn_>
|
||||
<PopoverContent_Shadcn_ className="p-0 w-52" side="bottom" align="end">
|
||||
Hello
|
||||
<PopoverContent_Shadcn_ className="p-0 w-64" side="bottom" align="end">
|
||||
<div className="p-2">
|
||||
<p className="text-xs text-foreground-light">Filter notifications</p>
|
||||
</div>
|
||||
<Separator />
|
||||
|
||||
<ScrollArea className="h-[240px]">
|
||||
<div className="p-2 flex flex-col gap-y-2">
|
||||
<p className="text-xs">Status</p>
|
||||
<div className="flex items-center gap-x-2">
|
||||
<Checkbox_Shadcn_
|
||||
disabled={activeTab === 'archived'}
|
||||
checked={snap.filterStatuses.includes('unread')}
|
||||
onCheckedChange={() => snap.setFilters('unread', 'status')}
|
||||
/>
|
||||
<p className={`text-xs ${activeTab === 'archived' ? 'opacity-50' : ''}`}>Unread</p>
|
||||
</div>
|
||||
</div>
|
||||
<Separator />
|
||||
<div className="p-2 flex flex-col gap-y-2">
|
||||
<p className="text-xs">Priority</p>
|
||||
<div className="flex items-center gap-x-2">
|
||||
<Checkbox_Shadcn_
|
||||
checked={snap.filterPriorities.includes('Warning')}
|
||||
onCheckedChange={() => snap.setFilters('Warning', 'priority')}
|
||||
/>
|
||||
<WarningIcon className="w-2 h-2" />
|
||||
<p className="text-xs">Warning</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-x-2">
|
||||
<Checkbox_Shadcn_
|
||||
checked={snap.filterPriorities.includes('Critical')}
|
||||
onCheckedChange={() => snap.setFilters('Critical', 'priority')}
|
||||
/>
|
||||
<CriticalIcon className="w-2 h-2" />
|
||||
<p className="text-xs">Critical</p>
|
||||
</div>
|
||||
</div>
|
||||
<Separator />
|
||||
<div className="p-2 flex flex-col gap-y-2">
|
||||
<p className="text-xs">Organizations</p>
|
||||
{(organizations ?? []).map((org) => (
|
||||
<div key={org.slug} className="flex items-center gap-x-2">
|
||||
<Checkbox_Shadcn_
|
||||
checked={snap.filterOrganizations.includes(org.slug)}
|
||||
onCheckedChange={() => snap.setFilters(org.slug, 'organizations')}
|
||||
/>
|
||||
<p className="text-xs">{org.name}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<Separator />
|
||||
<div className="p-2 flex flex-col gap-y-2">
|
||||
<p className="text-xs">Projects</p>
|
||||
{(projects ?? []).map((project) => (
|
||||
<div key={project.ref} className="flex items-center gap-x-2">
|
||||
<Checkbox_Shadcn_
|
||||
checked={snap.filterProjects.includes(project.ref)}
|
||||
onCheckedChange={() => snap.setFilters(project.ref, 'projects')}
|
||||
/>
|
||||
<p className="text-xs">{project.name}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</ScrollArea>
|
||||
|
||||
<Separator />
|
||||
<div className="p-2 flex justify-end">
|
||||
<Button type="default" onClick={() => snap.resetFilters()}>
|
||||
Reset filters
|
||||
</Button>
|
||||
</div>
|
||||
</PopoverContent_Shadcn_>
|
||||
</Popover_Shadcn_>
|
||||
)
|
||||
|
||||
-12
@@ -1,15 +1,3 @@
|
||||
export type NOTIFICATION_FILTER_TYPE = 'all' | 'unread' | 'warning' | 'critical'
|
||||
|
||||
export const NOTIFICATION_FILTERS: {
|
||||
id: NOTIFICATION_FILTER_TYPE
|
||||
label: string
|
||||
}[] = [
|
||||
{ id: 'all', label: 'View all notifications' },
|
||||
{ id: 'unread', label: 'View unread notifications' },
|
||||
{ id: 'warning', label: 'View warning notifications' },
|
||||
{ id: 'critical', label: 'View critical notifications' },
|
||||
]
|
||||
|
||||
export const CriticalIcon = ({ className }: { className?: string }) => {
|
||||
return (
|
||||
<svg
|
||||
|
||||
+47
-72
@@ -1,15 +1,7 @@
|
||||
import clsx from 'clsx'
|
||||
import { Settings2Icon, SlidersHorizontal } from 'lucide-react'
|
||||
import { useMemo, useRef, useState } from 'react'
|
||||
import {
|
||||
Button,
|
||||
CommandGroup_Shadcn_,
|
||||
CommandItem_Shadcn_,
|
||||
CommandList_Shadcn_,
|
||||
Command_Shadcn_,
|
||||
IconAlertCircle,
|
||||
IconAlertTriangle,
|
||||
IconCheck,
|
||||
IconInbox,
|
||||
PopoverContent_Shadcn_,
|
||||
PopoverTrigger_Shadcn_,
|
||||
@@ -21,24 +13,21 @@ import AlertError from 'components/ui/AlertError'
|
||||
import InfiniteList from 'components/ui/InfiniteList'
|
||||
import ShimmeringLoader, { GenericSkeletonLoader } from 'components/ui/ShimmeringLoader'
|
||||
import { useNotificationsV2Query } from 'data/notifications/notifications-v2-query'
|
||||
import { useNotificationsSummaryQuery } from 'data/notifications/notifications-v2-summary-query'
|
||||
import { useNotificationsV2UpdateMutation } from 'data/notifications/notifications-v2-update-mutation'
|
||||
import { useOrganizationsQuery } from 'data/organizations/organizations-query'
|
||||
import { useProjectsQuery } from 'data/projects/projects-query'
|
||||
import { useNotificationsStateSnapshot } from 'state/notifications'
|
||||
import NotificationRow from './NotificationRow'
|
||||
import {
|
||||
CriticalIcon,
|
||||
NOTIFICATION_FILTERS,
|
||||
NOTIFICATION_FILTER_TYPE,
|
||||
WarningIcon,
|
||||
} from './NotificationsPopover.constants'
|
||||
import { useNotificationsSummaryQuery } from 'data/notifications/notifications-v2-summary-query'
|
||||
import { NotificationsFilter } from './NotificationsFilter'
|
||||
import { CriticalIcon, WarningIcon } from './NotificationsPopover.constants'
|
||||
|
||||
const NotificationsPopverV2 = () => {
|
||||
const NotificationsPopoverV2 = () => {
|
||||
const [open, setOpen] = useState(false)
|
||||
const [openFilters, setOpenFilters] = useState(false)
|
||||
const [selectedFilter, setSelectedFilter] = useState<NOTIFICATION_FILTER_TYPE>('all')
|
||||
const [activeTab, setActiveTab] = useState<'inbox' | 'archived'>('inbox')
|
||||
|
||||
const snap = useNotificationsStateSnapshot()
|
||||
|
||||
// Storing in ref as no re-rendering required
|
||||
const markedRead = useRef<string[]>([])
|
||||
|
||||
@@ -59,15 +48,19 @@ const NotificationsPopverV2 = () => {
|
||||
isFetchingNextPage,
|
||||
fetchNextPage,
|
||||
} = useNotificationsV2Query({
|
||||
status: activeTab === 'archived' ? 'archived' : selectedFilter === 'unread' ? 'new' : undefined,
|
||||
priority:
|
||||
selectedFilter === 'critical'
|
||||
? 'Critical'
|
||||
: selectedFilter === 'warning'
|
||||
? 'Warning'
|
||||
status:
|
||||
activeTab === 'archived'
|
||||
? 'archived'
|
||||
: snap.filterStatuses.includes('unread')
|
||||
? 'new'
|
||||
: undefined,
|
||||
filters: {
|
||||
priority: snap.filterPriorities,
|
||||
organizations: snap.filterOrganizations,
|
||||
projects: snap.filterProjects,
|
||||
},
|
||||
})
|
||||
const { data: summary } = useNotificationsSummaryQuery()
|
||||
const { data: summary, isSuccess: isSuccessSummary } = useNotificationsSummaryQuery()
|
||||
const { mutate: updateNotifications } = useNotificationsV2UpdateMutation()
|
||||
|
||||
const notifications = useMemo(() => data?.pages.flatMap((page) => page) ?? [], [data?.pages])
|
||||
@@ -75,11 +68,6 @@ const NotificationsPopverV2 = () => {
|
||||
const hasWarning = summary?.has_warning
|
||||
const hasCritical = summary?.has_critical
|
||||
|
||||
const onSelectFilter = (value: 'all' | 'unread' | 'warning' | 'critical') => {
|
||||
setSelectedFilter(value)
|
||||
setOpenFilters(false)
|
||||
}
|
||||
|
||||
const markNotificationsRead = () => {
|
||||
if (markedRead.current.length > 0) {
|
||||
updateNotifications({ ids: markedRead.current, status: 'seen' })
|
||||
@@ -145,50 +133,31 @@ const NotificationsPopverV2 = () => {
|
||||
activeId={activeTab}
|
||||
onChange={(tab: 'inbox' | 'archived') => {
|
||||
setActiveTab(tab)
|
||||
if (tab === 'archived' && snap.filterStatuses.includes('unread')) {
|
||||
snap.setFilters('unread', 'status')
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Tabs.Panel
|
||||
id="inbox"
|
||||
label="Inbox"
|
||||
iconRight={
|
||||
<div
|
||||
className={clsx([
|
||||
'flex items-center justify-center text-xs rounded-full bg-surface-300 h-4',
|
||||
(summary?.unread_count ?? 0) > 9 ? 'px-0.5 w-auto' : 'w-4',
|
||||
])}
|
||||
>
|
||||
{summary?.unread_count}
|
||||
</div>
|
||||
isSuccessSummary ? (
|
||||
<div
|
||||
className={clsx([
|
||||
'flex items-center justify-center text-xs rounded-full bg-surface-300 h-4',
|
||||
(summary?.unread_count ?? 0) > 9 ? 'px-0.5 w-auto' : 'w-4',
|
||||
])}
|
||||
>
|
||||
{summary?.unread_count}
|
||||
</div>
|
||||
) : null
|
||||
}
|
||||
/>
|
||||
<Tabs.Panel id="archived" label="Archived" />
|
||||
</Tabs>
|
||||
<Popover_Shadcn_ modal={false} open={openFilters} onOpenChange={setOpenFilters}>
|
||||
<PopoverTrigger_Shadcn_ asChild>
|
||||
<Button type="text" icon={<Settings2Icon size={14} />}>
|
||||
View {selectedFilter}
|
||||
</Button>
|
||||
</PopoverTrigger_Shadcn_>
|
||||
<PopoverContent_Shadcn_ className="p-0 w-52" side="bottom" align="end">
|
||||
<Command_Shadcn_>
|
||||
<CommandList_Shadcn_>
|
||||
<CommandGroup_Shadcn_>
|
||||
{NOTIFICATION_FILTERS.map((filter) => (
|
||||
<CommandItem_Shadcn_
|
||||
key={filter.id}
|
||||
className="cursor-pointer flex items-center justify-between"
|
||||
onSelect={() => onSelectFilter(filter.id)}
|
||||
onClick={() => onSelectFilter(filter.id)}
|
||||
>
|
||||
<p>{filter.label}</p>
|
||||
{selectedFilter === filter.id && <IconCheck />}
|
||||
</CommandItem_Shadcn_>
|
||||
))}
|
||||
</CommandGroup_Shadcn_>
|
||||
</CommandList_Shadcn_>
|
||||
</Command_Shadcn_>
|
||||
</PopoverContent_Shadcn_>
|
||||
</Popover_Shadcn_>
|
||||
|
||||
<NotificationsFilter activeTab={activeTab} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="border-t">
|
||||
@@ -205,7 +174,7 @@ const NotificationsPopverV2 = () => {
|
||||
{isSuccess && (
|
||||
<div className="flex flex-1 h-[400px]">
|
||||
{notifications.length > 0 &&
|
||||
!(activeTab === 'archived' && selectedFilter === 'unread') ? (
|
||||
!(activeTab === 'archived' && snap.filterStatuses.includes('unread')) ? (
|
||||
<InfiniteList
|
||||
items={notifications}
|
||||
ItemComponent={NotificationRow}
|
||||
@@ -239,16 +208,22 @@ const NotificationsPopverV2 = () => {
|
||||
<div className="flex flex-col gap-y-4 items-center flex-grow justify-center">
|
||||
<IconInbox size={32} className="text-foreground-light" />
|
||||
<div className="flex flex-col gap-y-1">
|
||||
<p className="text-foreground-light text-sm w-64 text-center">
|
||||
<p className="text-foreground-light text-sm mx-auto text-center">
|
||||
{activeTab === 'archived'
|
||||
? `No archived ${
|
||||
['warning', 'critical'].includes(selectedFilter)
|
||||
? `${selectedFilter} `
|
||||
? `No archived notifications${
|
||||
snap.numFiltersApplied > 0
|
||||
? ` based on the ${snap.numFiltersApplied} filter${
|
||||
snap.numFiltersApplied > 1 ? 's' : ''
|
||||
} applied`
|
||||
: ''
|
||||
}notifications`
|
||||
}`
|
||||
: snap.numFiltersApplied > 0
|
||||
? `No notifications based on the ${snap.numFiltersApplied} filter${
|
||||
snap.numFiltersApplied > 1 ? 's' : ''
|
||||
} applied`
|
||||
: 'All caught up'}
|
||||
</p>
|
||||
<p className="text-foreground-lighter text-xs w-64 text-center">
|
||||
<p className="text-foreground-lighter text-xs w-60 mx-auto text-center">
|
||||
{activeTab === 'archived'
|
||||
? 'Notifications that you have previously archived will be shown here'
|
||||
: 'You will be notified here for any notices on your organizations and projects'}
|
||||
@@ -264,4 +239,4 @@ const NotificationsPopverV2 = () => {
|
||||
)
|
||||
}
|
||||
|
||||
export default NotificationsPopverV2
|
||||
export default NotificationsPopoverV2
|
||||
Vendored
+81
-4
@@ -724,7 +724,7 @@ export interface paths {
|
||||
get: operations['VercelAccessTokenController_getAccessToken']
|
||||
}
|
||||
'/platform/vercel/projects': {
|
||||
/** Gets the project with the given ID if provided, otherwise gets the list of projects */
|
||||
/** Gets the list of Vercel projects */
|
||||
get: operations['VercelProjectsController_getVercelProjects']
|
||||
}
|
||||
'/platform/vercel/projects/{id}': {
|
||||
@@ -1610,6 +1610,10 @@ export interface paths {
|
||||
/** Enables Database Webhooks on the project */
|
||||
post: operations['V1DatabaseWebhooksController_v1EnableDatabaseWebhooks']
|
||||
}
|
||||
'/v1/projects/{ref}/database/backups': {
|
||||
/** Lists all backups */
|
||||
get: operations['V1BackupsController_getBackups']
|
||||
}
|
||||
'/v1/projects/{ref}/database/backups/restore-pitr': {
|
||||
/** Restores a PITR backup for a database */
|
||||
post: operations['V1RestorePitrController_v1RestorePitr']
|
||||
@@ -1650,6 +1654,10 @@ export interface paths {
|
||||
*/
|
||||
get: operations['FunctionSlugController_getFunctionBody']
|
||||
}
|
||||
'/v1/projects/{ref}/storage/buckets': {
|
||||
/** Lists all buckets */
|
||||
get: operations['V1StorageBucketsController_getBuckets']
|
||||
}
|
||||
'/v1/organizations': {
|
||||
/**
|
||||
* List all organizations
|
||||
@@ -4789,6 +4797,22 @@ export interface components {
|
||||
created_at?: string
|
||||
updated_at?: string
|
||||
}
|
||||
V1Backup: {
|
||||
/** @enum {string} */
|
||||
status: 'COMPLETED' | 'FAILED' | 'PENDING' | 'REMOVED' | 'ARCHIVED'
|
||||
is_physical_backup: boolean
|
||||
inserted_at: string
|
||||
}
|
||||
V1BackupsResponse: {
|
||||
region: string
|
||||
walg_enabled: boolean
|
||||
pitr_enabled: boolean
|
||||
backups: components['schemas']['V1Backup'][]
|
||||
physical_backup_data: {
|
||||
earliest_physical_backup_date_unix?: number
|
||||
latest_physical_backup_date_unix?: number
|
||||
}
|
||||
}
|
||||
V1RestorePitrBody: {
|
||||
recovery_time_target_unix: number
|
||||
}
|
||||
@@ -4806,6 +4830,14 @@ export interface components {
|
||||
entrypoint_path?: string
|
||||
import_map_path?: string
|
||||
}
|
||||
V1StorageBucketResponse: {
|
||||
id: string
|
||||
name: string
|
||||
owner: string
|
||||
created_at: string
|
||||
updated_at: string
|
||||
public: boolean
|
||||
}
|
||||
OrganizationResponseV1: {
|
||||
id: string
|
||||
name: string
|
||||
@@ -5040,6 +5072,8 @@ export interface operations {
|
||||
query: {
|
||||
status: 'new' | 'seen' | 'archived'
|
||||
priority: 'Critical' | 'Warning' | 'Info'
|
||||
org_slug: string[]
|
||||
project_ref: string[]
|
||||
offset: number
|
||||
limit: number
|
||||
}
|
||||
@@ -10055,7 +10089,7 @@ export interface operations {
|
||||
}
|
||||
}
|
||||
}
|
||||
/** Gets the project with the given ID if provided, otherwise gets the list of projects */
|
||||
/** Gets the list of Vercel projects */
|
||||
VercelProjectsController_getVercelProjects: {
|
||||
parameters: {
|
||||
query: {
|
||||
@@ -10069,10 +10103,10 @@ export interface operations {
|
||||
responses: {
|
||||
200: {
|
||||
content: {
|
||||
'application/json': Record<string, never>
|
||||
'application/json': Record<string, never>[]
|
||||
}
|
||||
}
|
||||
/** @description Failed to get project(s) */
|
||||
/** @description Failed to get projects */
|
||||
500: {
|
||||
content: never
|
||||
}
|
||||
@@ -12334,6 +12368,26 @@ export interface operations {
|
||||
}
|
||||
}
|
||||
}
|
||||
/** Lists all backups */
|
||||
V1BackupsController_getBackups: {
|
||||
parameters: {
|
||||
path: {
|
||||
/** @description Project ref */
|
||||
ref: string
|
||||
}
|
||||
}
|
||||
responses: {
|
||||
200: {
|
||||
content: {
|
||||
'application/json': components['schemas']['V1BackupsResponse']
|
||||
}
|
||||
}
|
||||
/** @description Failed to get backups */
|
||||
500: {
|
||||
content: never
|
||||
}
|
||||
}
|
||||
}
|
||||
/** Restores a PITR backup for a database */
|
||||
V1RestorePitrController_v1RestorePitr: {
|
||||
parameters: {
|
||||
@@ -12475,6 +12529,29 @@ export interface operations {
|
||||
}
|
||||
}
|
||||
}
|
||||
/** Lists all buckets */
|
||||
V1StorageBucketsController_getBuckets: {
|
||||
parameters: {
|
||||
path: {
|
||||
/** @description Project ref */
|
||||
ref: string
|
||||
}
|
||||
}
|
||||
responses: {
|
||||
200: {
|
||||
content: {
|
||||
'application/json': components['schemas']['V1StorageBucketResponse'][]
|
||||
}
|
||||
}
|
||||
403: {
|
||||
content: never
|
||||
}
|
||||
/** @description Failed to get list of buckets */
|
||||
500: {
|
||||
content: never
|
||||
}
|
||||
}
|
||||
}
|
||||
/** Create an organization */
|
||||
OrganizationsController_createOrganization: {
|
||||
requestBody: {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
export const notificationKeys = {
|
||||
list: () => ['notifications'] as const,
|
||||
listV2: (options?: { status?: string; priority?: string; limit: number }) =>
|
||||
listV2: (options?: { status?: string; limit: number; filters: any }) =>
|
||||
['notifications', options] as const,
|
||||
summary: () => ['notifications', 'summary'] as const,
|
||||
}
|
||||
@@ -8,11 +8,14 @@ import { notificationKeys } from './keys'
|
||||
const NOTIFICATIONS_PAGE_LIMIT = 10
|
||||
|
||||
export type NotificationVariables = {
|
||||
// archived: boolean
|
||||
page: number
|
||||
limit?: number
|
||||
status?: 'new' | 'seen' | 'archived'
|
||||
priority?: 'Critical' | 'Warning' | 'Info'
|
||||
filters: {
|
||||
priority: readonly string[]
|
||||
organizations: readonly string[]
|
||||
projects: readonly string[]
|
||||
}
|
||||
}
|
||||
|
||||
export type Notification = components['schemas']['NotificationResponseV2']
|
||||
@@ -33,7 +36,7 @@ export type NotificationData = {
|
||||
}
|
||||
|
||||
export async function getNotifications(options: NotificationVariables, signal?: AbortSignal) {
|
||||
const { page = 0, limit = NOTIFICATIONS_PAGE_LIMIT, status, priority } = options
|
||||
const { status, filters, page = 0, limit = NOTIFICATIONS_PAGE_LIMIT } = options
|
||||
const { data, error } = await get('/platform/notifications', {
|
||||
params: {
|
||||
// @ts-ignore
|
||||
@@ -41,7 +44,9 @@ export async function getNotifications(options: NotificationVariables, signal?:
|
||||
offset: page * limit,
|
||||
limit,
|
||||
...(status !== undefined ? { status } : { status: ['new', 'seen'] }),
|
||||
...(priority !== undefined ? { priority } : {}),
|
||||
...(filters.priority.length > 0 ? { priority: filters.priority } : {}),
|
||||
...(filters.organizations.length > 0 ? { org_slug: filters.organizations } : {}),
|
||||
...(filters.projects.length > 0 ? { project_ref: filters.projects } : {}),
|
||||
},
|
||||
},
|
||||
headers: { Version: '2' },
|
||||
@@ -57,16 +62,16 @@ export type NotificationsData = Awaited<ReturnType<typeof getNotifications>>
|
||||
export type NotificationsError = ResponseError
|
||||
|
||||
export const useNotificationsV2Query = <TData = NotificationsData>(
|
||||
{ status, priority, limit = NOTIFICATIONS_PAGE_LIMIT }: Omit<NotificationVariables, 'page'>,
|
||||
{ status, filters, limit = NOTIFICATIONS_PAGE_LIMIT }: Omit<NotificationVariables, 'page'>,
|
||||
{
|
||||
enabled = true,
|
||||
...options
|
||||
}: UseInfiniteQueryOptions<NotificationsData, NotificationsError, TData> = {}
|
||||
) => {
|
||||
return useInfiniteQuery<NotificationsData, NotificationsError, TData>(
|
||||
notificationKeys.listV2({ status, priority, limit }),
|
||||
notificationKeys.listV2({ status, filters, limit }),
|
||||
({ signal, pageParam }) =>
|
||||
getNotifications({ status, priority, limit, page: pageParam }, signal),
|
||||
getNotifications({ status, filters, limit, page: pageParam }, signal),
|
||||
{
|
||||
enabled: enabled,
|
||||
getNextPageParam(lastPage, pages) {
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
import { proxy, snapshot, useSnapshot } from 'valtio'
|
||||
|
||||
export const notificationsState = proxy({
|
||||
filterStatuses: [] as string[],
|
||||
filterPriorities: [] as string[],
|
||||
filterOrganizations: [] as string[],
|
||||
filterProjects: [] as string[],
|
||||
get numFiltersApplied() {
|
||||
return [
|
||||
...this.filterStatuses,
|
||||
...this.filterPriorities,
|
||||
...this.filterOrganizations,
|
||||
...this.filterProjects,
|
||||
].length
|
||||
},
|
||||
setFilters: (value: string, type: 'status' | 'priority' | 'organizations' | 'projects') => {
|
||||
switch (type) {
|
||||
case 'status':
|
||||
if (notificationsState.filterStatuses.includes(value)) {
|
||||
notificationsState.filterStatuses = notificationsState.filterStatuses.filter(
|
||||
(x) => x !== value
|
||||
)
|
||||
} else {
|
||||
notificationsState.filterStatuses = notificationsState.filterStatuses.concat([value])
|
||||
}
|
||||
break
|
||||
case 'priority':
|
||||
if (notificationsState.filterPriorities.includes(value)) {
|
||||
notificationsState.filterPriorities = notificationsState.filterPriorities.filter(
|
||||
(x) => x !== value
|
||||
)
|
||||
} else {
|
||||
notificationsState.filterPriorities = notificationsState.filterPriorities.concat([value])
|
||||
}
|
||||
break
|
||||
case 'organizations':
|
||||
if (notificationsState.filterOrganizations.includes(value)) {
|
||||
notificationsState.filterOrganizations = notificationsState.filterOrganizations.filter(
|
||||
(x) => x !== value
|
||||
)
|
||||
} else {
|
||||
notificationsState.filterOrganizations = notificationsState.filterOrganizations.concat([
|
||||
value,
|
||||
])
|
||||
}
|
||||
break
|
||||
case 'projects':
|
||||
if (notificationsState.filterProjects.includes(value)) {
|
||||
notificationsState.filterProjects = notificationsState.filterProjects.filter(
|
||||
(x) => x !== value
|
||||
)
|
||||
} else {
|
||||
notificationsState.filterProjects = notificationsState.filterProjects.concat([value])
|
||||
}
|
||||
break
|
||||
}
|
||||
},
|
||||
resetFilters: () => {
|
||||
notificationsState.filterStatuses = []
|
||||
notificationsState.filterPriorities = []
|
||||
notificationsState.filterOrganizations = []
|
||||
notificationsState.filterProjects = []
|
||||
},
|
||||
})
|
||||
|
||||
export const getNotificationsStateSnapshot = () => snapshot(notificationsState)
|
||||
|
||||
export const useNotificationsStateSnapshot = (options?: Parameters<typeof useSnapshot>[1]) =>
|
||||
useSnapshot(notificationsState, options)
|
||||
Reference in new issue
Block a user