Skip to content

Commit fc59f77

Browse files
committed
feat(web): add list templates to catalogue (task-23)
- Add ListTemplates component with 3 pre-configured templates: * Literature Review (bibliography) * Weekly Reading (list) * Citation Network (list) - Integrate templates into CatalogueManager with menu - Update CreateListModal to support initial values from templates - Fix useSearchHistory to use catalogueService instead of idb - Fix unused generic type parameter in useUndoRedo
1 parent 73f3bad commit fc59f77

File tree

5 files changed

+461
-123
lines changed

5 files changed

+461
-123
lines changed

apps/web/src/components/catalogue/CatalogueManager.tsx

Lines changed: 95 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
* Handles lists, bibliographies, and entity management
44
*/
55

6+
import type { ListType } from "@bibgraph/utils";
67
import { logger } from "@bibgraph/utils";
78
import { SPECIAL_LIST_IDS } from "@bibgraph/utils/storage/catalogue-db";
89
import {
@@ -11,6 +12,7 @@ import {
1112
Card,
1213
Container,
1314
Group,
15+
Menu,
1416
Modal,
1517
Paper,
1618
SimpleGrid,
@@ -24,6 +26,7 @@ import {
2426
import { useHotkeys } from "@mantine/hooks";
2527
import {
2628
IconBook,
29+
IconChevronDown,
2730
IconDatabase,
2831
IconDownload,
2932
IconEdit,
@@ -42,6 +45,8 @@ import { CatalogueListComponent } from "@/components/catalogue/CatalogueList";
4245
import { CreateListModal } from "@/components/catalogue/CreateListModal";
4346
import { ExportModal } from "@/components/catalogue/ExportModal";
4447
import { ImportModal } from "@/components/catalogue/ImportModal";
48+
import type { ListTemplate } from "@/components/catalogue/ListTemplates";
49+
import { ListTemplates } from "@/components/catalogue/ListTemplates";
4550
import { ShareModal } from "@/components/catalogue/ShareModal";
4651
import { BORDER_STYLE_GRAY_3, ICON_SIZE } from '@/config/style-constants';
4752
import { useCatalogueContext } from "@/contexts/catalogue-context";
@@ -71,6 +76,8 @@ export const CatalogueManager = ({ onNavigate, shareData, initialListId }: Catal
7176

7277
const [activeTab, setActiveTab] = useState<string | null>("lists");
7378
const [showCreateModal, setShowCreateModal] = useState(false);
79+
const [showTemplatesModal, setShowTemplatesModal] = useState(false);
80+
const [selectedTemplate, setSelectedTemplate] = useState<ListTemplate | null>(null);
7481
const [showShareModal, setShowShareModal] = useState(false);
7582
const [showImportModal, setShowImportModal] = useState(false);
7683
const [showExportModal, setShowExportModal] = useState(false);
@@ -197,6 +204,43 @@ export const CatalogueManager = ({ onNavigate, shareData, initialListId }: Catal
197204
}
198205
};
199206

207+
// Handle template selection
208+
const handleUseTemplate = (template: ListTemplate) => {
209+
setSelectedTemplate(template);
210+
setShowTemplatesModal(false);
211+
setShowCreateModal(true);
212+
};
213+
214+
// Handle create list from template or custom
215+
const handleCreateList = async (params: {
216+
title: string;
217+
description?: string;
218+
type: ListType;
219+
tags?: string[];
220+
isPublic?: boolean;
221+
}) => {
222+
// Merge template tags with user-provided tags (avoiding duplicates)
223+
const mergedTags = selectedTemplate
224+
? [...new Set([...selectedTemplate.tags, ...(params.tags || [])])]
225+
: params.tags;
226+
227+
const listId = await createList({
228+
...params,
229+
tags: mergedTags,
230+
});
231+
// Switch to the appropriate tab based on list type
232+
setActiveTab(params.type === "bibliography" ? "bibliographies" : "lists");
233+
selectList(listId);
234+
setSelectedTemplate(null);
235+
setShowCreateModal(false);
236+
};
237+
238+
// Reset template when closing create modal
239+
const handleCloseCreateModal = () => {
240+
setSelectedTemplate(null);
241+
setShowCreateModal(false);
242+
};
243+
200244
return (
201245
<Container size="xl" py="md" data-testid="catalogue-manager">
202246
<Stack gap="lg">
@@ -232,13 +276,36 @@ export const CatalogueManager = ({ onNavigate, shareData, initialListId }: Catal
232276
Share
233277
</Button>
234278

235-
<Button
236-
leftSection={<IconPlus size={ICON_SIZE.MD} />}
237-
onClick={() => setShowCreateModal(true)}
238-
aria-label="Open modal to create a new catalogue list"
279+
<Menu
280+
shadow="md"
281+
width={200}
282+
position="bottom-end"
239283
>
240-
Create New List
241-
</Button>
284+
<Menu.Target>
285+
<Button
286+
leftSection={<IconPlus size={ICON_SIZE.MD} />}
287+
rightSection={<IconChevronDown size={16} />}
288+
aria-label="Create new list or use templates"
289+
>
290+
Create New List
291+
</Button>
292+
</Menu.Target>
293+
294+
<Menu.Dropdown>
295+
<Menu.Item
296+
leftSection={<IconBook size={14} />}
297+
onClick={() => setShowTemplatesModal(true)}
298+
>
299+
Use Templates
300+
</Menu.Item>
301+
<Menu.Item
302+
leftSection={<IconPlus size={14} />}
303+
onClick={() => setShowCreateModal(true)}
304+
>
305+
Create Custom List
306+
</Menu.Item>
307+
</Menu.Dropdown>
308+
</Menu>
242309
</Group>
243310
</Group>
244311

@@ -428,21 +495,33 @@ export const CatalogueManager = ({ onNavigate, shareData, initialListId }: Catal
428495
{/* Modals */}
429496
<Modal
430497
opened={showCreateModal}
431-
onClose={() => setShowCreateModal(false)}
432-
title="Create New List"
498+
onClose={handleCloseCreateModal}
499+
title={selectedTemplate ? `Create from Template: ${selectedTemplate.name}` : "Create New List"}
433500
size="md"
434501
trapFocus
435502
returnFocus
436503
>
437504
<CreateListModal
438-
onClose={() => setShowCreateModal(false)}
439-
onSubmit={async (params) => {
440-
const listId = await createList(params);
441-
// Switch to the appropriate tab based on list type
442-
setActiveTab(params.type === "bibliography" ? "bibliographies" : "lists");
443-
selectList(listId);
444-
setShowCreateModal(false);
445-
}}
505+
onClose={handleCloseCreateModal}
506+
onSubmit={handleCreateList}
507+
initialTitle={selectedTemplate?.name}
508+
initialDescription={selectedTemplate?.description}
509+
initialType={selectedTemplate?.type}
510+
initialTags={selectedTemplate?.tags}
511+
/>
512+
</Modal>
513+
514+
<Modal
515+
opened={showTemplatesModal}
516+
onClose={() => setShowTemplatesModal(false)}
517+
title="Choose a Template"
518+
size="xl"
519+
trapFocus
520+
returnFocus
521+
>
522+
<ListTemplates
523+
onUseTemplate={handleUseTemplate}
524+
onClose={() => setShowTemplatesModal(false)}
446525
/>
447526
</Modal>
448527

apps/web/src/components/catalogue/CreateListModal.tsx

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,18 +31,29 @@ interface CreateListModalProps {
3131
tags?: string[];
3232
isPublic?: boolean;
3333
}) => Promise<void>;
34+
initialTitle?: string;
35+
initialDescription?: string;
36+
initialType?: ListType;
37+
initialTags?: string[];
3438
}
3539

3640
// Validation constants
3741
const MAX_TITLE_LENGTH = 100;
3842
const MAX_DESCRIPTION_LENGTH = 500;
3943
const MAX_TAGS_COUNT = 10;
4044

41-
export const CreateListModal = ({ onClose, onSubmit }: CreateListModalProps) => {
42-
const [title, setTitle] = useState("");
43-
const [description, setDescription] = useState("");
44-
const [type, setType] = useState<ListType>("list");
45-
const [tags, setTags] = useState<string[]>([]);
45+
export const CreateListModal = ({
46+
onClose,
47+
onSubmit,
48+
initialTitle,
49+
initialDescription,
50+
initialType,
51+
initialTags
52+
}: CreateListModalProps) => {
53+
const [title, setTitle] = useState(initialTitle || "");
54+
const [description, setDescription] = useState(initialDescription || "");
55+
const [type, setType] = useState<ListType>(initialType || "list");
56+
const [tags, setTags] = useState<string[]>(initialTags || []);
4657
const [isPublic, setIsPublic] = useState(false);
4758
const [isSubmitting, setIsSubmitting] = useState(false);
4859
const [submitError, setSubmitError] = useState<string | null>(null);

0 commit comments

Comments
 (0)