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:
parent
a14d397bea
commit
3470654fc5
2 changed files with 19 additions and 26 deletions
|
|
@ -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 { useEffect, useMemo, useState, type ReactNode } from 'react';
|
||||||
|
|
||||||
import { DialogBody, DialogFooter, DialogFrame, DialogHeader } from '@/components/dialog';
|
import { DialogBody, DialogFooter, DialogFrame, DialogHeader } from '@/components/dialog';
|
||||||
|
|
@ -10,7 +10,7 @@ import {
|
||||||
|
|
||||||
import type { IssueRecord } from '../-issues.types';
|
import type { IssueRecord } from '../-issues.types';
|
||||||
|
|
||||||
import { deleteIssue, updateIssue } from '../-issues.api';
|
import { deleteIssue, issueDetailQueryOptions, updateIssue } from '../-issues.api';
|
||||||
import {
|
import {
|
||||||
formatIssueDate,
|
formatIssueDate,
|
||||||
formatStatusLabel,
|
formatStatusLabel,
|
||||||
|
|
@ -22,24 +22,27 @@ import {
|
||||||
import styles from './issue-detail-modal.module.css';
|
import styles from './issue-detail-modal.module.css';
|
||||||
|
|
||||||
export function IssueDetailModal({
|
export function IssueDetailModal({
|
||||||
error,
|
|
||||||
isAdmin,
|
isAdmin,
|
||||||
isLoading,
|
issueId,
|
||||||
issue,
|
|
||||||
onClose,
|
onClose,
|
||||||
onMutationSuccess,
|
onMutationSuccess,
|
||||||
profileId,
|
profileId,
|
||||||
}: {
|
}: {
|
||||||
error: unknown;
|
|
||||||
isAdmin: boolean;
|
isAdmin: boolean;
|
||||||
isLoading: boolean;
|
issueId: number | null;
|
||||||
issue: IssueRecord | null;
|
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
onMutationSuccess: () => void;
|
onMutationSuccess: () => void;
|
||||||
profileId: number;
|
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 [adminResponse, setAdminResponse] = useState('');
|
||||||
const isOpen = Boolean(issue || isLoading || error);
|
const isOpen = Boolean(issueId || queryLoading || queryError);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setAdminResponse(issue?.admin_response || '');
|
setAdminResponse(issue?.admin_response || '');
|
||||||
|
|
@ -171,7 +174,7 @@ export function IssueDetailModal({
|
||||||
return null;
|
return null;
|
||||||
}, [adminResponse, deleteMutation, isAdmin, issue, updateMutation]);
|
}, [adminResponse, deleteMutation, isAdmin, issue, updateMutation]);
|
||||||
|
|
||||||
if (!issue && !isLoading && !error) {
|
if (!issue && !queryLoading && !queryError) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -210,16 +213,16 @@ export function IssueDetailModal({
|
||||||
closeLabel="Close issue detail"
|
closeLabel="Close issue detail"
|
||||||
/>
|
/>
|
||||||
<DialogBody>
|
<DialogBody>
|
||||||
{isLoading ? (
|
{queryLoading ? (
|
||||||
<div className={styles.issuesLoading}>
|
<div className={styles.issuesLoading}>
|
||||||
<div className={styles.issuesSpinner} />
|
<div className={styles.issuesSpinner} />
|
||||||
Loading issue details...
|
Loading issue details...
|
||||||
</div>
|
</div>
|
||||||
) : error ? (
|
) : queryError ? (
|
||||||
<div className={styles.issuesEmpty}>
|
<div className={styles.issuesEmpty}>
|
||||||
<div className={styles.issuesEmptyTitle}>Failed to load issue</div>
|
<div className={styles.issuesEmptyTitle}>Failed to load issue</div>
|
||||||
<div className={styles.issuesEmptyText}>
|
<div className={styles.issuesEmptyText}>
|
||||||
{error instanceof Error ? error.message : 'Unknown error'}
|
{queryError instanceof Error ? queryError.message : 'Unknown error'}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : issue ? (
|
) : issue ? (
|
||||||
|
|
@ -414,7 +417,7 @@ export function IssueDetailModal({
|
||||||
<Button className={styles.modalButtonSecondary} type="button" onClick={onClose}>
|
<Button className={styles.modalButtonSecondary} type="button" onClick={onClose}>
|
||||||
Close
|
Close
|
||||||
</Button>
|
</Button>
|
||||||
{!isLoading && !error && issue && (
|
{issue && (
|
||||||
<>
|
<>
|
||||||
{statusButtons}
|
{statusButtons}
|
||||||
{isAdmin && (
|
{isAdmin && (
|
||||||
|
|
|
||||||
|
|
@ -8,11 +8,7 @@ import { useReactPageShell } from '@/platform/shell/route-controllers';
|
||||||
|
|
||||||
import type { IssueCounts, IssueRecord, IssueStatus } from '../-issues.types';
|
import type { IssueCounts, IssueRecord, IssueStatus } from '../-issues.types';
|
||||||
|
|
||||||
import {
|
import { issueCountsQueryOptions, issueListQueryOptions } from '../-issues.api';
|
||||||
issueCountsQueryOptions,
|
|
||||||
issueDetailQueryOptions,
|
|
||||||
issueListQueryOptions,
|
|
||||||
} from '../-issues.api';
|
|
||||||
import {
|
import {
|
||||||
CLOSE_EVENT,
|
CLOSE_EVENT,
|
||||||
REFRESH_EVENT,
|
REFRESH_EVENT,
|
||||||
|
|
@ -85,10 +81,6 @@ export function IssuesPage() {
|
||||||
...issueListQueryOptions(profileId, normalizedSearch),
|
...issueListQueryOptions(profileId, normalizedSearch),
|
||||||
enabled: profileId > 0,
|
enabled: profileId > 0,
|
||||||
});
|
});
|
||||||
const selectedIssueQuery = useQuery({
|
|
||||||
...issueDetailQueryOptions(profileId, selectedIssueId ?? 0),
|
|
||||||
enabled: profileId > 0 && selectedIssueId !== null,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!bridge || !profile || !bridge.isPageAllowed('issues')) {
|
if (!bridge || !profile || !bridge.isPageAllowed('issues')) {
|
||||||
return null;
|
return null;
|
||||||
|
|
@ -130,9 +122,7 @@ export function IssuesPage() {
|
||||||
/>
|
/>
|
||||||
<IssueDetailModal
|
<IssueDetailModal
|
||||||
isAdmin={profile.isAdmin}
|
isAdmin={profile.isAdmin}
|
||||||
issue={selectedIssueQuery.data ?? null}
|
issueId={selectedIssueId}
|
||||||
isLoading={selectedIssueQuery.isLoading}
|
|
||||||
error={selectedIssueQuery.error}
|
|
||||||
onClose={() => clearIssueSelection(navigate)}
|
onClose={() => clearIssueSelection(navigate)}
|
||||||
onMutationSuccess={() => {
|
onMutationSuccess={() => {
|
||||||
clearIssueSelection(navigate);
|
clearIssueSelection(navigate);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue