Centralize issues refresh handling

- add a shared issues query invalidation helper
- invalidate from the page and domain host directly
- remove the internal window refresh event listener
- keep the legacy bridge refresh method wired to the shared helper
pull/388/head
Antti Kettunen 2 weeks ago
parent 892334007d
commit ed2b8d0e3d
No known key found for this signature in database
GPG Key ID: C6B2A3D250359BD7

@ -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<v
export function issueCountsQueryOptions(profileId: number) {
return queryOptions({
queryKey: ['issues', 'counts', profileId],
queryKey: [...ISSUES_QUERY_KEY, 'counts', profileId],
queryFn: () => fetchIssueCounts(profileId),
});
}
@ -135,15 +136,19 @@ export function issueListQueryOptions(
search: Pick<IssuesSearch, 'status' | 'category'>,
) {
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 });
}

@ -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 {};

@ -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<IssueReportPayload | null>(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: '',

@ -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 (
<>
<IssueBoard />
<IssueDetailModal
issueId={params.issueId}
onClose={clearIssueSelection}
onMutationSuccess={() => {
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),
});

Loading…
Cancel
Save