Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/components/modals/DialogFrame.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ export function DialogFrame({
>
<div
className={cn(
'border-b border-neutral-200 px-6 py-4 dark:border-[#2d3540]',
'shrink-0 border-b border-neutral-200 px-6 py-4 dark:border-[#2d3540]',
headerClassName
)}
>
Expand Down Expand Up @@ -166,7 +166,7 @@ export function DialogFrame({
{footer && (
<div
className={cn(
'flex flex-wrap items-center justify-end gap-2 border-t border-neutral-200 bg-neutral-50 px-6 py-4',
'flex shrink-0 flex-wrap items-center justify-end gap-2 border-t border-neutral-200 bg-neutral-50 px-6 py-4',
'dark:border-[#2d3540] dark:bg-[#1e252e]',
footerClassName
)}
Expand Down
136 changes: 136 additions & 0 deletions src/components/modals/ModelViewDialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
import { useState, type ReactNode } from 'react';
import { Database, ExternalLink, Maximize2, Minimize2 } from 'lucide-react';
import { DialogFrame, type DialogFrameSize } from '@/components/modals/DialogFrame';
import { ImageWithFallback } from '@/components/ui/ImageWithFallback';
import { cn } from '@/utils/cn';

export interface ModelViewDialogProps {
isOpen: boolean;
onClose: () => void;
schemaUrl: string;
title: ReactNode;
description?: ReactNode;
icon?: ReactNode;
size?: DialogFrameSize;
sourceLabel?: string;
previewSummary?: ReactNode;
backgroundNotice?: ReactNode;
}

export function ModelViewDialog({
isOpen,
onClose,
schemaUrl,
title,
description,
icon,
size = 'full',
sourceLabel = 'Open source',
previewSummary = 'SVG preview of the current backend schema.',
backgroundNotice = 'Some hosts block embedded frame previews. This dialog uses image preview mode for compatibility.',
}: ModelViewDialogProps) {
// Schema diagrams range from ~520px to ~1830px wide, so the preview fits the
// stage by default and only scrolls at natural size when the user opts in.
const [isActualSize, setIsActualSize] = useState(false);

const handleClose = () => {
setIsActualSize(false);
onClose();
};

const altText = typeof title === 'string' ? title : 'Backend model schema';

return (
<DialogFrame
isOpen={isOpen}
onClose={handleClose}
title={title}
description={description}
icon={icon ?? <Database className='h-5 w-5' />}
size={size}
// Bound the panel to the viewport (the frame adds p-4 around it) and lay it
// out as a column so only the preview stage absorbs the leftover height.
panelClassName='flex max-h-[calc(100dvh-2rem)] flex-col'
bodyClassName='flex min-h-0 flex-col space-y-0 p-0'
>
<div className='flex shrink-0 items-center justify-between gap-3 border-b border-neutral-200 bg-neutral-50 px-4 py-3 dark:border-[#2d3540] dark:bg-[#1e252e]'>
<p className='min-w-0 truncate text-sm text-slate-600 dark:text-[#9dabb9]'>
{previewSummary}
</p>

<div className='flex shrink-0 items-center gap-3'>
<button
type='button'
onClick={() => setIsActualSize((current) => !current)}
aria-pressed={isActualSize}
className={cn(
'inline-flex items-center gap-1.5 rounded-md border border-neutral-200 px-2 py-1',
'text-xs font-medium text-slate-600 transition-colors',
'hover:bg-neutral-100 hover:text-slate-900',
'focus:ring-2 focus:ring-blue-500 focus:outline-none',
'dark:border-[#2d3540] dark:text-[#9dabb9] dark:hover:bg-[#111418] dark:hover:text-slate-100'
)}
>
{isActualSize ? (
<Minimize2 className='h-3.5 w-3.5' />
) : (
<Maximize2 className='h-3.5 w-3.5' />
)}
{isActualSize ? 'Fit to window' : 'Actual size'}
</button>

<a
href={schemaUrl}
target='_blank'
rel='noopener noreferrer'
className='inline-flex items-center gap-1 text-sm font-medium text-blue-600 hover:underline dark:text-blue-400'
>
{sourceLabel}
<ExternalLink className='h-4 w-4' />
</a>
</div>
</div>

{/* basis-[70vh] is the preferred stage height; min-h-0 lets it shrink
instead of pushing the panel past its max-height on short viewports. */}
<div
className={cn(
'relative min-h-0 basis-[70vh] bg-white dark:bg-[#111418]',
isActualSize ? 'overflow-auto' : 'overflow-hidden'
)}
>
{isActualSize ? (
// min-w-max keeps the centred diagram from overflowing past the
// scroll container's start edge, which would make it unreachable.
<a
href={schemaUrl}
target='_blank'
rel='noopener noreferrer'
className='flex min-h-full min-w-max items-center justify-center p-3'
>
<ImageWithFallback src={schemaUrl} alt={altText} className='block max-w-none' />
</a>
) : (
// Absolute inset-0 gives the image a definite box in both axes, so
// object-contain can scale the diagram down to fit rather than clip.
<a
href={schemaUrl}
target='_blank'
rel='noopener noreferrer'
className='absolute inset-0 flex items-center justify-center p-3'
>
<ImageWithFallback
src={schemaUrl}
alt={altText}
className='block h-full w-full object-contain'
/>
</a>
)}
</div>

<div className='shrink-0 border-t border-neutral-200 bg-neutral-50 px-4 py-2 dark:border-[#2d3540] dark:bg-[#1e252e]'>
<p className='text-xs text-slate-500 dark:text-[#9dabb9]'>{backgroundNotice}</p>
</div>
</DialogFrame>
);
}
27 changes: 26 additions & 1 deletion src/features/cases/components/CasesInfoDrawer.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { useState } from 'react';
import { Briefcase, FolderSync } from 'lucide-react';
import { Briefcase, Database, FolderSync } from 'lucide-react';
import { DrawerFrame } from '@/components/modals/DrawerFrame';
import { InfoDrawerActionCard } from '@/components/modals/InfoDrawerActionCard';
import { ModelViewDialog } from '@/components/modals/ModelViewDialog';
import { formatTableDate } from '@/utils/timeFormat';
import { useCaseSyncFromRedcapAutoHistoryModel } from '../api/cases.api';
// Manual case creation is temporarily unavailable because the Case Manager API
Expand All @@ -16,12 +17,16 @@ interface CasesInfoDrawerProps {
onClose: () => void;
}

const CASE_MANAGER_SCHEMA_SVG_URL =
'https://raw.githubusercontent.com/OrcaBus/service-case-manager/refs/heads/main/case-manager/docs/schema.drawio.svg';

export function CasesInfoDrawer({ isOpen, onClose }: CasesInfoDrawerProps) {
// Manual case creation is temporarily unavailable. Preserve this state for
// restoration alongside AddCaseModal when POST /case/ returns.
// const [showAddCaseModal, setShowAddCaseModal] = useState(false);
const [showAutoImportModal, setShowAutoImportModal] = useState(false);
const [showSyncHistoryModal, setShowSyncHistoryModal] = useState(false);
const [showSchemaPreviewModal, setShowSchemaPreviewModal] = useState(false);

const {
data: syncHistoryData,
Expand Down Expand Up @@ -78,6 +83,15 @@ export function CasesInfoDrawer({ isOpen, onClose }: CasesInfoDrawerProps) {
buttonIcon={<FolderSync className='h-4 w-4' />}
variant='secondary'
/>
<InfoDrawerActionCard
title='Case Model View'
description='Preview the Case Manager model entity relationship diagram used to model case data.'
buttonLabel='Case Model View'
onClick={() => setShowSchemaPreviewModal(true)}
icon={<Database className='h-4 w-4' />}
buttonIcon={<Database className='h-4 w-4' />}
variant='secondary'
/>
{/* Manual case creation is temporarily unavailable because the Case Manager API
no longer exposes POST /case/. Restore this card with AddCaseModal when
collection create support returns.
Expand Down Expand Up @@ -105,6 +119,17 @@ export function CasesInfoDrawer({ isOpen, onClose }: CasesInfoDrawerProps) {
isOpen={showAutoImportModal}
onClose={() => setShowAutoImportModal(false)}
/>

<ModelViewDialog
isOpen={showSchemaPreviewModal}
onClose={() => setShowSchemaPreviewModal(false)}
schemaUrl={CASE_MANAGER_SCHEMA_SVG_URL}
title='Case Manager Entity Schema'
description='Case Manager model entity diagram from service-case-manager.'
icon={<Database className='h-5 w-5' />}
previewSummary='SVG preview of the current Case Manager model schema.'
backgroundNotice='This source is theme-aware, so dark mode can render a dark canvas background.'
/>
</DrawerFrame>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ const caseDetail: CaseDetailModel = {
isNataAccredited: true,
alias: ['CASE-ALIAS'],
links: {},
redcapPayload: {},
dueDate: '2026-08-31',
externalEntitySet: [],
userSet: [],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ const caseDetail: CaseDetailModel = {
isNataAccredited: false,
alias: ['CASE-ALIAS'],
links: { trello: 'https://example.com/card' },
redcapPayload: {},
dueDate: '2026-08-31',
externalEntitySet: [],
userSet: [],
Expand Down
30 changes: 28 additions & 2 deletions src/features/lab/root/components/LabInfoDrawer.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
import { useState } from 'react';
import { LibraryBig, RefreshCw } from 'lucide-react';
import { Database, LibraryBig, RefreshCw } from 'lucide-react';
import { DrawerFrame } from '@/components/modals/DrawerFrame';
import { InfoDrawerActionCard } from '@/components/modals/InfoDrawerActionCard';
import { ModelViewDialog } from '@/components/modals/ModelViewDialog';
import { SyncMetadataModal } from './SyncMetadataModal';

interface LabInfoDrawerProps {
isOpen: boolean;
onClose: () => void;
}

const METADATA_MANAGER_SCHEMA_SVG_URL =
'https://github.com/OrcaBus/service-metadata-manager/raw/refs/heads/main/metadata-manager/docs/schema.drawio.svg';

export function LabInfoDrawer({ isOpen, onClose }: LabInfoDrawerProps) {
const [showSyncModal, setShowSyncModal] = useState(false);
const [showModelViewModal, setShowModelViewModal] = useState(false);

return (
<>
Expand All @@ -32,7 +37,7 @@ export function LabInfoDrawer({ isOpen, onClose }: LabInfoDrawerProps) {

<section>
<h3 className='text-sm font-semibold text-slate-900 dark:text-white'>Actions</h3>
<div className='mt-3'>
<div className='mt-3 space-y-3'>
<InfoDrawerActionCard
title='Sync metadata'
description='Refresh lab metadata from upstream sources so library fields, phenotypes, and workflow links stay current.'
Expand All @@ -43,12 +48,33 @@ export function LabInfoDrawer({ isOpen, onClose }: LabInfoDrawerProps) {
icon={<LibraryBig className='h-4 w-4' />}
buttonIcon={<RefreshCw className='h-4 w-4' />}
/>

<InfoDrawerActionCard
title='Lab Model View'
description='Preview the Metadata Manager backend entity relationship diagram for lab metadata.'
buttonLabel='Lab Model View'
onClick={() => setShowModelViewModal(true)}
icon={<Database className='h-4 w-4' />}
buttonIcon={<Database className='h-4 w-4' />}
variant='secondary'
/>
</div>
</section>
</div>
</DrawerFrame>

<SyncMetadataModal isOpen={showSyncModal} onClose={() => setShowSyncModal(false)} />

<ModelViewDialog
isOpen={showModelViewModal}
onClose={() => setShowModelViewModal(false)}
schemaUrl={METADATA_MANAGER_SCHEMA_SVG_URL}
title='Metadata Manager Entity Schema'
description='Metadata Manager model entity diagram from service-metadata-manager.'
icon={<Database className='h-5 w-5' />}
previewSummary='SVG preview of the current Metadata Manager model schema.'
backgroundNotice='This source is theme-aware, so dark mode can render a dark canvas background.'
/>
</>
);
}
10 changes: 10 additions & 0 deletions src/features/runs/sequence-runs/pages/SequenceRunsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ import { buildSequenceRunsFilterBadges } from '../utils/buildSequenceRunsFilterB
import { SequenceRunsStatusCards } from '../components/SequenceRunsStatusCards';
import SequenceRunsTable from '../components/SequenceRunsTable';

const SEQUENCE_RUN_MANAGER_SCHEMA_SVG_URL =
'https://raw.githubusercontent.com/OrcaBus/service-sequence-run-manager/refs/heads/main/docs/diagram/sequence-run-manager-entity-diagram.drawio.svg';

export function SequenceRunsPage() {
const title = 'Sequence Runs';
const description = 'Monitor instrument runs and sequencing run status.';
Expand Down Expand Up @@ -134,6 +137,13 @@ export function SequenceRunsPage() {
onClose={closeInfoDrawer}
title={title}
description={description}
modelSchema={{
url: SEQUENCE_RUN_MANAGER_SCHEMA_SVG_URL,
title: 'Sequence Run Manager Entity Schema',
description:
'Sequence Run Manager model entity diagram from service-sequence-run-manager.',
previewSummary: 'SVG preview of the current Sequence Run Manager model schema.',
}}
/>
</>
);
Expand Down
Loading
Loading