diff --git a/plans/task-fix-template-view.md b/plans/task-fix-template-view.md new file mode 100644 index 0000000..e18f0c4 --- /dev/null +++ b/plans/task-fix-template-view.md @@ -0,0 +1,542 @@ +# Task Fix: Quotation Template JSON Viewer Overflow & Sheet Refactor + +## Status + +Planned Fix + +## Objective + +Fix the Quotation Template settings page layout overflow caused by rendering a full PDFME JSON schema inline. + +The page should no longer render large JSON content directly in the main content area. Instead, each template version should expose a compact `View JSON` action that opens a scrollable Sheet viewer. + +This fix improves: + +* page readability +* button visibility +* layout stability +* JSON review experience +* future maintainability for large PDFME templates + +--- + +## Problem + +Current page: + +```txt +/dashboard/crm/settings/templates +``` + +renders full JSON schema inline. + +Observed issue: + +* full JSON expands vertically and horizontally +* content overflows the visible page +* buttons/actions become hard to find +* page is difficult to operate +* JSON block dominates the whole settings page + +Current pattern is likely similar to: + +```tsx +
{JSON.stringify(schemaJson, null, 2)}
+``` + +or: + +```tsx +{JSON.stringify(version.schemaJson, null, 2)} +``` + +This should be removed from the main page layout. + +--- + +## Screenshot Context + +Current UI shows: + +```txt +Quotation Templates +Template Configuration Center +ALLA Quotation Standard +Version 1.0 +Large inline JSON block +``` + +The large inline JSON causes the page to become visually noisy and may hide or push important actions away from the viewport. + +--- + +## Mandatory Review + +Review before implementation: + +```txt +src/app/dashboard/crm/settings/templates/** +src/features/crm/document-templates/** +src/features/crm/quotations/** +src/components/ui/sheet.tsx +src/components/ui/button.tsx +``` + +Also search for inline JSON rendering: + +```bash +rg "JSON.stringify|schemaJson|schema_json| + {JSON.stringify(schemaJson, null, 2)} + +``` + +Correct: + +```tsx + +``` + +--- + +## Scope 2: Create Template JSON Sheet Component + +Create component: + +```txt +src/features/crm/document-templates/components/template-json-sheet.tsx +``` + +If the feature folder differs, place it near the current template settings components. + +Component props: + +```ts +type TemplateJsonSheetProps = { + version: string; + filePath?: string | null; + schemaJson: unknown; + triggerLabel?: string; +}; +``` + +Required behavior: + +* client component +* uses shadcn `Sheet` +* trigger button opens Sheet +* formats JSON with `JSON.stringify(schemaJson ?? {}, null, 2)` +* displays JSON inside scrollable area +* includes copy-to-clipboard button +* handles invalid or empty JSON safely +* does not crash if schemaJson is null + +--- + +## Scope 3: Sheet Layout Rules + +Use: + +```tsx + +``` + +or if project layout requires: + +```tsx + +``` + +JSON block should use: + +```txt +max-h-[calc(100vh-180px)] +overflow-auto +rounded-lg +border +bg-muted +p-4 +font-mono +text-xs +whitespace-pre +``` + +Important: + +* JSON scrolls inside Sheet +* main page does not grow because of JSON +* horizontal JSON overflow is contained inside Sheet +* Sheet remains usable on smaller screens + +--- + +## Scope 4: Copy JSON Action + +Add: + +```txt +Copy JSON +``` + +Behavior: + +* copy formatted JSON to clipboard +* show success toast if toast utility exists +* if no toast utility exists, copy silently or use existing notification pattern + +Recommended: + +```tsx +await navigator.clipboard.writeText(formattedJson); +toast.success('Copied JSON'); +``` + +Rules: + +* do not import a new toast library if project already has one +* use existing toast/sonner convention + +--- + +## Scope 5: Compact Version Card UI + +Refactor version display. + +Instead of showing JSON inline, display a compact card section like: + +```txt +Version 1.0 +src/pdfme_template/ALLA_template_pdfme_fainal3.json + +Mappings: 12 +Table Columns: 24 +Status: Active + +[View JSON] +``` + +If mapping counts are already available, use them. + +If not available, show only safe existing metadata. + +Do not introduce extra server queries unless necessary. + +--- + +## Scope 6: Empty / Missing JSON State + +If `schemaJson` is missing: + +Sheet should show: + +```txt +No schema JSON available. +``` + +or: + +```json +{} +``` + +Do not throw runtime error. + +--- + +## Scope 7: Optional JSON Summary + +If easy, add small metadata before the button: + +```txt +Pages: n +Schemas: n +PDFME Version: x +``` + +Possible calculation: + +```ts +const pageCount = Array.isArray(schemaJson?.schemas) + ? schemaJson.schemas.length + : 0; +``` + +This is optional and should not block the fix. + +--- + +## Scope 8: Accessibility + +Requirements: + +* button label must be clear +* Sheet title must exist +* code block should be selectable +* copy button should have visible text, not icon only +* no hidden overflow on the whole page + +--- + +## Scope 9: Do Not Change Template Data Model + +This is UI-only. + +Do not change: + +```txt +crm_document_templates +crm_document_template_versions +crm_document_template_mappings +crm_document_template_table_columns +PDFME schema format +template seed behavior +template upload behavior +quotation PDF generation +``` + +--- + +## Suggested Component Example + +```tsx +'use client'; + +import { useMemo } from 'react'; +import { Copy, Eye } from 'lucide-react'; +import { toast } from 'sonner'; + +import { Button } from '@/components/ui/button'; +import { + Sheet, + SheetContent, + SheetDescription, + SheetHeader, + SheetTitle, + SheetTrigger +} from '@/components/ui/sheet'; + +type TemplateJsonSheetProps = { + version: string; + filePath?: string | null; + schemaJson: unknown; + triggerLabel?: string; +}; + +export function TemplateJsonSheet({ + version, + filePath, + schemaJson, + triggerLabel = 'View JSON' +}: TemplateJsonSheetProps) { + const formattedJson = useMemo(() => { + try { + return JSON.stringify(schemaJson ?? {}, null, 2); + } catch { + return '{}'; + } + }, [schemaJson]); + + const handleCopy = async () => { + await navigator.clipboard.writeText(formattedJson); + toast.success('Copied JSON'); + }; + + return ( + + + + + + + + Template Schema JSON + + Version {version} + {filePath ? ` · ${filePath}` : ''} + + + +
+ +
+ +
+          {formattedJson}
+        
+
+
+ ); +} +``` + +Adjust import paths and formatting to match the project style. + +--- + +## Scope 10: Page-Level Overflow Guard + +Review parent containers. + +Ensure the templates page does not use layout classes that create viewport overflow unexpectedly. + +Recommended: + +```txt +space-y-6 +overflow-hidden only when intentional +min-w-0 on flex/grid children +``` + +If cards contain long paths, use: + +```txt +truncate +break-all +``` + +For file paths: + +```tsx +

+ {filePath} +

+``` + +--- + +## Verification + +Run: + +```bash +npm exec tsc --noEmit +npm run build +``` + +Manual verification: + +```txt +Open /dashboard/crm/settings/templates +Page no longer shows full JSON inline +Page does not overflow because of JSON +Template version card remains compact +View JSON button is visible +Click View JSON +Sheet opens from right side +JSON displays formatted +JSON scrolls inside Sheet +Copy JSON works +Close Sheet +No regression in quotation PDF preview/download +``` + +Search verification: + +```bash +rg "JSON.stringify| formatSchemaJson(schemaJson), [schemaJson]); + const pageCount = React.useMemo(() => getSchemaPageCount(schemaJson), [schemaJson]); + const hasSchemaJson = formattedJson !== '{}'; + + async function handleCopy() { + try { + await navigator.clipboard.writeText(formattedJson); + toast.success('Copied JSON'); + } catch { + toast.error('Unable to copy JSON'); + } + } + + return ( + + + + + + + + Template Schema JSON + + Version {version} + {filePath ? ` | ${filePath}` : ''} + + + +
+
+ {pageCount > 0 ? `Pages: ${pageCount}` : 'Schema metadata unavailable'} +
+ + +
+ + {hasSchemaJson ? ( +
+            {formattedJson}
+          
+ ) : ( +
+ No schema JSON available. +
+ )} +
+
+ ); +} diff --git a/src/features/foundation/document-template/components/template-settings.tsx b/src/features/foundation/document-template/components/template-settings.tsx index 848b297..41a763c 100644 --- a/src/features/foundation/document-template/components/template-settings.tsx +++ b/src/features/foundation/document-template/components/template-settings.tsx @@ -18,6 +18,7 @@ import { Input } from '@/components/ui/input'; import { Switch } from '@/components/ui/switch'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; import { Textarea } from '@/components/ui/textarea'; +import { TemplateJsonSheet } from './template-json-sheet'; import { createDocumentTemplateMappingMutation, createDocumentTemplateMutation, @@ -182,6 +183,10 @@ function Field({ ); } +function getTableColumnCount(version: DocumentTemplateVersionDetail) { + return version.mappings.reduce((total, mapping) => total + mapping.columns.length, 0); +} + function TemplateDialog({ open, onOpenChange, @@ -574,40 +579,61 @@ function TemplateDetailCard({ templateId }: { templateId: string }) { No versions created yet. ) : null} - {template.versions.map((version) => ( - -
-
-
-
Version {version.version}
-
- {version.filePath || 'No file path set'}{version.previewImageUrl ? ` • ${version.previewImageUrl}` : ''} + {template.versions.map((version) => ( + +
+
+
+
Version {version.version}
+
+
+
File Path
+
+ {version.filePath || 'No file path set'} +
+
+
+
Mappings
+
{version.mappings.length}
+
+
+
Table Columns
+
{getTableColumnCount(version)}
+
+
+
Preview Image
+
+ {version.previewImageUrl || '-'} +
+
+
+
+
+ + {version.isActive ? 'Active' : 'Inactive'} + + + +
-
- - {version.isActive ? 'Active' : 'Inactive'} - - - -
-
-                {JSON.stringify(version.schemaJson, null, 2)}
-              
-
{version.mappings.map((mapping) => (