diff --git a/app/dashboard/[id]/page.tsx b/app/dashboard/[id]/page.tsx index 99cda0f..072f4c9 100644 --- a/app/dashboard/[id]/page.tsx +++ b/app/dashboard/[id]/page.tsx @@ -157,7 +157,7 @@ export default function AssociationDetailPage() { ) : ( )} - Genera Attestato + Genera Certificato di Adesione ([]); + const [loading, setLoading] = useState(true); + const [downloadingKey, setDownloadingKey] = useState(null); + const [uploadingKey, setUploadingKey] = useState(null); + const fileInputRef = useRef(null); + const uploadTargetKey = useRef(null); + + useEffect(() => { + if (!ready) return; + if (!user || user.role === "CLIENT") { + router.push("/dashboard"); + return; + } + getTemplates() + .then(setTemplates) + .catch((error: Error) => { + if (error.message === "Unauthorized") { + router.push("/login"); + } else { + console.error(error); + } + }) + .finally(() => setLoading(false)); + }, [ready, user, router]); + + const handleDownload = async (template: DocumentTemplate) => { + setDownloadingKey(template.key); + try { + await downloadTemplate(template.key, template.fileName); + } catch (error) { + console.error("Failed to download template:", error); + } finally { + setDownloadingKey(null); + } + }; + + const openUploadPicker = (key: string) => { + uploadTargetKey.current = key; + fileInputRef.current?.click(); + }; + + const handleFileSelected = async (e: React.ChangeEvent) => { + const file = e.target.files?.[0]; + const key = uploadTargetKey.current; + e.target.value = ""; + if (!file || !key) return; + + setUploadingKey(key); + try { + const updated = await uploadTemplate(key, file); + setTemplates((prev) => prev.map((t) => (t.key === key ? updated : t))); + } catch (error) { + console.error("Failed to upload template:", error); + alert("Impossibile caricare il file"); + } finally { + setUploadingKey(null); + } + }; + + if (!ready || loading) { + return ( +
+ +
+ ); + } + + if (!user || user.role === "CLIENT") { + return null; + } + + return ( +
+ +
+ + + + + {templates.length === 0 ? ( +
+ Nessun modello disponibile +
+ ) : ( + templates.map((template) => ( + + + + + {template.name} + + + +
+

{template.fileName}

+ {template.updatedAt && ( +

Ultimo aggiornamento: {new Date(template.updatedAt).toLocaleString("it-IT")}

+ )} +
+
+ + {user.role === "ADMIN" && ( + + )} +
+
+
+ )) + )} +
+
+ ); +} diff --git a/components/layout/app-header.tsx b/components/layout/app-header.tsx index 6a9b888..146b547 100644 --- a/components/layout/app-header.tsx +++ b/components/layout/app-header.tsx @@ -36,16 +36,26 @@ export function AppHeader() { return (
- - ENCAP - ENCAP - +
+ + ENCAP + ENCAP + + {user && (user.role === "ADMIN" || user.role === "MANAGER") && ( + + Modelli + + )} +
{user && ( diff --git a/lib/api.ts b/lib/api.ts index aa4de66..ed46782 100644 --- a/lib/api.ts +++ b/lib/api.ts @@ -1,5 +1,5 @@ const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL ?? 'http://localhost:8080/api'; -import { LoginResponse, User, Association } from "./models"; +import { LoginResponse, User, Association, DocumentTemplate } from "./models"; // Store token in localStorage (we'll improve this later) export function setAuthToken(token: string) { @@ -125,7 +125,7 @@ export async function downloadAttestato(id: string, associationName: string): Pr const url = window.URL.createObjectURL(blob); const link = document.createElement('a'); link.href = url; - link.download = `attestato-${associationName}.pdf`; + link.download = `certificato-di-adesione-${associationName}.pdf`; document.body.appendChild(link); link.click(); link.remove(); @@ -147,6 +147,73 @@ export async function deleteAssociation(id: string): Promise { } } +export async function getTemplates(): Promise { + const response = await fetch(`${API_BASE_URL}/templates`, { + headers: getHeaders(), + }); + + if (!response.ok) { + if (response.status === 401 || response.status === 403) { + removeAuthToken(); + throw new Error('Unauthorized'); + } + throw new Error('Failed to fetch templates'); + } + + return response.json(); +} + +export async function downloadTemplate(key: string, fileName: string): Promise { + const response = await fetch(`${API_BASE_URL}/templates/${key}/download`, { + headers: getHeaders(), + }); + + if (!response.ok) { + if (response.status === 401 || response.status === 403) { + removeAuthToken(); + throw new Error('Unauthorized'); + } + throw new Error('Failed to download template'); + } + + const blob = await response.blob(); + const url = window.URL.createObjectURL(blob); + const link = document.createElement('a'); + link.href = url; + link.download = fileName; + document.body.appendChild(link); + link.click(); + link.remove(); + window.URL.revokeObjectURL(url); +} + +export async function uploadTemplate(key: string, file: File): Promise { + const formData = new FormData(); + formData.append('file', file); + + const token = getAuthToken(); + const headers: HeadersInit = {}; + if (token) { + headers['Authorization'] = `Bearer ${token}`; + } + + const response = await fetch(`${API_BASE_URL}/templates/${key}`, { + method: 'POST', + headers, + body: formData, + }); + + if (!response.ok) { + if (response.status === 401 || response.status === 403) { + removeAuthToken(); + throw new Error('Unauthorized'); + } + throw new Error('Failed to upload template'); + } + + return response.json(); +} + export function logout() { removeAuthToken(); } diff --git a/lib/models.ts b/lib/models.ts index b03b84a..5c6bba7 100644 --- a/lib/models.ts +++ b/lib/models.ts @@ -31,6 +31,15 @@ export interface Association { nasFolderNumber?: number; } +export interface DocumentTemplate { + id?: string; + key: string; + name: string; + fileName: string; + contentType: string; + updatedAt?: string; +} + export interface LoginResponse { token: string; user: User;