Files
the_order/apps/mcp-legal/src/components/TemplateLibrary.tsx
defiQUG 6a8582e54d feat: comprehensive project structure improvements and Cloud for Sovereignty landing zone
- Add Cloud for Sovereignty landing zone architecture and deployment
- Implement complete legal document management system
- Reorganize documentation with improved navigation
- Add infrastructure improvements (Dockerfiles, K8s, monitoring)
- Add operational improvements (graceful shutdown, rate limiting, caching)
- Create comprehensive project structure documentation
- Add Azure deployment automation scripts
- Improve repository navigation and organization
2025-11-13 09:32:55 -08:00

253 lines
8.2 KiB
TypeScript

/**
* Template Library Component
* UI for document template management
*/
import React, { useState } from 'react';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import {
Box,
Button,
Card,
CardContent,
CardHeader,
Chip,
Dialog,
DialogActions,
DialogContent,
DialogTitle,
TextField,
Typography,
Grid,
IconButton,
MenuItem,
Select,
FormControl,
InputLabel,
} from '@mui/material';
import {
Add as AddIcon,
Edit as EditIcon,
Preview as PreviewIcon,
FileCopy as FileCopyIcon,
} from '@mui/icons-material';
interface Template {
id: string;
name: string;
description?: string;
category?: string;
version: number;
is_active: boolean;
}
export function TemplateLibrary() {
const [selectedTemplate, setSelectedTemplate] = useState<Template | null>(null);
const [createDialogOpen, setCreateDialogOpen] = useState(false);
const [previewDialogOpen, setPreviewDialogOpen] = useState(false);
const [categoryFilter, setCategoryFilter] = useState<string>('all');
const queryClient = useQueryClient();
const { data: templates, isLoading } = useQuery<Template[]>({
queryKey: ['templates', categoryFilter],
queryFn: async () => {
const params = new URLSearchParams();
if (categoryFilter !== 'all') params.append('category', categoryFilter);
const response = await fetch(`/api/templates?${params}`);
if (!response.ok) throw new Error('Failed to fetch templates');
const data = await response.json();
return data.templates || [];
},
});
const createTemplate = useMutation({
mutationFn: async (template: Partial<Template & { template_content: string }>) => {
const response = await fetch('/api/templates', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(template),
});
if (!response.ok) throw new Error('Failed to create template');
return response.json();
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['templates'] });
setCreateDialogOpen(false);
},
});
const renderTemplate = useMutation({
mutationFn: async ({ templateId, variables }: { templateId: string; variables: Record<string, unknown> }) => {
const response = await fetch(`/api/templates/${templateId}/render`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ variables }),
});
if (!response.ok) throw new Error('Failed to render template');
return response.json();
},
});
const handleCreateTemplate = (formData: FormData) => {
createTemplate.mutate({
name: formData.get('name') as string,
description: formData.get('description') as string,
category: formData.get('category') as string,
template_content: formData.get('template_content') as string,
is_active: true,
});
};
const handlePreview = async (template: Template) => {
setSelectedTemplate(template);
// For preview, we'd typically show a form to input variables
// For now, just open the dialog
setPreviewDialogOpen(true);
};
return (
<Box>
<Box display="flex" justifyContent="space-between" alignItems="center" mb={3}>
<Typography variant="h4">Template Library</Typography>
<Box>
<FormControl size="small" sx={{ minWidth: 120, mr: 2 }}>
<InputLabel>Category</InputLabel>
<Select
value={categoryFilter}
label="Category"
onChange={(e) => setCategoryFilter(e.target.value)}
>
<MenuItem value="all">All</MenuItem>
<MenuItem value="contract">Contract</MenuItem>
<MenuItem value="pleading">Pleading</MenuItem>
<MenuItem value="brief">Brief</MenuItem>
<MenuItem value="letter">Letter</MenuItem>
</Select>
</FormControl>
<Button
variant="contained"
startIcon={<AddIcon />}
onClick={() => setCreateDialogOpen(true)}
>
New Template
</Button>
</Box>
</Box>
{isLoading ? (
<Typography>Loading...</Typography>
) : (
<Grid container spacing={3}>
{templates?.map((template) => (
<Grid item xs={12} sm={6} md={4} key={template.id}>
<Card>
<CardHeader
title={template.name}
subheader={template.category}
action={
<Box>
<IconButton size="small" onClick={() => handlePreview(template)}>
<PreviewIcon />
</IconButton>
<IconButton size="small">
<EditIcon />
</IconButton>
</Box>
}
/>
<CardContent>
<Typography variant="body2" color="text.secondary" sx={{ mb: 1 }}>
{template.description}
</Typography>
<Box display="flex" gap={1}>
<Chip label={`v${template.version}`} size="small" />
{template.is_active && <Chip label="Active" size="small" color="success" />}
</Box>
<Button
fullWidth
variant="outlined"
startIcon={<FileCopyIcon />}
sx={{ mt: 2 }}
onClick={() => {
// Navigate to document assembly with this template
window.location.href = `/assembly?template=${template.id}`;
}}
>
Use Template
</Button>
</CardContent>
</Card>
</Grid>
))}
</Grid>
)}
<Dialog open={createDialogOpen} onClose={() => setCreateDialogOpen(false)} maxWidth="md" fullWidth>
<form
onSubmit={(e) => {
e.preventDefault();
handleCreateTemplate(new FormData(e.currentTarget));
}}
>
<DialogTitle>Create New Template</DialogTitle>
<DialogContent>
<TextField
autoFocus
margin="dense"
name="name"
label="Template Name"
fullWidth
required
sx={{ mb: 2 }}
/>
<TextField
margin="dense"
name="description"
label="Description"
fullWidth
sx={{ mb: 2 }}
/>
<FormControl fullWidth margin="dense" sx={{ mb: 2 }}>
<InputLabel>Category</InputLabel>
<Select name="category" label="Category">
<MenuItem value="contract">Contract</MenuItem>
<MenuItem value="pleading">Pleading</MenuItem>
<MenuItem value="brief">Brief</MenuItem>
<MenuItem value="letter">Letter</MenuItem>
</Select>
</FormControl>
<TextField
margin="dense"
name="template_content"
label="Template Content"
fullWidth
multiline
rows={15}
required
placeholder="Use {{variable}} for variables"
/>
</DialogContent>
<DialogActions>
<Button onClick={() => setCreateDialogOpen(false)}>Cancel</Button>
<Button type="submit" variant="contained">
Create
</Button>
</DialogActions>
</form>
</Dialog>
<Dialog open={previewDialogOpen} onClose={() => setPreviewDialogOpen(false)} maxWidth="lg" fullWidth>
<DialogTitle>Preview: {selectedTemplate?.name}</DialogTitle>
<DialogContent>
<Typography>Template preview and variable input form would go here</Typography>
</DialogContent>
<DialogActions>
<Button onClick={() => setPreviewDialogOpen(false)}>Close</Button>
<Button variant="contained">Generate Document</Button>
</DialogActions>
</Dialog>
</Box>
);
}