load project

This commit is contained in:
2026-06-15 09:18:58 +03:00
commit bf1839607f
70 changed files with 7503 additions and 0 deletions

View File

@@ -0,0 +1,42 @@
import { Link, useLocation } from 'react-router-dom'
import { useAuth } from '../context/AuthContext'
export default function Navbar() {
const { user, logout } = useAuth()
const location = useLocation()
const isActive = (path: string) =>
location.pathname === path || location.pathname.startsWith(path + '/')
return (
<nav className="navbar">
<Link to="/" className="navbar-brand">
Document Template Editor
</Link>
<div className="navbar-links">
<Link to="/templates" className={isActive('/templates') ? 'active' : ''}>
Templates
</Link>
<Link to="/documents" className={isActive('/documents') ? 'active' : ''}>
Documents
</Link>
{user?.role === 'admin' && (
<Link to="/users" className={isActive('/users') ? 'active' : ''}>
Users
</Link>
)}
{user && (
<>
<span>
{user.username}{' '}
<span className={`badge badge-${user.role}`}>{user.role}</span>
</span>
<button className="btn btn-secondary btn-sm" onClick={logout}>
Logout
</button>
</>
)}
</div>
</nav>
)
}

View File

@@ -0,0 +1,80 @@
import type { TemplateVariable } from '../types'
import VariableField from './VariableField'
interface TableRowEditorProps {
tableVariable: TemplateVariable
childVariables: TemplateVariable[]
rows: Record<string, unknown>[]
onChange: (rows: Record<string, unknown>[]) => void
}
export default function TableRowEditor({
tableVariable,
childVariables,
rows,
onChange,
}: TableRowEditorProps) {
const addRow = () => {
const newRow: Record<string, unknown> = {}
childVariables.forEach((v) => {
newRow[v.name] = v.default_value ?? ''
})
onChange([...rows, newRow])
}
const removeRow = (index: number) => {
onChange(rows.filter((_, i) => i !== index))
}
const updateRowField = (rowIndex: number, field: string, value: unknown) => {
const updated = rows.map((row, i) =>
i === rowIndex ? { ...row, [field]: value } : row,
)
onChange(updated)
}
const tableStyle = tableVariable.style_params?.table_style as
| { row_styles?: Record<string, unknown>[][] }
| undefined
return (
<div className="form-group">
<label>{tableVariable.label} (repeating table rows)</label>
{tableStyle && (
<div className="style-hint">
Table styles preserved from template ({tableStyle.row_styles?.length ?? 0} reference
rows)
</div>
)}
{rows.map((row, rowIndex) => (
<div key={rowIndex} className="table-row-editor">
<div className="table-row-editor-header">
<strong>Row {rowIndex + 1}</strong>
<button
type="button"
className="btn btn-danger btn-sm"
onClick={() => removeRow(rowIndex)}
>
Remove
</button>
</div>
<div className="table-row-fields">
{childVariables.map((child) => (
<VariableField
key={child.id}
variable={child}
value={row[child.name]}
onChange={(val) => updateRowField(rowIndex, child.name, val)}
/>
))}
</div>
</div>
))}
<button type="button" className="btn btn-secondary" onClick={addRow}>
+ Add Row
</button>
</div>
)
}

View File

@@ -0,0 +1,71 @@
import type { TemplateVariable } from '../types'
interface VariableFieldProps {
variable: TemplateVariable
value: unknown
onChange: (value: unknown) => void
}
export default function VariableField({ variable, value, onChange }: VariableFieldProps) {
const styleHint = variable.style_params
? [
variable.style_params.font_name && `Font: ${variable.style_params.font_name}`,
variable.style_params.font_size && `Size: ${variable.style_params.font_size}pt`,
variable.style_params.bold && 'Bold',
variable.style_params.alignment && `Align: ${variable.style_params.alignment}`,
]
.filter(Boolean)
.join(' · ')
: null
const renderInput = () => {
switch (variable.field_type) {
case 'textarea':
return (
<textarea
value={(value as string) ?? ''}
onChange={(e) => onChange(e.target.value)}
required={variable.is_required}
/>
)
case 'number':
return (
<input
type="number"
value={(value as number) ?? ''}
onChange={(e) => onChange(e.target.value ? Number(e.target.value) : '')}
required={variable.is_required}
/>
)
case 'date':
return (
<input
type="date"
value={(value as string) ?? ''}
onChange={(e) => onChange(e.target.value)}
required={variable.is_required}
/>
)
default:
return (
<input
type="text"
value={(value as string) ?? ''}
onChange={(e) => onChange(e.target.value)}
required={variable.is_required}
/>
)
}
}
return (
<div className="form-group">
<label>
{variable.label}
{variable.is_required && ' *'}
</label>
{renderInput()}
{styleHint && <div className="style-hint">Style: {styleHint}</div>}
</div>
)
}