From 5992fc867d99896bea7ee951e74376bad7d4e098 Mon Sep 17 00:00:00 2001 From: AI Assistant Date: Sat, 28 Feb 2026 02:33:57 +0200 Subject: [PATCH] 3.06 Template Library Redenumire, Versionare, Multi-format - Renamed from 'Sabloane Word' to 'Biblioteca Sabloane' (Template Library) - Multi-format support: Word, Excel, PDF, DWG, Archicad with auto-detection - Auto versioning: 'Revizie Noua' button archives current version, bumps semver - Version history dialog: browse and download any previous version - Simplified UX: file upload vs external link toggle, auto-detect placeholders silently for .docx, hide placeholders section for non-Word formats - File type icons: distinct icons for docx, xlsx, archicad, dwg, pdf - Updated stats cards: Word/Excel count, DWG/Archicad count, versioned count - Backward compatible: old entries without fileType/versionHistory get defaults --- src/core/i18n/locales/ro.ts | 5 +- .../components/email-signature-module.tsx | 44 +- .../components/word-templates-module.tsx | 535 +++++++++++++----- src/modules/word-templates/config.ts | 25 +- .../word-templates/hooks/use-templates.ts | 49 +- src/modules/word-templates/index.ts | 11 +- src/modules/word-templates/types.ts | 42 +- 7 files changed, 529 insertions(+), 182 deletions(-) diff --git a/src/core/i18n/locales/ro.ts b/src/core/i18n/locales/ro.ts index b9c7a2b..3fbbe0d 100644 --- a/src/core/i18n/locales/ro.ts +++ b/src/core/i18n/locales/ro.ts @@ -91,8 +91,9 @@ export const ro: Labels = { description: "Clienți, furnizori, instituții", }, "word-templates": { - title: "Șabloane Word", - description: "Bibliotecă contracte, oferte, rapoarte", + title: "Bibliotecă Șabloane", + description: + "Șabloane contracte, oferte, rapoarte (Word, Excel, Archicad, DWG, PDF)", }, "tag-manager": { title: "Manager Etichete", diff --git a/src/modules/email-signature/components/email-signature-module.tsx b/src/modules/email-signature/components/email-signature-module.tsx index 4c73afe..9529db1 100644 --- a/src/modules/email-signature/components/email-signature-module.tsx +++ b/src/modules/email-signature/components/email-signature-module.tsx @@ -1,25 +1,32 @@ -'use client'; +"use client"; -import { useSignatureConfig } from '../hooks/use-signature-config'; -import { useSavedSignatures } from '../hooks/use-saved-signatures'; -import { SignatureConfigurator } from './signature-configurator'; -import { SignaturePreview } from './signature-preview'; -import { SavedSignaturesPanel } from './saved-signatures-panel'; -import { Separator } from '@/shared/components/ui/separator'; -import { Button } from '@/shared/components/ui/button'; -import { RotateCcw } from 'lucide-react'; -import type { SignatureBanner } from '../types'; +import { useSignatureConfig } from "../hooks/use-signature-config"; +import { useSavedSignatures } from "../hooks/use-saved-signatures"; +import { SignatureConfigurator } from "./signature-configurator"; +import { SignaturePreview } from "./signature-preview"; +import { SavedSignaturesPanel } from "./saved-signatures-panel"; +import { Separator } from "@/shared/components/ui/separator"; +import { Button } from "@/shared/components/ui/button"; +import { RotateCcw } from "lucide-react"; +import type { SignatureBanner } from "../types"; export function EmailSignatureModule() { const { - config, updateField, updateColor, updateLayout, - setVariant, setCompany, setAddress, resetToDefaults, loadConfig, + config, + updateField, + updateColor, + updateLayout, + setVariant, + setCompany, + setAddress, + resetToDefaults, + loadConfig, } = useSignatureConfig(); const { saved, loading, save, remove } = useSavedSignatures(); const setBanner = (banner: SignatureBanner | undefined) => { - updateField('banner', banner); + updateField("banner", banner); }; return ( @@ -42,7 +49,9 @@ export function EmailSignatureModule() { { await save(label, cfg); }} + onSave={async (label, cfg) => { + await save(label, cfg); + }} onLoad={loadConfig} onRemove={remove} currentConfig={config} @@ -50,7 +59,12 @@ export function EmailSignatureModule() { - diff --git a/src/modules/word-templates/components/word-templates-module.tsx b/src/modules/word-templates/components/word-templates-module.tsx index 9c8716f..aa4b095 100644 --- a/src/modules/word-templates/components/word-templates-module.tsx +++ b/src/modules/word-templates/components/word-templates-module.tsx @@ -7,11 +7,17 @@ import { Trash2, Search, FileText, + FileSpreadsheet, + FileArchive, + File, ExternalLink, Copy, FolderOpen, - Wand2, Loader2, + History, + Link, + Upload, + RotateCcw, } from "lucide-react"; import { Button } from "@/shared/components/ui/button"; import { Input } from "@/shared/components/ui/input"; @@ -39,12 +45,13 @@ import { DialogFooter, } from "@/shared/components/ui/dialog"; import type { CompanyId } from "@/core/auth/types"; -import type { WordTemplate, TemplateCategory } from "../types"; +import type { + WordTemplate, + TemplateCategory, + TemplateFileType, +} from "../types"; import { useTemplates } from "../hooks/use-templates"; -import { - parsePlaceholdersFromBuffer, - parsePlaceholdersFromUrl, -} from "../services/placeholder-parser"; +import { parsePlaceholdersFromBuffer } from "../services/placeholder-parser"; const CATEGORY_LABELS: Record = { contract: "Contract", @@ -57,6 +64,44 @@ const CATEGORY_LABELS: Record = { altele: "Altele", }; +const FILE_TYPE_LABELS: Record = { + docx: "Word (.docx)", + xlsx: "Excel (.xlsx)", + pdf: "PDF", + dwg: "DWG", + archicad: "Archicad (.tpl/.pln)", + other: "Altul", +}; + +function detectFileType(url: string): TemplateFileType { + const ext = url.split(".").pop()?.toLowerCase() ?? ""; + if (ext === "docx" || ext === "doc") return "docx"; + if (ext === "xlsx" || ext === "xls") return "xlsx"; + if (ext === "pdf") return "pdf"; + if (ext === "dwg" || ext === "dxf") return "dwg"; + if (["tpl", "pln", "pla", "bpn"].includes(ext)) return "archicad"; + return "other"; +} + +function FileTypeIcon({ + fileType, + className, +}: { + fileType: TemplateFileType; + className?: string; +}) { + switch (fileType) { + case "docx": + return ; + case "xlsx": + return ; + case "archicad": + return ; + default: + return ; + } +} + type ViewMode = "list" | "add" | "edit"; export function WordTemplatesModule() { @@ -68,6 +113,7 @@ export function WordTemplatesModule() { updateFilter, addTemplate, updateTemplate, + createRevision, cloneTemplate, removeTemplate, } = useTemplates(); @@ -76,6 +122,13 @@ export function WordTemplatesModule() { null, ); const [deletingId, setDeletingId] = useState(null); + const [revisingTemplate, setRevisingTemplate] = + useState(null); + const [revisionUrl, setRevisionUrl] = useState(""); + const [revisionNotes, setRevisionNotes] = useState(""); + const [historyTemplate, setHistoryTemplate] = useState( + null, + ); const handleSubmit = async ( data: Omit, @@ -96,6 +149,15 @@ export function WordTemplatesModule() { } }; + const handleRevisionConfirm = async () => { + if (revisingTemplate) { + await createRevision(revisingTemplate.id, revisionUrl, revisionNotes); + setRevisingTemplate(null); + setRevisionUrl(""); + setRevisionNotes(""); + } + }; + return (
{/* Stats */} @@ -108,28 +170,25 @@ export function WordTemplatesModule() { -

Beletage

+

Word/Excel

- {allTemplates.filter((t) => t.company === "beletage").length} + {allTemplates.filter((t) => (t.fileType ?? "docx") === "docx" || t.fileType === "xlsx").length}

-

Urban Switch

+

DWG/Archicad

- {allTemplates.filter((t) => t.company === "urban-switch").length} + {allTemplates.filter((t) => t.fileType === "dwg" || t.fileType === "archicad").length}

-

Studii de Teren

+

Cu versiuni

- { - allTemplates.filter((t) => t.company === "studii-de-teren") - .length - } + {allTemplates.filter((t) => (t.versionHistory ?? []).length > 0).length}

@@ -193,7 +252,7 @@ export function WordTemplatesModule() {

) : templates.length === 0 ? (

- Niciun șablon găsit. Adaugă primul șablon Word. + Niciun șablon găsit. Adaugă primul șablon.

) : (
@@ -201,6 +260,15 @@ export function WordTemplatesModule() {
+ + )} +
@@ -330,6 +417,144 @@ export function WordTemplatesModule() { + + {/* Revision dialog */} + { + if (!open) { + setRevisingTemplate(null); + setRevisionUrl(""); + setRevisionNotes(""); + } + }} + > + + + + Revizie nouă — {revisingTemplate?.name} + + +
+

+ Versiunea curentă v{revisingTemplate?.version} va + fi arhivată. Se va crea automat versiunea următoare. +

+
+ + setRevisionUrl(e.target.value)} + placeholder="Lasă gol pentru a păstra URL-ul actual" + className="mt-1" + /> +
+
+ +