Move issue detail query into modal

- let the issue detail modal own its selected-issue query and loading states
- keep the issues page focused on route state and modal orchestration
- preserve the loader prefetch so the modal still opens from warm cache
This commit is contained in:
Antti Kettunen 2026-05-02 20:27:42 +03:00
parent a14d397bea
commit 3470654fc5
No known key found for this signature in database
GPG key ID: C6B2A3D250359BD7
2 changed files with 19 additions and 26 deletions

View file

@ -1,4 +1,4 @@
import { useMutation } from '@tanstack/react-query';
import { useMutation, useQuery } from '@tanstack/react-query';
import { useEffect, useMemo, useState, type ReactNode } from 'react';
import { DialogBody, DialogFooter, DialogFrame, DialogHeader } from '@/components/dialog';
@ -10,7 +10,7 @@ import {
import type { IssueRecord } from '../-issues.types';
import { deleteIssue, updateIssue } from '../-issues.api';
import { deleteIssue, issueDetailQueryOptions, updateIssue } from '../-issues.api';
import {
formatIssueDate,
formatStatusLabel,
@ -22,24 +22,27 @@ import {
import styles from './issue-detail-modal.module.css';
export function IssueDetailModal({
error,
isAdmin,
isLoading,
issue,
issueId,
onClose,
onMutationSuccess,
profileId,
}: {
error: unknown;
isAdmin: boolean;
isLoading: boolean;
issue: IssueRecord | null;
issueId: number | null;
onClose: () => void;
onMutationSuccess: () => void;
profileId: number;
}) {
const selectedIssueQuery = useQuery({
...issueDetailQueryOptions(profileId, issueId ?? 0),
enabled: profileId > 0 && issueId !== null,
});
const issue = selectedIssueQuery.data ?? null;
const queryError = selectedIssueQuery.error;
const queryLoading = selectedIssueQuery.isLoading;
const [adminResponse, setAdminResponse] = useState('');
const isOpen = Boolean(issue || isLoading || error);
const isOpen = Boolean(issueId || queryLoading || queryError);
useEffect(() => {
setAdminResponse(issue?.admin_response || '');
@ -171,7 +174,7 @@ export function IssueDetailModal({
return null;
}, [adminResponse, deleteMutation, isAdmin, issue, updateMutation]);
if (!issue && !isLoading && !error) {
if (!issue && !queryLoading && !queryError) {
return null;
}
@ -210,16 +213,16 @@ export function IssueDetailModal({
closeLabel="Close issue detail"
/>
<DialogBody>
{isLoading ? (
{queryLoading ? (
<div className={styles.issuesLoading}>
<div className={styles.issuesSpinner} />
Loading issue details...
</div>
) : error ? (
) : queryError ? (
<div className={styles.issuesEmpty}>
<div className={styles.issuesEmptyTitle}>Failed to load issue</div>
<div className={styles.issuesEmptyText}>
{error instanceof Error ? error.message : 'Unknown error'}
{queryError instanceof Error ? queryError.message : 'Unknown error'}
</div>
</div>
) : issue ? (
@ -414,7 +417,7 @@ export function IssueDetailModal({
<Button className={styles.modalButtonSecondary} type="button" onClick={onClose}>
Close
</Button>
{!isLoading && !error && issue && (
{issue && (
<>
{statusButtons}
{isAdmin && (

View file

@ -8,11 +8,7 @@ import { useReactPageShell } from '@/platform/shell/route-controllers';
import type { IssueCounts, IssueRecord, IssueStatus } from '../-issues.types';
import {
issueCountsQueryOptions,
issueDetailQueryOptions,
issueListQueryOptions,
} from '../-issues.api';
import { issueCountsQueryOptions, issueListQueryOptions } from '../-issues.api';
import {
CLOSE_EVENT,
REFRESH_EVENT,
@ -85,10 +81,6 @@ export function IssuesPage() {
...issueListQueryOptions(profileId, normalizedSearch),
enabled: profileId > 0,
});
const selectedIssueQuery = useQuery({
...issueDetailQueryOptions(profileId, selectedIssueId ?? 0),
enabled: profileId > 0 && selectedIssueId !== null,
});
if (!bridge || !profile || !bridge.isPageAllowed('issues')) {
return null;
@ -130,9 +122,7 @@ export function IssuesPage() {
/>
<IssueDetailModal
isAdmin={profile.isAdmin}
issue={selectedIssueQuery.data ?? null}
isLoading={selectedIssueQuery.isLoading}
error={selectedIssueQuery.error}
issueId={selectedIssueId}
onClose={() => clearIssueSelection(navigate)}
onMutationSuccess={() => {
clearIssueSelection(navigate);