commit task-fix-template
This commit is contained in:
542
plans/task-fix-template-view.md
Normal file
542
plans/task-fix-template-view.md
Normal file
@@ -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
|
||||||
|
<pre>{JSON.stringify(schemaJson, null, 2)}</pre>
|
||||||
|
```
|
||||||
|
|
||||||
|
or:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<code>{JSON.stringify(version.schemaJson, null, 2)}</code>
|
||||||
|
```
|
||||||
|
|
||||||
|
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|<pre|<code" src/app/dashboard/crm/settings/templates src/features/crm/document-templates src/features/crm/quotations
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Required UX Change
|
||||||
|
|
||||||
|
Replace inline JSON rendering with a Sheet-based viewer.
|
||||||
|
|
||||||
|
Main page should display only compact information.
|
||||||
|
|
||||||
|
Each template version card/row should show:
|
||||||
|
|
||||||
|
```txt
|
||||||
|
Version
|
||||||
|
File path
|
||||||
|
Mapping count
|
||||||
|
Table column count
|
||||||
|
Active status
|
||||||
|
View JSON button
|
||||||
|
```
|
||||||
|
|
||||||
|
When user clicks:
|
||||||
|
|
||||||
|
```txt
|
||||||
|
View JSON
|
||||||
|
```
|
||||||
|
|
||||||
|
Open a right-side Sheet.
|
||||||
|
|
||||||
|
Sheet should show:
|
||||||
|
|
||||||
|
```txt
|
||||||
|
Title: Template Schema JSON
|
||||||
|
Description: version + file path
|
||||||
|
Actions: Copy JSON
|
||||||
|
Content: formatted JSON in a scrollable code block
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Scope 1: Remove Inline JSON From Main Page
|
||||||
|
|
||||||
|
Find the current inline JSON block and remove it from normal page flow.
|
||||||
|
|
||||||
|
Do not render full JSON directly inside:
|
||||||
|
|
||||||
|
```txt
|
||||||
|
Card
|
||||||
|
Accordion
|
||||||
|
Tabs
|
||||||
|
Main page content
|
||||||
|
```
|
||||||
|
|
||||||
|
Replace it with a compact action button.
|
||||||
|
|
||||||
|
Incorrect:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<pre className="...">
|
||||||
|
{JSON.stringify(schemaJson, null, 2)}
|
||||||
|
</pre>
|
||||||
|
```
|
||||||
|
|
||||||
|
Correct:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<TemplateJsonSheet
|
||||||
|
version={version.version}
|
||||||
|
filePath={version.filePath}
|
||||||
|
schemaJson={version.schemaJson}
|
||||||
|
/>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 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
|
||||||
|
<SheetContent side="right" className="w-full sm:max-w-4xl">
|
||||||
|
```
|
||||||
|
|
||||||
|
or if project layout requires:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<SheetContent side="right" className="w-full max-w-[90vw] sm:max-w-5xl">
|
||||||
|
```
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<Sheet>
|
||||||
|
<SheetTrigger asChild>
|
||||||
|
<Button variant='outline' size='sm'>
|
||||||
|
<Eye className='mr-2 h-4 w-4' />
|
||||||
|
{triggerLabel}
|
||||||
|
</Button>
|
||||||
|
</SheetTrigger>
|
||||||
|
|
||||||
|
<SheetContent side='right' className='w-full sm:max-w-4xl'>
|
||||||
|
<SheetHeader>
|
||||||
|
<SheetTitle>Template Schema JSON</SheetTitle>
|
||||||
|
<SheetDescription>
|
||||||
|
Version {version}
|
||||||
|
{filePath ? ` · ${filePath}` : ''}
|
||||||
|
</SheetDescription>
|
||||||
|
</SheetHeader>
|
||||||
|
|
||||||
|
<div className='mt-4 flex items-center justify-end'>
|
||||||
|
<Button variant='outline' size='sm' onClick={handleCopy}>
|
||||||
|
<Copy className='mr-2 h-4 w-4' />
|
||||||
|
Copy JSON
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<pre className='mt-4 max-h-[calc(100vh-180px)] overflow-auto rounded-lg border bg-muted p-4 font-mono text-xs whitespace-pre'>
|
||||||
|
<code>{formattedJson}</code>
|
||||||
|
</pre>
|
||||||
|
</SheetContent>
|
||||||
|
</Sheet>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
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
|
||||||
|
<p className="break-all text-sm text-muted-foreground">
|
||||||
|
{filePath}
|
||||||
|
</p>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 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|<pre|schemaJson|schema_json" src/app/dashboard/crm/settings/templates src/features/crm/document-templates src/features/crm/quotations
|
||||||
|
```
|
||||||
|
|
||||||
|
Allowed:
|
||||||
|
|
||||||
|
```txt
|
||||||
|
TemplateJsonSheet
|
||||||
|
intentional JSON formatting inside Sheet
|
||||||
|
server-side schema serialization
|
||||||
|
```
|
||||||
|
|
||||||
|
Not allowed:
|
||||||
|
|
||||||
|
```txt
|
||||||
|
inline full JSON rendering on main settings page
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Definition of Done
|
||||||
|
|
||||||
|
Task is complete when:
|
||||||
|
|
||||||
|
* Quotation Template settings page no longer renders full JSON inline
|
||||||
|
* JSON schema opens only through Sheet
|
||||||
|
* Sheet has independent scroll area
|
||||||
|
* Copy JSON action works
|
||||||
|
* Page actions remain visible
|
||||||
|
* Layout overflow is fixed
|
||||||
|
* TypeScript passes
|
||||||
|
* Build passes
|
||||||
|
|
||||||
|
Result:
|
||||||
|
|
||||||
|
```txt
|
||||||
|
Quotation Template JSON Viewer = Refactored
|
||||||
|
Template Settings Layout = Stable
|
||||||
|
Admin UX = Improved
|
||||||
|
```
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import * as React from 'react';
|
||||||
|
import { IconCopy, IconEye } from '@tabler/icons-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;
|
||||||
|
};
|
||||||
|
|
||||||
|
function formatSchemaJson(schemaJson: unknown) {
|
||||||
|
try {
|
||||||
|
return JSON.stringify(schemaJson ?? {}, null, 2);
|
||||||
|
} catch {
|
||||||
|
return '{}';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSchemaPageCount(schemaJson: unknown) {
|
||||||
|
if (
|
||||||
|
schemaJson &&
|
||||||
|
typeof schemaJson === 'object' &&
|
||||||
|
'schemas' in schemaJson &&
|
||||||
|
Array.isArray((schemaJson as { schemas?: unknown }).schemas)
|
||||||
|
) {
|
||||||
|
return (schemaJson as { schemas: unknown[] }).schemas.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function TemplateJsonSheet({
|
||||||
|
version,
|
||||||
|
filePath,
|
||||||
|
schemaJson,
|
||||||
|
triggerLabel = 'View JSON'
|
||||||
|
}: TemplateJsonSheetProps) {
|
||||||
|
const formattedJson = React.useMemo(() => 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 (
|
||||||
|
<Sheet>
|
||||||
|
<SheetTrigger asChild>
|
||||||
|
<Button variant='outline' size='sm'>
|
||||||
|
<IconEye className='size-4' />
|
||||||
|
{triggerLabel}
|
||||||
|
</Button>
|
||||||
|
</SheetTrigger>
|
||||||
|
|
||||||
|
<SheetContent side='right' className='w-full max-w-[90vw] sm:max-w-5xl'>
|
||||||
|
<SheetHeader>
|
||||||
|
<SheetTitle>Template Schema JSON</SheetTitle>
|
||||||
|
<SheetDescription>
|
||||||
|
Version {version}
|
||||||
|
{filePath ? ` | ${filePath}` : ''}
|
||||||
|
</SheetDescription>
|
||||||
|
</SheetHeader>
|
||||||
|
|
||||||
|
<div className='flex items-center justify-between gap-3'>
|
||||||
|
<div className='text-muted-foreground text-sm'>
|
||||||
|
{pageCount > 0 ? `Pages: ${pageCount}` : 'Schema metadata unavailable'}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Button variant='outline' size='sm' onClick={handleCopy}>
|
||||||
|
<IconCopy className='size-4' />
|
||||||
|
Copy JSON
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{hasSchemaJson ? (
|
||||||
|
<pre className='bg-muted max-h-[calc(100vh-180px)] overflow-auto rounded-lg border p-4 font-mono text-xs whitespace-pre'>
|
||||||
|
<code>{formattedJson}</code>
|
||||||
|
</pre>
|
||||||
|
) : (
|
||||||
|
<div className='text-muted-foreground rounded-lg border border-dashed p-6 text-sm'>
|
||||||
|
No schema JSON available.
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</SheetContent>
|
||||||
|
</Sheet>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -18,6 +18,7 @@ import { Input } from '@/components/ui/input';
|
|||||||
import { Switch } from '@/components/ui/switch';
|
import { Switch } from '@/components/ui/switch';
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
||||||
import { Textarea } from '@/components/ui/textarea';
|
import { Textarea } from '@/components/ui/textarea';
|
||||||
|
import { TemplateJsonSheet } from './template-json-sheet';
|
||||||
import {
|
import {
|
||||||
createDocumentTemplateMappingMutation,
|
createDocumentTemplateMappingMutation,
|
||||||
createDocumentTemplateMutation,
|
createDocumentTemplateMutation,
|
||||||
@@ -182,6 +183,10 @@ function Field({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getTableColumnCount(version: DocumentTemplateVersionDetail) {
|
||||||
|
return version.mappings.reduce((total, mapping) => total + mapping.columns.length, 0);
|
||||||
|
}
|
||||||
|
|
||||||
function TemplateDialog({
|
function TemplateDialog({
|
||||||
open,
|
open,
|
||||||
onOpenChange,
|
onOpenChange,
|
||||||
@@ -574,40 +579,61 @@ function TemplateDetailCard({ templateId }: { templateId: string }) {
|
|||||||
No versions created yet.
|
No versions created yet.
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
{template.versions.map((version) => (
|
{template.versions.map((version) => (
|
||||||
<TabsContent key={version.id} value={version.id} className='space-y-4'>
|
<TabsContent key={version.id} value={version.id} className='space-y-4'>
|
||||||
<div className='rounded-lg border p-4'>
|
<div className='rounded-lg border p-4'>
|
||||||
<div className='flex flex-wrap items-center justify-between gap-3'>
|
<div className='flex flex-wrap items-start justify-between gap-3'>
|
||||||
<div>
|
<div className='min-w-0 flex-1 space-y-3'>
|
||||||
<div className='font-medium'>Version {version.version}</div>
|
<div className='font-medium'>Version {version.version}</div>
|
||||||
<div className='text-muted-foreground text-sm'>
|
<div className='grid gap-3 sm:grid-cols-2 xl:grid-cols-4'>
|
||||||
{version.filePath || 'No file path set'}{version.previewImageUrl ? ` • ${version.previewImageUrl}` : ''}
|
<div className='rounded-lg bg-muted/40 p-3'>
|
||||||
|
<div className='text-muted-foreground text-xs'>File Path</div>
|
||||||
|
<div className='mt-1 break-all text-sm font-medium'>
|
||||||
|
{version.filePath || 'No file path set'}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className='rounded-lg bg-muted/40 p-3'>
|
||||||
|
<div className='text-muted-foreground text-xs'>Mappings</div>
|
||||||
|
<div className='mt-1 text-sm font-medium'>{version.mappings.length}</div>
|
||||||
|
</div>
|
||||||
|
<div className='rounded-lg bg-muted/40 p-3'>
|
||||||
|
<div className='text-muted-foreground text-xs'>Table Columns</div>
|
||||||
|
<div className='mt-1 text-sm font-medium'>{getTableColumnCount(version)}</div>
|
||||||
|
</div>
|
||||||
|
<div className='rounded-lg bg-muted/40 p-3'>
|
||||||
|
<div className='text-muted-foreground text-xs'>Preview Image</div>
|
||||||
|
<div className='mt-1 break-all text-sm font-medium'>
|
||||||
|
{version.previewImageUrl || '-'}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className='flex flex-wrap items-center gap-2'>
|
||||||
|
<Badge variant={version.isActive ? 'secondary' : 'outline'}>
|
||||||
|
{version.isActive ? 'Active' : 'Inactive'}
|
||||||
|
</Badge>
|
||||||
|
<TemplateJsonSheet
|
||||||
|
version={version.version}
|
||||||
|
filePath={version.filePath}
|
||||||
|
schemaJson={version.schemaJson}
|
||||||
|
/>
|
||||||
|
<Button variant='outline' onClick={() => setVersionDialogOpen(true)}>
|
||||||
|
Edit Version
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={() =>
|
||||||
|
setMappingDialogState({
|
||||||
|
open: true,
|
||||||
|
version
|
||||||
|
})
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Icons.add className='mr-2 h-4 w-4' />
|
||||||
|
Add Mapping
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className='flex items-center gap-2'>
|
|
||||||
<Badge variant={version.isActive ? 'secondary' : 'outline'}>
|
|
||||||
{version.isActive ? 'Active' : 'Inactive'}
|
|
||||||
</Badge>
|
|
||||||
<Button variant='outline' onClick={() => setVersionDialogOpen(true)}>
|
|
||||||
Edit Version
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
onClick={() =>
|
|
||||||
setMappingDialogState({
|
|
||||||
open: true,
|
|
||||||
version
|
|
||||||
})
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Icons.add className='mr-2 h-4 w-4' />
|
|
||||||
Add Mapping
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<pre className='bg-muted mt-4 max-h-72 overflow-auto rounded-lg p-4 text-xs'>
|
|
||||||
{JSON.stringify(version.schemaJson, null, 2)}
|
|
||||||
</pre>
|
|
||||||
</div>
|
|
||||||
<div className='space-y-3'>
|
<div className='space-y-3'>
|
||||||
{version.mappings.map((mapping) => (
|
{version.mappings.map((mapping) => (
|
||||||
<div key={mapping.id} className='rounded-lg border p-4'>
|
<div key={mapping.id} className='rounded-lg border p-4'>
|
||||||
|
|||||||
Reference in New Issue
Block a user