164 lines
5.2 KiB
TypeScript
164 lines
5.2 KiB
TypeScript
import { useEffect, useState } from 'react'
|
|
import { Link, useNavigate, useParams } from 'react-router-dom'
|
|
import { api, downloadWithAuth } from '../api'
|
|
import ConfirmDialog from '../components/ConfirmDialog'
|
|
import { useAuth } from '../context/AuthContext'
|
|
import { useI18n } from '../context/I18nContext'
|
|
import type { DocumentTemplate, FilledDocument } from '../types'
|
|
|
|
export default function DocumentDetailPage() {
|
|
const { id } = useParams<{ id: string }>()
|
|
const navigate = useNavigate()
|
|
const { user } = useAuth()
|
|
const { t, formatDateTime } = useI18n()
|
|
const [document, setDocument] = useState<FilledDocument | null>(null)
|
|
const [template, setTemplate] = useState<DocumentTemplate | null>(null)
|
|
const [previewHtml, setPreviewHtml] = useState<string | null>(null)
|
|
const [error, setError] = useState('')
|
|
const [loading, setLoading] = useState(true)
|
|
const [showDeleteConfirm, setShowDeleteConfirm] = useState(false)
|
|
|
|
useEffect(() => {
|
|
const load = async () => {
|
|
try {
|
|
const doc = await api.getDocument(Number(id))
|
|
setDocument(doc)
|
|
const loadedTemplate = await api.getTemplate(doc.template_id)
|
|
setTemplate(loadedTemplate)
|
|
const preview = await api.previewDocument({
|
|
template_id: doc.template_id,
|
|
name: doc.name,
|
|
field_data: doc.field_data,
|
|
})
|
|
setPreviewHtml(preview.html)
|
|
} catch (err) {
|
|
setError(err instanceof Error ? err.message : t('errors.loadFailed'))
|
|
} finally {
|
|
setLoading(false)
|
|
}
|
|
}
|
|
load()
|
|
}, [id])
|
|
|
|
const handleExportDocx = async () => {
|
|
if (!document) return
|
|
try {
|
|
await downloadWithAuth(
|
|
api.getExportDocxUrl(document.id),
|
|
`${document.name}.docx`,
|
|
)
|
|
} catch (err) {
|
|
alert(err instanceof Error ? err.message : t('errors.exportFailed'))
|
|
}
|
|
}
|
|
|
|
const handleExportPdf = async () => {
|
|
if (!document) return
|
|
try {
|
|
await downloadWithAuth(
|
|
api.getExportPdfUrl(document.id),
|
|
`${document.name}.pdf`,
|
|
)
|
|
} catch (err) {
|
|
alert(err instanceof Error ? err.message : t('errors.exportPdfFailed'))
|
|
}
|
|
}
|
|
|
|
const handleDelete = async () => {
|
|
if (!document) return
|
|
try {
|
|
await api.deleteDocument(document.id)
|
|
navigate('/documents')
|
|
} catch (err) {
|
|
alert(err instanceof Error ? err.message : t('errors.deleteFailed'))
|
|
}
|
|
}
|
|
|
|
const canManage =
|
|
user?.role === 'admin' || document?.owner_id === user?.id
|
|
|
|
if (loading) return <div className="container"><p>{t('common.loading')}</p></div>
|
|
if (!document) return <div className="container"><div className="error">{error}</div></div>
|
|
|
|
return (
|
|
<div className="container">
|
|
<ConfirmDialog
|
|
open={showDeleteConfirm}
|
|
title={t('documents.deleteTitle')}
|
|
message={t('documents.deleteMessage', {
|
|
name: document.name,
|
|
cannotUndo: t('common.cannotUndo'),
|
|
})}
|
|
confirmLabel={t('common.delete')}
|
|
danger
|
|
onConfirm={() => void handleDelete()}
|
|
onCancel={() => setShowDeleteConfirm(false)}
|
|
/>
|
|
|
|
<div className="page-header">
|
|
<div>
|
|
<h1>{document.name}</h1>
|
|
{template && (
|
|
<div className="style-hint">
|
|
{t('documents.fromTemplate', { name: template.name })}
|
|
{document.owner_username && ` · ${t('documents.ownerLine', { name: document.owner_username })}`}
|
|
{' · '}
|
|
{t('documents.saved', { date: formatDateTime(document.created_at) })}
|
|
</div>
|
|
)}
|
|
</div>
|
|
<div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>
|
|
<button className="btn btn-primary" onClick={handleExportDocx}>
|
|
{t('documents.exportDocx')}
|
|
</button>
|
|
<button className="btn btn-secondary" onClick={handleExportPdf}>
|
|
{t('documents.exportPdf')}
|
|
</button>
|
|
<Link to={`/documents/${document.id}/edit`} className="btn btn-secondary">
|
|
{t('common.edit')}
|
|
</Link>
|
|
{template && (
|
|
<Link to={`/templates/${template.id}/fill`} className="btn btn-secondary">
|
|
{t('documents.reuseTemplate')}
|
|
</Link>
|
|
)}
|
|
{canManage && (
|
|
<button className="btn btn-danger" onClick={() => setShowDeleteConfirm(true)}>
|
|
{t('common.delete')}
|
|
</button>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
{error && <div className="error">{error}</div>}
|
|
|
|
<div className="grid-2">
|
|
<div className="card">
|
|
<h2 style={{ marginTop: 0 }}>{t('documents.preview')}</h2>
|
|
{previewHtml ? (
|
|
<div
|
|
className="doc-preview"
|
|
dangerouslySetInnerHTML={{ __html: previewHtml }}
|
|
/>
|
|
) : (
|
|
<p>{t('documents.previewUnavailable')}</p>
|
|
)}
|
|
</div>
|
|
|
|
<div className="card">
|
|
<h2 style={{ marginTop: 0 }}>{t('documents.fieldData')}</h2>
|
|
<pre style={{
|
|
background: '#f8fafc',
|
|
padding: 16,
|
|
borderRadius: 8,
|
|
overflow: 'auto',
|
|
fontSize: '0.85rem',
|
|
}}>
|
|
{JSON.stringify(document.field_data, null, 2)}
|
|
</pre>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|