Files
Document_editor/frontend/src/pages/TemplatesPage.tsx

178 lines
6.0 KiB
TypeScript

import { useEffect, useState } from 'react'
import { Link } 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 { TemplateListItem } from '../types'
export default function TemplatesPage() {
const { user } = useAuth()
const { t, formatDate } = useI18n()
const [templates, setTemplates] = useState<TemplateListItem[]>([])
const [loading, setLoading] = useState(true)
const [error, setError] = useState('')
const [deleteTarget, setDeleteTarget] = useState<TemplateListItem | null>(null)
const load = async () => {
try {
setTemplates(await api.listTemplates())
} catch (err) {
setError(err instanceof Error ? err.message : t('errors.loadTemplatesFailed'))
} finally {
setLoading(false)
}
}
useEffect(() => {
load()
}, [])
const handleDelete = async () => {
if (!deleteTarget) return
try {
await api.deleteTemplate(deleteTarget.id)
setTemplates((list) => list.filter((x) => x.id !== deleteTarget.id))
setDeleteTarget(null)
} catch (err) {
alert(err instanceof Error ? err.message : t('errors.deleteFailed'))
}
}
const handleTogglePublic = async (item: TemplateListItem) => {
try {
const updated = await api.updateTemplate(item.id, { is_public: !item.is_public })
setTemplates((list) =>
list.map((x) =>
x.id === item.id ? { ...x, is_public: updated.is_public } : x,
),
)
} catch (err) {
alert(err instanceof Error ? err.message : t('errors.updateFailed'))
}
}
const handleDownloadSource = async (item: TemplateListItem) => {
try {
await downloadWithAuth(api.getTemplateSourceUrl(item.id), item.original_filename)
} catch (err) {
alert(err instanceof Error ? err.message : t('errors.downloadFailed'))
}
}
const canManage = (item: TemplateListItem) =>
item.is_owner || user?.role === 'admin'
return (
<div className="container">
<ConfirmDialog
open={deleteTarget !== null}
title={t('templates.deleteTitle')}
message={
deleteTarget
? t('templates.deleteMessage', {
name: deleteTarget.name,
cannotUndo: t('common.cannotUndo'),
})
: ''
}
confirmLabel={t('common.delete')}
danger
onConfirm={() => void handleDelete()}
onCancel={() => setDeleteTarget(null)}
/>
<div className="page-header">
<h1>{t('templates.title')}</h1>
<Link to="/templates/new" className="btn btn-primary">
{t('templates.upload')}
</Link>
</div>
{error && <div className="error">{error}</div>}
{loading ? (
<p>{t('common.loading')}</p>
) : templates.length === 0 ? (
<div className="card empty-state">
<p>{t('templates.empty')}</p>
<Link to="/templates/new" className="btn btn-primary" style={{ marginTop: 16 }}>
{t('templates.uploadShort')}
</Link>
</div>
) : (
<div className="card">
<table className="table">
<thead>
<tr>
<th>{t('common.name')}</th>
<th>{t('common.file')}</th>
<th>{t('common.owner')}</th>
<th>{t('templates.visibility')}</th>
<th>{t('templates.variables')}</th>
<th>{t('common.created')}</th>
<th>{t('common.actions')}</th>
</tr>
</thead>
<tbody>
{templates.map((item) => (
<tr key={item.id}>
<td>
<strong>{item.name}</strong>
{item.description && (
<div className="style-hint">{item.description}</div>
)}
</td>
<td>{item.original_filename}</td>
<td>{item.owner_username ?? `#${item.owner_id}`}</td>
<td>
{item.is_public ? (
<span className="badge" style={{ background: '#dbeafe', color: '#1d4ed8' }}>
{t('visibility.public')}
</span>
) : (
<span className="badge badge-user">{t('visibility.private')}</span>
)}
</td>
<td>{item.variable_count}</td>
<td>{formatDate(item.created_at)}</td>
<td style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>
<Link to={`/templates/${item.id}/fill`} className="btn btn-primary btn-sm">
{t('templates.fill')}
</Link>
<Link to={`/templates/${item.id}`} className="btn btn-secondary btn-sm">
{t('common.view')}
</Link>
<button
className="btn btn-secondary btn-sm"
onClick={() => handleDownloadSource(item)}
>
{t('templates.source')}
</button>
{canManage(item) && (
<>
<button
className="btn btn-secondary btn-sm"
onClick={() => handleTogglePublic(item)}
>
{item.is_public ? t('visibility.makePrivate') : t('visibility.makePublic')}
</button>
<button
className="btn btn-danger btn-sm"
onClick={() => setDeleteTarget(item)}
>
{t('common.delete')}
</button>
</>
)}
</td>
</tr>
))}
</tbody>
</table>
</div>
)}
</div>
)
}