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
6 changes: 4 additions & 2 deletions packages/ui/src/components/FileUpload.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,11 +69,12 @@ const FileUploadComponent: Component<FileUploadProps> = props => {
const { isDraggingOverWindow } = useWindowDrag();

const handleFileChange = (details: { acceptedFiles: File[] }) => {
// Pass files to callback
merged.onFilesChange?.(details.acceptedFiles);
};

const handleFileAccept = (_details: { files: File[] }) => {
merged.onFileAccept?.(_details);
const handleFileAccept = (details: { files: File[] }) => {
merged.onFileAccept?.(details);
};

const handleFileReject = (details: { files: unknown[] }) => {
Expand All @@ -86,6 +87,7 @@ const FileUploadComponent: Component<FileUploadProps> = props => {
maxFiles={merged.multiple ? 100 : 1}
directory={allowDirs()}
disabled={merged.disabled}
acceptedFiles={[]}
onFileChange={handleFileChange}
onFileAccept={handleFileAccept}
onFileReject={handleFileReject}
Expand Down
3 changes: 1 addition & 2 deletions packages/web/src/components/charts/AMSTARDistribution.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,7 @@ export default function AMSTARDistribution(props) {

const width = () => props.width ?? containerSize().width;
const height = () => props.height ?? containerSize().width / 1.5;
const title = () =>
props.title ?? 'Level Judgments Across Included Reviews';
const title = () => props.title ?? 'Level Judgments Across Included Reviews';
const greyscale = () => props.greyscale ?? false;

const margin = { top: 50, right: 150, bottom: 60, left: 80 };
Expand Down

This file was deleted.

47 changes: 22 additions & 25 deletions packages/web/src/components/project/add-studies/AddStudiesForm.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import { Tabs, showToast } from '@corates/ui';
import projectStore from '@/stores/projectStore.js';

import { useAddStudies } from '@primitives/useAddStudies.js';
import { AddStudiesProvider } from './AddStudiesContext.jsx';
import PdfUploadSection from './PdfUploadSection.jsx';
import ReferenceImportSection from './ReferenceImportSection.jsx';
import DoiLookupSection from './DoiLookupSection.jsx';
Expand Down Expand Up @@ -249,30 +248,28 @@ export default function AddStudiesForm(props) {
}))}
/>

<AddStudiesProvider
studies={studies}
formType={props.formType}
projectId={props.projectId}
onSaveFormState={handleSaveFormState}
>
<div class='mt-4'>
<Show when={activeTab() === 'pdfs'}>
<PdfUploadSection />
</Show>

<Show when={activeTab() === 'references'}>
<ReferenceImportSection />
</Show>

<Show when={activeTab() === 'lookup'}>
<DoiLookupSection />
</Show>

<Show when={activeTab() === 'drive'}>
<GoogleDriveSection />
</Show>
</div>
</AddStudiesProvider>
<div class='mt-4'>
<Show when={activeTab() === 'pdfs'}>
<PdfUploadSection studies={studies} />
</Show>

<Show when={activeTab() === 'references'}>
<ReferenceImportSection studies={studies} onSaveFormState={handleSaveFormState} />
</Show>

<Show when={activeTab() === 'lookup'}>
<DoiLookupSection studies={studies} onSaveFormState={handleSaveFormState} />
</Show>

<Show when={activeTab() === 'drive'}>
<GoogleDriveSection
studies={studies}
formType={props.formType}
projectId={props.projectId}
onSaveFormState={handleSaveFormState}
/>
</Show>
</div>

{/* Summary and Actions - hidden in collect mode since parent handles submission */}
<Show when={studies.totalStudyCount() > 0 && !props.collectMode}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,21 @@ import {
import { FiFile, FiFileText, FiAlertCircle, FiCheck, FiDownload } from 'solid-icons/fi';
import { Checkbox, Tooltip, showToast } from '@corates/ui';
import { getRefDisplayName } from '@/lib/referenceParser.js';
import { useStudiesContext } from './AddStudiesContext.jsx';

export default function DoiLookupSection() {
const studies = useStudiesContext();
export default function DoiLookupSection(props) {
const studies = () => props.studies;

// Count refs with PDFs available
const refsWithPdf = createMemo(() => studies.lookupRefs().filter(r => r.pdfAvailable));
const refsWithoutPdf = createMemo(() => studies.lookupRefs().filter(r => !r.pdfAvailable));

const refsWithPdf = createMemo(() =>
studies()
.lookupRefs()
.filter(r => r.pdfAvailable),
);
const refsWithoutPdf = createMemo(() =>
studies()
.lookupRefs()
.filter(r => !r.pdfAvailable),
);
return (
<div class='space-y-3'>
<p class='text-sm text-gray-500'>
Expand All @@ -34,19 +40,19 @@ export default function DoiLookupSection() {
<div class='space-y-2'>
<textarea
placeholder='10.1000/xyz123&#10;32615397&#10;10.1016/j.example.2023.01.001'
value={studies.identifierInput()}
onInput={e => studies.setIdentifierInput(e.target.value)}
value={studies().identifierInput()}
onInput={e => studies().setIdentifierInput(e.target.value)}
rows='4'
class='w-full rounded-lg border border-gray-300 px-3 py-2 font-mono text-sm text-gray-900 placeholder-gray-400 transition focus:border-transparent focus:ring-2 focus:ring-blue-500 focus:outline-none'
/>
<button
type='button'
onClick={() => studies.handleLookup()}
disabled={studies.lookingUp() || !studies.identifierInput().trim()}
onClick={() => studies().handleLookup()}
disabled={studies().lookingUp() || !studies().identifierInput().trim()}
class='inline-flex items-center gap-2 rounded-lg bg-blue-600 px-3 py-2 text-sm font-medium text-white transition-colors hover:bg-blue-700 focus:ring-2 focus:ring-blue-500 focus:outline-none disabled:cursor-not-allowed disabled:opacity-50'
>
<Show
when={!studies.lookingUp()}
when={!studies().lookingUp()}
fallback={
<>
<div class='h-4 w-4 animate-spin rounded-full border-2 border-white border-t-transparent' />
Expand All @@ -60,11 +66,11 @@ export default function DoiLookupSection() {
</button>
</div>

<Show when={studies.lookupErrors().length > 0}>
<Show when={studies().lookupErrors().length > 0}>
<div class='rounded-lg border border-red-200 bg-red-50 p-3'>
<p class='mb-1 text-sm font-medium text-red-700'>Some lookups failed:</p>
<ul class='list-inside list-disc text-xs text-red-600'>
<For each={studies.lookupErrors()}>
<For each={studies().lookupErrors()}>
{err => (
<li>
<code class='font-mono'>{err.identifier}</code>: {err.error}
Expand All @@ -75,7 +81,7 @@ export default function DoiLookupSection() {
</div>
</Show>

<Show when={studies.lookupRefs().length > 0}>
<Show when={studies().lookupRefs().length > 0}>
<div class='space-y-2'>
<div class='flex items-center justify-between'>
<span class='text-sm text-gray-600'>
Expand All @@ -88,7 +94,7 @@ export default function DoiLookupSection() {
</span>
<button
type='button'
onClick={() => studies.clearLookupRefs()}
onClick={() => studies().clearLookupRefs()}
class='text-xs text-red-600 hover:text-red-700 hover:underline'
>
Clear all
Expand All @@ -98,13 +104,13 @@ export default function DoiLookupSection() {
<Show when={refsWithPdf().length > 0}>
<div class='flex items-center gap-2 border-b border-gray-200 pb-2'>
<Checkbox
checked={studies.selectedLookupIds().size === refsWithPdf().length}
checked={studies().selectedLookupIds().size === refsWithPdf().length}
indeterminate={
studies.selectedLookupIds().size > 0 &&
studies.selectedLookupIds().size < refsWithPdf().length
studies().selectedLookupIds().size > 0 &&
studies().selectedLookupIds().size < refsWithPdf().length
}
onChange={studies.toggleSelectAllLookup}
label={`Select all with PDF (${studies.selectedLookupIds().size}/${refsWithPdf().length})`}
onChange={studies().toggleSelectAllLookup}
label={`Select all with PDF (${studies().selectedLookupIds().size}/${refsWithPdf().length})`}
/>
</div>
</Show>
Expand All @@ -126,7 +132,7 @@ export default function DoiLookupSection() {

try {
const arrayBuffer = await file.arrayBuffer();
studies.attachPdfToLookupRef?.(ref._id, file.name, arrayBuffer);
studies().attachPdfToLookupRef?.(ref._id, file.name, arrayBuffer);
showToast.success('PDF Attached', `Attached ${file.name}`);
} catch (err) {
const { handleError } = await import('@/lib/error-utils.js');
Expand All @@ -141,11 +147,11 @@ export default function DoiLookupSection() {
return (
<div
class={`flex cursor-pointer items-start gap-3 rounded-lg p-2 transition-colors ${
studies.selectedLookupIds().has(ref._id) ?
studies().selectedLookupIds().has(ref._id) ?
'border border-green-200 bg-green-50 hover:bg-green-100'
: 'border border-transparent bg-gray-50 hover:bg-gray-100'
}`}
onClick={() => studies.toggleLookupSelection(ref._id)}
onClick={() => studies().toggleLookupSelection(ref._id)}
>
{/* Hidden file input for manual PDF upload */}
<input
Expand All @@ -156,8 +162,8 @@ export default function DoiLookupSection() {
onChange={handleManualPdfSelect}
/>
<Checkbox
checked={studies.selectedLookupIds().has(ref._id)}
onChange={() => studies.toggleLookupSelection(ref._id)}
checked={studies().selectedLookupIds().has(ref._id)}
onChange={() => studies().toggleLookupSelection(ref._id)}
class='mt-0.5'
/>
<div class='min-w-0 flex-1'>
Expand Down Expand Up @@ -250,7 +256,7 @@ export default function DoiLookupSection() {
type='button'
onClick={e => {
e.stopPropagation();
studies.removeLookupRef(ref._id);
studies().removeLookupRef(ref._id);
}}
class='rounded p-1 text-gray-400 transition-colors hover:bg-red-50 hover:text-red-600 focus:ring-2 focus:ring-blue-500 focus:outline-none'
>
Expand Down Expand Up @@ -302,7 +308,7 @@ export default function DoiLookupSection() {
type='button'
onClick={e => {
e.stopPropagation();
studies.removeLookupRef(ref._id);
studies().removeLookupRef(ref._id);
}}
class='rounded p-1 text-gray-400 transition-colors hover:bg-red-50 hover:text-red-600 focus:ring-2 focus:ring-blue-500 focus:outline-none'
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,18 @@ import { BiRegularTrash } from 'solid-icons/bi';
import { FiFile } from 'solid-icons/fi';
import { formatFileSize } from '@/api/google-drive.js';
import GoogleDrivePickerLauncher from '../google-drive/GoogleDrivePickerLauncher.jsx';
import { useStudiesContext, useFormPersistenceContext } from './AddStudiesContext.jsx';

export default function GoogleDriveSection() {
const studies = useStudiesContext();
const { formType, projectId, onSaveFormState } = useFormPersistenceContext();
export default function GoogleDriveSection(props) {
const studies = () => props.studies;
const onSaveFormState = () => props.onSaveFormState;

const isFileSelected = fileId => {
return studies.selectedDriveFiles().some(f => f.id === fileId);
return studies()
.selectedDriveFiles()
.some(f => f.id === fileId);
};

const selectedCount = () => studies.selectedDriveFiles().length;
const selectedCount = () => studies().selectedDriveFiles().length;

return (
<div class='space-y-3'>
Expand All @@ -35,14 +36,14 @@ export default function GoogleDriveSection() {
</span>
<button
type='button'
onClick={() => studies.clearDriveFiles()}
onClick={() => studies().clearDriveFiles()}
class='text-xs text-gray-500 transition-colors hover:text-red-600'
>
Clear all
</button>
</div>
<div class='max-h-40 space-y-2 overflow-y-auto'>
<For each={studies.selectedDriveFiles()}>
<For each={studies().selectedDriveFiles()}>
{file => (
<div class='flex items-center gap-3 rounded-lg border border-blue-200 bg-blue-50 p-2'>
<FiFile class='h-4 w-4 shrink-0 text-red-600' />
Expand All @@ -52,7 +53,7 @@ export default function GoogleDriveSection() {
</div>
<button
type='button'
onClick={() => studies.removeDriveFile(file.id)}
onClick={() => studies().removeDriveFile(file.id)}
class='rounded p-1 text-gray-400 transition-colors hover:bg-red-50 hover:text-red-600 focus:ring-2 focus:ring-blue-500 focus:outline-none'
>
<BiRegularTrash class='h-4 w-4' />
Expand All @@ -67,13 +68,13 @@ export default function GoogleDriveSection() {
<GoogleDrivePickerLauncher
active={true}
multiselect={true}
formType={formType}
projectId={projectId}
onSaveFormState={onSaveFormState}
formType={props.formType}
projectId={props.projectId}
onSaveFormState={onSaveFormState()}
onPick={picked => {
for (const file of picked) {
if (!isFileSelected(file.id)) {
studies.toggleDriveFile({ id: file.id, name: file.name });
studies().toggleDriveFile({ id: file.id, name: file.name });
}
}
}}
Expand Down
Loading