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
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,13 @@
*/

import { useState, useCallback, useMemo } from 'react';
import type * as Y from 'yjs';
import { InfoIcon } from 'lucide-react';
import { AMSTAR_CHECKLIST } from './checklist-map';
import { createChecklist as createAMSTAR2Checklist } from './checklist.js';
import { Tooltip, TooltipTrigger, TooltipContent } from '@/components/ui/tooltip';
import { NoteEditor } from '@/components/checklist/common/NoteEditor';
import type { TextRef } from '@/primitives/useProject/checklists';

// -- Shared internal components --

Expand Down Expand Up @@ -130,28 +132,27 @@ function StandardQuestion({
question,
handleChange,
onUpdate,
getQuestionNote,
getTextRef,
readOnly,
width,
}: {
state: any;
question: any;
handleChange: (_colIdx: number, _optIdx: number) => void;
onUpdate: (_newState: any) => void;
getQuestionNote?: (_questionKey: string) => any;
getTextRef: (_ref: TextRef) => Y.Text | null;
readOnly?: boolean;
width?: string;
}) {
const questionKey = useMemo(() => {
const text = question?.text || '';
const match = text.match(/^(\d+[a-z]?)\./);
return match ? `q${match[1]}` : null;
const match = question.text.match(/^(\d+[a-z]?)\./);
return `q${match[1]}`;
}, [question]);

const noteYText = useMemo(() => {
if (!questionKey || !getQuestionNote) return null;
return getQuestionNote(questionKey);
}, [questionKey, getQuestionNote]);
const noteYText = useMemo(
() => getTextRef({ type: 'AMSTAR2', questionKey }),
[questionKey, getTextRef],
);

return (
<div className='bg-card relative rounded-lg p-7 pb-3 shadow-md'>
Expand All @@ -167,7 +168,7 @@ function StandardQuestion({
handleChange={handleChange}
width={width}
/>
{getQuestionNote && <NoteEditor yText={noteYText} readOnly={readOnly} collapsed={true} />}
<NoteEditor yText={noteYText} readOnly={readOnly} collapsed={true} />
</div>
);
}
Expand Down Expand Up @@ -344,12 +345,12 @@ const QUESTION_CONFIGS: QuestionConfig[] = [
function Question9({
checklist,
onUpdate,
getQuestionNote,
getTextRef,
readOnly,
}: {
checklist: any;
onUpdate: (_patch: Record<string, any>) => void;
getQuestionNote?: (_key: string) => any;
getTextRef: (_ref: TextRef) => Y.Text | null;
readOnly?: boolean;
}) {
const stateA = checklist.q9a;
Expand Down Expand Up @@ -416,10 +417,7 @@ function Question9({
[stateA, stateB, onUpdate],
);

const noteYText = useMemo(
() => (getQuestionNote ? getQuestionNote('q9') : null),
[getQuestionNote],
);
const noteYText = useMemo(() => getTextRef({ type: 'AMSTAR2', questionKey: 'q9' }), [getTextRef]);

return (
<div className='bg-card relative rounded-lg p-7 pb-3 text-sm shadow-md'>
Expand All @@ -442,7 +440,7 @@ function Question9({
columns={question.columns2}
handleChange={handleChangeB}
/>
{getQuestionNote && <NoteEditor yText={noteYText} readOnly={readOnly} collapsed={true} />}
<NoteEditor yText={noteYText} readOnly={readOnly} collapsed={true} />
</div>
);
}
Expand All @@ -451,12 +449,12 @@ function Question9({
function Question11({
checklist,
onUpdate,
getQuestionNote,
getTextRef,
readOnly,
}: {
checklist: any;
onUpdate: (_patch: Record<string, any>) => void;
getQuestionNote?: (_key: string) => any;
getTextRef: (_ref: TextRef) => Y.Text | null;
readOnly?: boolean;
}) {
const stateA = checklist.q11a;
Expand Down Expand Up @@ -519,8 +517,8 @@ function Question11({
);

const noteYText = useMemo(
() => (getQuestionNote ? getQuestionNote('q11') : null),
[getQuestionNote],
() => getTextRef({ type: 'AMSTAR2', questionKey: 'q11' }),
[getTextRef],
);

return (
Expand All @@ -546,7 +544,7 @@ function Question11({
handleChange={handleChangeB}
width='w-48'
/>
{getQuestionNote && <NoteEditor yText={noteYText} readOnly={readOnly} collapsed={true} />}
<NoteEditor yText={noteYText} readOnly={readOnly} collapsed={true} />
</div>
);
}
Expand All @@ -557,14 +555,14 @@ interface AMSTAR2ChecklistProps {
externalChecklist?: any;
onExternalUpdate?: (_patch: Record<string, any>) => void;
readOnly?: boolean;
getQuestionNote?: (_questionKey: string) => any;
getTextRef: (_ref: TextRef) => Y.Text | null;
}

export function AMSTAR2Checklist({
externalChecklist,
onExternalUpdate,
readOnly,
getQuestionNote,
getTextRef,
}: AMSTAR2ChecklistProps) {
// Local fallback state for standalone mode (no Yjs)
const [localChecklist, setLocalChecklist] = useState<any>(() => {
Expand Down Expand Up @@ -621,7 +619,7 @@ export function AMSTAR2Checklist({
handleChecklistChange({ q1: { ...state, answers: newAnswers } });
}}
onUpdate={(newQ: any) => handleChecklistChange({ q1: newQ })}
getQuestionNote={getQuestionNote}
getTextRef={getTextRef}
readOnly={readOnly}
/>

Expand All @@ -636,7 +634,7 @@ export function AMSTAR2Checklist({
handleChecklistChange({ [cfg.qKey]: newQ });
}}
onUpdate={(newQ: any) => handleChecklistChange({ [cfg.qKey]: newQ })}
getQuestionNote={getQuestionNote}
getTextRef={getTextRef}
readOnly={readOnly}
width={cfg.width}
/>
Expand All @@ -646,7 +644,7 @@ export function AMSTAR2Checklist({
<Question9
checklist={checklist}
onUpdate={handleChecklistChange}
getQuestionNote={getQuestionNote}
getTextRef={getTextRef}
readOnly={readOnly}
/>

Expand All @@ -659,15 +657,15 @@ export function AMSTAR2Checklist({
handleChecklistChange({ q10: newQ });
}}
onUpdate={(newQ: any) => handleChecklistChange({ q10: newQ })}
getQuestionNote={getQuestionNote}
getTextRef={getTextRef}
readOnly={readOnly}
/>

{/* Q11: split question */}
<Question11
checklist={checklist}
onUpdate={handleChecklistChange}
getQuestionNote={getQuestionNote}
getTextRef={getTextRef}
readOnly={readOnly}
/>

Expand All @@ -682,7 +680,7 @@ export function AMSTAR2Checklist({
handleChecklistChange({ [cfg.qKey]: newQ });
}}
onUpdate={(newQ: any) => handleChecklistChange({ [cfg.qKey]: newQ })}
getQuestionNote={getQuestionNote}
getTextRef={getTextRef}
readOnly={readOnly}
width={cfg.width}
/>
Expand Down
16 changes: 6 additions & 10 deletions packages/web/src/components/checklist/ChecklistWithPdf.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@
*/

import { lazy, Suspense } from 'react';
import type * as Y from 'yjs';
import { GenericChecklist } from '@/components/checklist/GenericChecklist';
import { SplitScreenLayout } from '@/components/checklist/SplitScreenLayout';
import type { TextRef } from '@/primitives/useProject/checklists';

const EmbedPdfViewer = lazy(() => import('@/components/pdf/EmbedPdfViewer'));

Expand All @@ -22,9 +24,7 @@ interface ChecklistWithPdfProps {
pdfs?: any[];
selectedPdfId?: string | null;
onPdfSelect?: (_pdfId: string) => void;
getQuestionNote?: (_questionKey: string) => any;
getRobinsText?: (_sectionKey: string, _fieldKey: string, _questionKey?: string) => any;
getRob2Text?: (_sectionKey: string, _fieldKey: string, _questionKey?: string) => any;
getTextRef: (_ref: TextRef) => Y.Text | null;
pdfUrl?: string | null;
onAnnotationAdd?: (_annotation: any) => void;
onAnnotationUpdate?: (_annotation: any) => void;
Expand All @@ -46,9 +46,7 @@ export function ChecklistWithPdf({
pdfs,
selectedPdfId,
onPdfSelect,
getQuestionNote,
getRobinsText,
getRob2Text,
getTextRef,
pdfUrl,
onAnnotationAdd,
onAnnotationUpdate,
Expand All @@ -68,15 +66,13 @@ export function ChecklistWithPdf({
pdfUrl={pdfUrl}
pdfData={pdfData}
>
{/* First panel: Checklist (type-aware) */}
{/* First panel: Checklist */}
<GenericChecklist
checklistType={checklistType}
checklist={checklist}
onUpdate={onUpdate}
readOnly={readOnly}
getQuestionNote={getQuestionNote}
getRobinsText={getRobinsText}
getRob2Text={getRob2Text}
getTextRef={getTextRef}
/>

{/* Second panel: PDF Viewer */}
Expand Down
11 changes: 2 additions & 9 deletions packages/web/src/components/checklist/ChecklistYjsWrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,7 @@ export function ChecklistYjsWrapper({ projectId, studyId, checklistId }: Checkli

const ops = connectionPool.getOps(projectId);
if (!ops) throw new Error(`No connection for project ${projectId}`);
const { updateChecklistAnswer, updateChecklist, getQuestionNote, getRobinsText, getRob2Text } =
ops.checklist;
const { updateChecklistAnswer, updateChecklist, getTextRef } = ops.checklist;
const { addPdfToStudy } = ops.pdf;
const { addAnnotation, updateAnnotation, deleteAnnotation } = ops.annotation;

Expand Down Expand Up @@ -439,13 +438,7 @@ export function ChecklistYjsWrapper({ projectId, studyId, checklistId }: Checkli
pdfs={studyPdfs}
selectedPdfId={selectedPdfId}
onPdfSelect={handlePdfSelect}
getQuestionNote={(questionKey: string) => getQuestionNote(studyId, checklistId, questionKey)}
getRobinsText={(sectionKey: string, fieldKey: string, questionKey?: string) =>
getRobinsText(studyId, checklistId, sectionKey, fieldKey, questionKey)
}
getRob2Text={(sectionKey: string, fieldKey: string, questionKey?: string) =>
getRob2Text(studyId, checklistId, sectionKey, fieldKey, questionKey)
}
getTextRef={ref => getTextRef(studyId, checklistId, ref)}
onAnnotationAdd={handleAnnotationAdd}
onAnnotationUpdate={handleAnnotationUpdate}
onAnnotationDelete={handleAnnotationDelete}
Expand Down
16 changes: 7 additions & 9 deletions packages/web/src/components/checklist/GenericChecklist.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
*/

import { useMemo } from 'react';
import type * as Y from 'yjs';
import {
getChecklistTypeFromState,
DEFAULT_CHECKLIST_TYPE,
Expand All @@ -14,25 +15,22 @@ import {
import { AMSTAR2Checklist } from '@/components/checklist/AMSTAR2Checklist/AMSTAR2Checklist';
import { ROBINSIChecklist } from '@/components/checklist/ROBINSIChecklist/ROBINSIChecklist';
import { ROB2Checklist } from '@/components/checklist/ROB2Checklist/ROB2Checklist';
import type { TextRef } from '@/primitives/useProject/checklists';

interface GenericChecklistProps {
checklistType?: string;
checklist: any;
onUpdate: (_patch: Record<string, any>) => void;
readOnly?: boolean;
getQuestionNote?: (_questionKey: string) => any;
getRobinsText?: (_sectionKey: string, _fieldKey: string, _questionKey?: string) => any;
getRob2Text?: (_sectionKey: string, _fieldKey: string, _questionKey?: string) => any;
getTextRef: (_ref: TextRef) => Y.Text | null;
}

export function GenericChecklist({
checklistType: checklistTypeProp,
checklist,
onUpdate,
readOnly,
getQuestionNote,
getRobinsText,
getRob2Text,
getTextRef,
}: GenericChecklistProps) {
const checklistType = useMemo(() => {
if (checklistTypeProp) return checklistTypeProp;
Expand All @@ -47,7 +45,7 @@ export function GenericChecklist({
externalChecklist={checklist}
onExternalUpdate={onUpdate}
readOnly={readOnly}
getQuestionNote={getQuestionNote}
getTextRef={getTextRef}
/>
)}
{checklistType === CHECKLIST_TYPES.ROBINS_I && (
Expand All @@ -57,7 +55,7 @@ export function GenericChecklist({
showComments={true}
showLegend={true}
readOnly={readOnly}
getRobinsText={getRobinsText}
getTextRef={getTextRef}
/>
)}
{checklistType === CHECKLIST_TYPES.ROB2 && (
Expand All @@ -67,7 +65,7 @@ export function GenericChecklist({
showComments={true}
showLegend={true}
readOnly={readOnly}
getRob2Text={getRob2Text}
getTextRef={getTextRef}
/>
)}
</div>
Expand Down
Loading
Loading