Skip to main content

Frontend Template Management - Quick Reference

Routes​

RouteComponentDescription
/admin/templatesTemplateManagementPageMain template management interface
/admin/locations/:locationId/templatesLocationTemplateConfigPageLocation-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​

  1. Always validate HTML before saving templates
  2. Use descriptive template names for easy identification
  3. Test templates with sample data before deploying
  4. Set appropriate page margins for your printer
  5. Use version control - duplicate templates before major changes
  6. Document custom variables in template descriptions
  7. Configure location settings for each document type
  8. Keep CSS simple for better PDF rendering
  9. Test print output on actual printers
  10. Use default templates as fallbacks

Support​

For issues or questions:

  1. Check the console for error messages
  2. Review the implementation documentation
  3. Verify API endpoints are accessible
  4. Check authentication status
  5. Consult the main documentation