Frontend Template Management - Quick Reference
Routesβ
| Route | Component | Description |
|---|---|---|
/admin/templates | TemplateManagementPage | Main template management interface |
/admin/locations/:locationId/templates | LocationTemplateConfigPage | Location-specific template configuration |
Key Componentsβ
TemplateUploadDialogβ
<TemplateUploadDialog
isOpen={boolean}
onClose={() => void}
businessId={string}
token={string}
onSuccess={() => void}
/>
TemplateEditorβ
<TemplateEditor
isOpen={boolean}
onClose={() => void}
template={Template}
businessId={string}
token={string}
onSuccess={() => void}
/>
TemplateListβ
<TemplateList
templates={Template[]}
isLoading={boolean}
onEdit={(template) => void}
onDelete={(template) => void}
onPreview={(template) => void}
onDuplicate={(template) => void}
onSetDefault={(template) => void}
onFilterChange={(documentType, search) => void}
/>
TemplatePreviewModalβ
<TemplatePreviewModal
isOpen={boolean}
onClose={() => void}
template={Template}
businessId={string}
token={string}
/>
LocationTemplateConfigβ
<LocationTemplateConfig
locationId={string}
businessId={string}
token={string}
/>
CodeEditorβ
<CodeEditor
language="html" | "css" | "javascript" | "json"
value={string}
onChange={(e) => void}
showLineNumbers={boolean}
rows={number}
/>
Service Functionsβ
Template CRUD Operationsβ
// Create
await uploadTemplate(token, {
name: string,
documentType: DocumentType,
htmlContent: string,
cssContent: string,
status: TemplateStatus,
isDefault: boolean,
configuration: PdfConfiguration,
businessId: string
});
// Read
await getTemplates(token, businessId, page, size, documentType?, search?);
await getTemplate(token, id, businessId);
// Update
await updateTemplate(token, id, {
name?: string,
htmlContent?: string,
// ... other fields
businessId: string
});
// Delete
await deleteTemplate(token, id, businessId);
Template Operationsβ
// Test with sample data
const pdfBlob = await testTemplate(token, businessId, {
templateId: string,
sampleData: Record<string, unknown>
});
// Get HTML preview
const html = await getTemplatePreview(token, businessId, {
htmlContent: string,
cssContent: string,
sampleData: Record<string, unknown>
});
// Get available variables
const variables = await getTemplateVariables(token, documentType);
// Duplicate template
const newTemplate = await duplicateTemplate(token, id, businessId, newName);
// Set as default
await setDefaultTemplate(token, id, businessId);
Location Configurationβ
// Create config
await saveLocationTemplateConfig(token, {
locationId: string,
documentType: DocumentType,
templateId: string,
autoPrint: boolean,
copies: number,
isActive: boolean,
businessId: string
});
// Get configs
await getLocationTemplateConfigs(token, locationId, businessId);
// Update config
await updateLocationTemplateConfig(token, id, {
templateId?: string,
autoPrint?: boolean,
copies?: number,
isActive?: boolean,
businessId: string
});
// Delete config
await deleteLocationTemplateConfig(token, id, businessId);
Type Definitionsβ
DocumentType Enumβ
enum DocumentType {
SALE = "sale",
PURCHASE = "purchase",
INVOICE = "invoice",
CREDIT_NOTE = "credit_note",
DEBIT_NOTE = "debit_note",
PURCHASE_ORDER = "purchase_order",
GOODS_RECEIVED_NOTE = "goods_received_note",
TRANSFER_REQUEST = "transfer_request",
TRANSFER_DISPATCH_NOTE = "transfer_dispatch_note",
TRANSFER_GOODS_RECEIPT = "transfer_goods_receipt",
STOCK_COUNT = "stock_count",
INVENTORY_ADJUSTMENT = "inventory_adjustment",
ACCOUNTS_PAYABLE_PAYMENT = "accounts_payable_payment",
ACCOUNTS_RECEIVABLE_RECEIPT = "accounts_receivable_receipt",
CUSTOM = "custom"
}
Template Interfaceβ
interface Template {
id: string;
businessId: string;
name: string;
documentType: DocumentType;
description?: string;
htmlContent: string;
cssContent?: string;
status: TemplateStatus;
version: number;
isDefault: boolean;
configuration: PdfConfiguration;
variables?: TemplateVariable[];
previewUrl?: string;
createdBy: string;
updatedBy?: string;
createdAt: Date;
updatedAt: Date;
}
PdfConfiguration Interfaceβ
interface PdfConfiguration {
pageSize?: string; // "Letter", "Legal", "A4", "A5", "Tabloid"
margins?: {
top: number;
right: number;
bottom: number;
left: number;
};
printBackground?: boolean;
timeout?: number;
headerHeight?: number;
footerHeight?: number;
displayHeaderFooter?: boolean;
}
Common Patternsβ
Getting Auth Tokenβ
import { auth } from "@/firebase";
import { getIdToken } from "firebase/auth";
const getToken = async () => {
const user = auth.currentUser;
if (!user) throw new Error("No authenticated user");
return getIdToken(user);
};
Using Current Businessβ
import { useCurrentBusiness } from "@/contexts/useCurrentBusiness";
const { currentBusiness } = useCurrentBusiness();
if (!currentBusiness) return null;
Using Current Locationβ
import { useCurrentLocation } from "@/contexts/useCurrentLocation";
const { currentLocation } = useCurrentLocation();
if (!currentLocation) return null;
Toast Notificationsβ
import { toast } from "sonner";
// Success
toast.success("Template saved successfully");
// Error
toast.error("Failed to save template");
// Info
toast.info("Loading templates...");
Loading Stateβ
const [isLoading, setIsLoading] = useState(false);
try {
setIsLoading(true);
await someAsyncOperation();
} catch (error) {
console.error(error);
toast.error("Operation failed");
} finally {
setIsLoading(false);
}
Handlebars Template Examplesβ
Basic Variableβ
<h1>{{business.name}}</h1>
Conditionalβ
{{#if customer}}
<p>Customer: {{customer.name}}</p>
{{/if}}
Loopβ
{{#each items}}
<div>
<span>{{this.name}}</span>
<span>{{this.quantity}}</span>
</div>
{{/each}}
Nested Propertiesβ
<p>{{business.address.street}}</p>
<p>{{business.address.city}}, {{business.address.state}}</p>
Sample Templateβ
<!DOCTYPE html>
<html>
<head>
<style>
body { font-family: Arial, sans-serif; }
.header { text-align: center; margin-bottom: 20px; }
.items { margin: 20px 0; }
.item { display: flex; justify-content: space-between; padding: 5px 0; }
.total { font-weight: bold; text-align: right; margin-top: 20px; }
</style>
</head>
<body>
<div class="header">
<h1>{{business.name}}</h1>
<p>{{business.address}}</p>
<p>Phone: {{business.phone}}</p>
</div>
<h2>{{documentType}} #{{documentNumber}}</h2>
<p>Date: {{date}}</p>
{{#if customer}}
<div class="customer">
<h3>Customer Information</h3>
<p>{{customer.name}}</p>
<p>{{customer.address}}</p>
</div>
{{/if}}
<div class="items">
<h3>Items</h3>
{{#each items}}
<div class="item">
<span>{{this.name}}</span>
<span>{{this.quantity}} x ${{this.price}}</span>
<span>${{this.total}}</span>
</div>
{{/each}}
</div>
<div class="total">
<p>Subtotal: ${{subtotal}}</p>
<p>Tax: ${{tax}}</p>
<p>Total: ${{total}}</p>
</div>
</body>
</html>
Troubleshootingβ
Template Not Savingβ
- Check that all required fields are filled
- Verify business context is available
- Check browser console for errors
- Ensure valid HTML syntax
Preview Not Loadingβ
- Verify template has valid HTML
- Check that sample data is properly formatted
- Ensure API endpoint is accessible
- Check network tab for errors
Location Config Not Workingβ
- Verify location ID is valid
- Check that templates exist for the document type
- Ensure business context matches
- Verify user has proper permissions
Best Practicesβ
- Always validate HTML before saving templates
- Use descriptive template names for easy identification
- Test templates with sample data before deploying
- Set appropriate page margins for your printer
- Use version control - duplicate templates before major changes
- Document custom variables in template descriptions
- Configure location settings for each document type
- Keep CSS simple for better PDF rendering
- Test print output on actual printers
- Use default templates as fallbacks
Supportβ
For issues or questions:
- Check the console for error messages
- Review the implementation documentation
- Verify API endpoints are accessible
- Check authentication status
- Consult the main documentation