Skip to content

Commit 9a507f0

Browse files
committed
feat: Implement enhanced search and filtering for templates
1 parent 92af915 commit 9a507f0

6 files changed

Lines changed: 380 additions & 31 deletions

File tree

app/tools/template-manager/page.tsx

Lines changed: 71 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,62 @@
11
'use client';
22

3-
import { useState, useEffect } from 'react';
3+
import { useState, useEffect, useCallback } from 'react';
44
import { TemplateService } from '@/lib/db/services/templateService';
55
import { VariableService } from '@/lib/db/services/variableService';
66
import { Template, Variable } from '@/types';
77
import { TemplateEditor } from '@/components/template-editor';
88
import { VariableForm } from '@/components/variable-form';
99
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
10+
import { Input } from '@/components/ui/input';
11+
import { MultiSelect } from '@/components/ui/multi-select';
1012

1113
export default function TemplateManagerPage() {
1214
const [variables, setVariables] = useState<Variable[]>([]);
1315
const [templates, setTemplates] = useState<Template[]>([]);
1416
const [selectedTemplate, setSelectedTemplate] = useState<Template | null>(null);
1517
const [loading, setLoading] = useState(true);
18+
const [searchQuery, setSearchQuery] = useState('');
19+
const [selectedCategories, setSelectedCategories] = useState<string[]>([]);
20+
const [tags, setTags] = useState('');
1621

1722
useEffect(() => {
18-
loadData();
23+
const init = async () => {
24+
await TemplateService.initializeDefaultTemplates();
25+
await VariableService.initializeDefaultVariables();
26+
};
27+
init();
1928
}, []);
2029

21-
const loadData = async () => {
30+
const loadData = useCallback(async () => {
2231
try {
2332
setLoading(true);
33+
34+
const templatesData = await TemplateService.getFilteredTemplates({
35+
searchQuery,
36+
categories: selectedCategories,
37+
tags: tags.split(',').map(t => t.trim()).filter(t => t),
38+
});
2439

25-
// Initialize default data if needed
26-
await TemplateService.initializeDefaultTemplates();
27-
await VariableService.initializeDefaultVariables();
28-
29-
// Load data
30-
const [templatesData, variablesData] = await Promise.all([
31-
TemplateService.getAllTemplates(),
32-
VariableService.getAllVariables()
33-
]);
40+
const variablesData = await VariableService.getAllVariables();
3441

3542
setTemplates(templatesData);
3643
setVariables(variablesData);
3744

3845
if (templatesData.length > 0) {
3946
setSelectedTemplate(templatesData[0]);
47+
} else {
48+
setSelectedTemplate(null);
4049
}
4150
} catch (error) {
4251
console.error('Error loading data:', error);
4352
} finally {
4453
setLoading(false);
4554
}
46-
};
55+
}, [searchQuery, selectedCategories, tags]);
56+
57+
useEffect(() => {
58+
loadData();
59+
}, [loadData]);
4760
const handleCreateTemplate = async () => {
4861
const newTemplate = await TemplateService.createTemplate({
4962
name: 'Untitled',
@@ -71,6 +84,21 @@ export default function TemplateManagerPage() {
7184
}
7285
};
7386

87+
const getHighlightedText = (text: string, highlight: string) => {
88+
const parts = text.split(new RegExp(`(${highlight})`, 'gi'));
89+
return (
90+
<span>
91+
{parts.map((part, i) =>
92+
part.toLowerCase() === highlight.toLowerCase() ? (
93+
<strong key={i}>{part}</strong>
94+
) : (
95+
part
96+
)
97+
)}
98+
</span>
99+
);
100+
};
101+
74102
if (loading) {
75103
return (
76104
<div className="min-h-screen bg-background flex items-center justify-center">
@@ -91,9 +119,35 @@ export default function TemplateManagerPage() {
91119
Create and manage your professional templates with ease
92120
</p>
93121
</div>
94-
<button onClick={handleCreateTemplate} className="btn btn-primary">
95-
+ New Template
96-
</button>
122+
<div className="flex justify-between items-center">
123+
<div className="flex gap-4">
124+
<Input
125+
placeholder="Search templates..."
126+
value={searchQuery}
127+
onChange={(e) => setSearchQuery(e.target.value)}
128+
className="max-w-xs"
129+
/>
130+
<MultiSelect
131+
options={[
132+
{ label: 'Email', value: 'email' },
133+
{ label: 'Blog', value: 'blog' },
134+
{ label: 'Social', value: 'social' },
135+
]}
136+
onValueChange={setSelectedCategories}
137+
defaultValue={selectedCategories}
138+
placeholder="Filter by category"
139+
/>
140+
<Input
141+
placeholder="Filter by tags (comma-separated)"
142+
value={tags}
143+
onChange={(e) => setTags(e.target.value)}
144+
className="max-w-xs"
145+
/>
146+
</div>
147+
<button onClick={handleCreateTemplate} className="btn btn-primary">
148+
+ New Template
149+
</button>
150+
</div>
97151

98152
<div className="grid md:grid-cols-2 gap-8">
99153
<VariableForm
@@ -111,7 +165,7 @@ export default function TemplateManagerPage() {
111165
value={template.id}
112166
onClick={() => setSelectedTemplate(template)}
113167
>
114-
{template.name}
168+
{getHighlightedText(template.name, searchQuery)}
115169
</TabsTrigger>
116170
))}
117171
</TabsList>

components/template-editor.tsx

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import { Template, Variable } from '@/types';
55
import { Button } from '@/components/ui/button';
66
import { Textarea } from '@/components/ui/textarea';
77
import { Card } from '@/components/ui/card';
8+
import { Input } from '@/components/ui/input';
9+
import { TemplateService } from '@/lib/db/services/templateService';
810

911
interface TemplateEditorProps {
1012
template: Template;
@@ -13,6 +15,16 @@ interface TemplateEditorProps {
1315

1416
export function TemplateEditor({ template, variables }: TemplateEditorProps) {
1517
const [content, setContent] = useState(template.content);
18+
const [category, setCategory] = useState(template.category);
19+
const [tags, setTags] = useState(template.tags.join(', '));
20+
21+
const handleSave = async () => {
22+
await TemplateService.updateTemplate(template.id, {
23+
content,
24+
category,
25+
tags: tags.split(',').map(t => t.trim()),
26+
});
27+
};
1628

1729
const generateContent = () => {
1830
let result = content;
@@ -31,6 +43,18 @@ export function TemplateEditor({ template, variables }: TemplateEditorProps) {
3143
<Card className="p-6">
3244
<div className="space-y-4">
3345
<h3 className="text-lg font-semibold">{template.name}</h3>
46+
<div className="grid grid-cols-2 gap-4">
47+
<Input
48+
value={category}
49+
onChange={(e) => setCategory(e.target.value)}
50+
placeholder="Category"
51+
/>
52+
<Input
53+
value={tags}
54+
onChange={(e) => setTags(e.target.value)}
55+
placeholder="Tags (comma-separated)"
56+
/>
57+
</div>
3458
<Textarea
3559
value={content}
3660
onChange={(e) => setContent(e.target.value)}
@@ -41,7 +65,10 @@ export function TemplateEditor({ template, variables }: TemplateEditorProps) {
4165
<div className="p-4 rounded-md bg-muted whitespace-pre-wrap">
4266
{generateContent()}
4367
</div>
44-
<Button onClick={copyToClipboard}>Copy to Clipboard</Button>
68+
<div className="flex gap-4">
69+
<Button onClick={handleSave}>Save</Button>
70+
<Button onClick={copyToClipboard}>Copy to Clipboard</Button>
71+
</div>
4572
</div>
4673
</div>
4774
</Card>

0 commit comments

Comments
 (0)