diff --git a/webui/src/routes/issues/-issues.api.ts b/webui/src/routes/issues/-issues.api.ts index 5dba4467..5f0d5b16 100644 --- a/webui/src/routes/issues/-issues.api.ts +++ b/webui/src/routes/issues/-issues.api.ts @@ -1,4 +1,4 @@ -import { queryOptions } from '@tanstack/react-query'; +import { queryOptions, type QueryClient } from '@tanstack/react-query'; import { apiClient, readJson } from '@/app/api-client'; @@ -13,6 +13,7 @@ import type { } from './-issues.types'; const DEFAULT_LIMIT = 100; +export const ISSUES_QUERY_KEY = ['issues'] as const; function createIssueHeaders(profileId: number, extra?: HeadersInit): Headers { const headers = new Headers(extra); @@ -125,7 +126,7 @@ export async function deleteIssue(profileId: number, issueId: number): Promise fetchIssueCounts(profileId), }); } @@ -135,15 +136,19 @@ export function issueListQueryOptions( search: Pick, ) { return queryOptions({ - queryKey: ['issues', 'list', profileId, search.status, search.category], + queryKey: [...ISSUES_QUERY_KEY, 'list', profileId, search.status, search.category], queryFn: () => fetchIssueList(profileId, search), }); } export function issueDetailQueryOptions(profileId: number, issueId: number) { return queryOptions({ - queryKey: ['issues', 'detail', profileId, issueId], + queryKey: [...ISSUES_QUERY_KEY, 'detail', profileId, issueId], queryFn: () => fetchIssue(profileId, issueId), enabled: issueId > 0, }); } + +export function invalidateIssuesQueries(queryClient: QueryClient) { + return queryClient.invalidateQueries({ queryKey: ISSUES_QUERY_KEY }); +} diff --git a/webui/src/routes/issues/-issues.helpers.ts b/webui/src/routes/issues/-issues.helpers.ts index 6c07b654..3c4158e0 100644 --- a/webui/src/routes/issues/-issues.helpers.ts +++ b/webui/src/routes/issues/-issues.helpers.ts @@ -6,8 +6,6 @@ import { type IssueStatus, } from './-issues.types'; -export const REFRESH_EVENT = 'ss:issues-refresh'; - export const ISSUE_CATEGORY_META: Record< IssueCategory, { label: string; icon: string; description: string; applies: Array<'track' | 'album' | 'artist'> } @@ -100,10 +98,6 @@ export function getIssueStatusMeta(status: string) { return ISSUE_STATUS_META[status as IssueStatus]; } -export function dispatchIssuesRefreshEvent() { - window.dispatchEvent(new CustomEvent(REFRESH_EVENT)); -} - export function parseSnapshot(snapshot: IssueRecord['snapshot_data']): IssueSnapshot { if (!snapshot) { return {}; diff --git a/webui/src/routes/issues/-ui/issue-domain-host.tsx b/webui/src/routes/issues/-ui/issue-domain-host.tsx index 8a24c109..c5aca392 100644 --- a/webui/src/routes/issues/-ui/issue-domain-host.tsx +++ b/webui/src/routes/issues/-ui/issue-domain-host.tsx @@ -1,6 +1,6 @@ import { useForm } from '@tanstack/react-form'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; -import { useEffect, useMemo, useState } from 'react'; +import { useCallback, useEffect, useMemo, useState } from 'react'; import { z } from 'zod'; import { DialogBody, DialogFooter, DialogFrame, DialogHeader } from '@/components/dialog'; @@ -20,9 +20,8 @@ import { useProfile } from '@/platform/shell/route-controllers'; import type { IssueReportPayload } from '../-issues.types'; -import { createIssue, issueCountsQueryOptions } from '../-issues.api'; +import { createIssue, issueCountsQueryOptions, invalidateIssuesQueries } from '../-issues.api'; import { - REFRESH_EVENT, createDefaultIssueTitle, getIssueCategoriesForEntity, getEntityLabel, @@ -35,6 +34,9 @@ export function IssueDomainHost() { const profile = useProfile(); const [reportPayload, setReportPayload] = useState(null); const profileId = profile.profileId; + const refreshIssues = useCallback(() => { + void invalidateIssuesQueries(queryClient); + }, [queryClient]); const countsQuery = useQuery({ ...issueCountsQueryOptions(profileId), @@ -46,17 +48,6 @@ export function IssueDomainHost() { } }, [countsQuery.data]); - useEffect(() => { - const handleRefresh = () => { - void queryClient.invalidateQueries({ queryKey: ISSUE_DOMAIN_QUERY_KEY }); - }; - - window.addEventListener(REFRESH_EVENT, handleRefresh); - return () => { - window.removeEventListener(REFRESH_EVENT, handleRefresh); - }; - }, [queryClient]); - useEffect(() => { window.SoulSyncIssueDomain = { openReportIssue(payload) { @@ -65,9 +56,7 @@ export function IssueDomainHost() { closeReportIssue() { setReportPayload(null); }, - refresh() { - void queryClient.invalidateQueries({ queryKey: ISSUE_DOMAIN_QUERY_KEY }); - }, + refresh: refreshIssues, }; return () => { @@ -87,7 +76,7 @@ export function IssueDomainHost() { onClose={() => setReportPayload(null)} onSubmitted={() => { setReportPayload(null); - void queryClient.invalidateQueries({ queryKey: ISSUE_DOMAIN_QUERY_KEY }); + refreshIssues(); }} /> )} @@ -330,8 +319,6 @@ function ReportIssueModal({ ); } -const ISSUE_DOMAIN_QUERY_KEY = ['issues'] as const; - const DEFAULT_REPORT_ISSUE_VALUES: ReportIssueFormValues = { category: '', description: '', diff --git a/webui/src/routes/issues/-ui/issues-page.tsx b/webui/src/routes/issues/-ui/issues-page.tsx index e15fbb18..52e86f83 100644 --- a/webui/src/routes/issues/-ui/issues-page.tsx +++ b/webui/src/routes/issues/-ui/issues-page.tsx @@ -1,6 +1,5 @@ import { useQuery, useQueryClient } from '@tanstack/react-query'; import { useNavigate } from '@tanstack/react-router'; -import { useEffect } from 'react'; import { Select } from '@/components/form'; import { Show } from '@/components/primitives'; @@ -8,10 +7,12 @@ import { useProfile, useReactPageShell } from '@/platform/shell/route-controller import type { IssueCounts, IssuePriority, IssueRecord, IssuesSearch } from '../-issues.types'; -import { issueCountsQueryOptions, issueListQueryOptions } from '../-issues.api'; import { - REFRESH_EVENT, - dispatchIssuesRefreshEvent, + issueCountsQueryOptions, + issueListQueryOptions, + invalidateIssuesQueries, +} from '../-issues.api'; +import { formatIssueDate, getEntityDetails, getEntityLabel, @@ -31,6 +32,7 @@ import styles from './issues-page.module.css'; export function IssuesPage() { useReactPageShell('issues'); + const queryClient = useQueryClient(); const navigate = useNavigate({ from: Route.fullPath }); const params = Route.useSearch(); @@ -42,16 +44,18 @@ export function IssuesPage() { }); }; + const handleMutationSuccess = () => { + clearIssueSelection(); + void invalidateIssuesQueries(queryClient); + }; + return ( <> { - clearIssueSelection(); - dispatchIssuesRefreshEvent(); - }} + onMutationSuccess={handleMutationSuccess} /> ); @@ -59,21 +63,9 @@ export function IssuesPage() { function IssueBoard() { const { isAdmin, profileId } = useProfile(); - const queryClient = useQueryClient(); const navigate = useNavigate({ from: Route.fullPath }); const params = Route.useSearch(); - useEffect(() => { - const handleRefresh = () => { - void queryClient.invalidateQueries({ queryKey: ['issues'] }); - }; - - window.addEventListener(REFRESH_EVENT, handleRefresh); - return () => { - window.removeEventListener(REFRESH_EVENT, handleRefresh); - }; - }, [queryClient]); - const countsQuery = useQuery({ ...issueCountsQueryOptions(profileId), });