From 989a9908baf56d0cf6193e1a7ca2663240ba4484 Mon Sep 17 00:00:00 2001 From: AI Assistant Date: Sat, 28 Feb 2026 01:54:40 +0200 Subject: [PATCH] 3.12 Mini Utilities Extindere si Fix-uri - NumberToText: Romanian number-to-words converter (lei/bani, compact, spaced) - ColorPaletteExtractor: image upload -> top 8 color swatches with hex copy - AreaConverter: bidirectional (mp, ari, ha, km2, sq ft) - UValueConverter: bidirectional U->R and R->U toggle - MDLPA: replaced broken iframe with 3 styled external link cards - PdfReducer: drag-and-drop, simplified to 2 levels, Deblocare PDF + PDF/A links - DWG-to-DXF skipped (needs backend service) --- .../components/digital-signatures-module.tsx | 26 +- .../components/mini-utilities-module.tsx | 727 +++++++++++++++--- 2 files changed, 623 insertions(+), 130 deletions(-) diff --git a/src/modules/digital-signatures/components/digital-signatures-module.tsx b/src/modules/digital-signatures/components/digital-signatures-module.tsx index 596a63a..6bdda90 100644 --- a/src/modules/digital-signatures/components/digital-signatures-module.tsx +++ b/src/modules/digital-signatures/components/digital-signatures-module.tsx @@ -343,8 +343,7 @@ export function DigitalSignaturesModule() { {groupedAssets.map(([group, items]) => (

- {group}{" "} - ({items.length}) + {group} ({items.length})

{items.map((asset) => ( @@ -539,15 +538,11 @@ function AssetCard({ Original - void downloadAsWord(asset)} - > + void downloadAsWord(asset)}> Word (.docx) - void downloadAsPdf(asset)} - > + void downloadAsPdf(asset)}> PDF (.pdf) @@ -575,8 +570,7 @@ function ImageUploadField({ const [processing, setProcessing] = useState(false); const handleFile = async (file: File) => { - const isTiff = - file.type === "image/tiff" || /\.tiff?$/i.test(file.name); + const isTiff = file.type === "image/tiff" || /\.tiff?$/i.test(file.name); if (isTiff) { setProcessing(true); @@ -638,9 +632,7 @@ function ImageUploadField({ ) : ( <> - - Trage imaginea aici sau apasă pentru a selecta - + Trage imaginea aici sau apasă pentru a selecta PNG, JPG, TIFF @@ -778,9 +770,7 @@ function AssetForm({ const [company, setCompany] = useState( initial?.company ?? "beletage", ); - const [subcategory, setSubcategory] = useState( - initial?.subcategory ?? "", - ); + const [subcategory, setSubcategory] = useState(initial?.subcategory ?? ""); const [tags, setTags] = useState(initial?.tags ?? []); const [tagInput, setTagInput] = useState(""); @@ -927,9 +917,7 @@ function AssetForm({ if (tagInput.trim()) addTag(tagInput); }} placeholder={ - tags.length === 0 - ? "Adaugă etichete (Enter sau virgulă)..." - : "" + tags.length === 0 ? "Adaugă etichete (Enter sau virgulă)..." : "" } className="min-w-[120px] flex-1 bg-transparent text-sm outline-none placeholder:text-muted-foreground" /> diff --git a/src/modules/mini-utilities/components/mini-utilities-module.tsx b/src/modules/mini-utilities/components/mini-utilities-module.tsx index 84452cf..e9d0716 100644 --- a/src/modules/mini-utilities/components/mini-utilities-module.tsx +++ b/src/modules/mini-utilities/components/mini-utilities-module.tsx @@ -1,6 +1,6 @@ "use client"; -import { useState, useRef } from "react"; +import { useState, useRef, useCallback } from "react"; import { Copy, Check, @@ -13,6 +13,9 @@ import { Building2, FileDown, ScanText, + CaseUpper, + Palette, + Upload, } from "lucide-react"; import { Button } from "@/shared/components/ui/button"; import { Input } from "@/shared/components/ui/input"; @@ -204,46 +207,61 @@ function PercentageCalculator() { } function AreaConverter() { - const [mp, setMp] = useState(""); - const v = parseFloat(mp); + const units = [ + { key: "mp", label: "mp (m²)", factor: 1 }, + { key: "ari", label: "ari (100 m²)", factor: 100 }, + { key: "ha", label: "hectare (10.000 m²)", factor: 10000 }, + { key: "km2", label: "km²", factor: 1000000 }, + { key: "sqft", label: "sq ft", factor: 1 / 10.7639 }, + ] as const; - const conversions = !isNaN(v) - ? [ - { label: "mp (m²)", value: v.toFixed(2) }, - { label: "ari (100 m²)", value: (v / 100).toFixed(4) }, - { label: "hectare (10.000 m²)", value: (v / 10000).toFixed(6) }, - { label: "km²", value: (v / 1000000).toFixed(8) }, - { label: "sq ft", value: (v * 10.7639).toFixed(2) }, - ] - : []; + const [values, setValues] = useState>({}); + const [activeField, setActiveField] = useState(null); + + const handleChange = (key: string, raw: string) => { + if (raw === "") { + setValues({}); + return; + } + const v = parseFloat(raw); + if (isNaN(v)) return; + const unit = units.find((u) => u.key === key); + if (!unit) return; + const mp = v * unit.factor; + const next: Record = {}; + for (const u of units) { + next[u.key] = + u.key === key + ? raw + : (mp / u.factor).toPrecision(8).replace(/\.?0+$/, ""); + } + setValues(next); + }; return (
-
- - setMp(e.target.value)} - className="mt-1" - placeholder="Introdu suprafața..." - /> +

+ Introdu o valoare în orice câmp — celelalte se calculează automat. +

+
+ {units.map((u) => ( +
+ + {u.label} + + setActiveField(u.key)} + onBlur={() => setActiveField(null)} + onChange={(e) => handleChange(u.key, e.target.value)} + className={`flex-1 ${activeField === u.key ? "ring-1 ring-primary" : ""}`} + placeholder="0" + /> + +
+ ))}
- {conversions.length > 0 && ( -
- {conversions.map(({ label, value: val }) => ( -
- - {val} - - - {label} - - -
- ))} -
- )}
); } @@ -251,36 +269,87 @@ function AreaConverter() { // ─── U-value → R-value Converter ───────────────────────────────────────────── function UValueConverter() { + const [mode, setMode] = useState<"u-to-r" | "r-to-u">("u-to-r"); const [uValue, setUValue] = useState(""); + const [rInput, setRInput] = useState(""); const [thickness, setThickness] = useState(""); - const u = parseFloat(uValue); - const t = parseFloat(thickness); - const rValue = !isNaN(u) && u > 0 ? (1 / u).toFixed(4) : null; const rsi = 0.13; const rse = 0.04; - const rTotal = - rValue !== null ? (parseFloat(rValue) + rsi + rse).toFixed(4) : null; + + // U → R + const u = parseFloat(uValue); + const rFromU = !isNaN(u) && u > 0 ? (1 / u).toFixed(4) : null; + const rTotalFromU = + rFromU !== null ? (parseFloat(rFromU) + rsi + rse).toFixed(4) : null; + + // R → U + const rIn = parseFloat(rInput); + const uFromR = !isNaN(rIn) && rIn > 0 ? (1 / rIn).toFixed(4) : null; + const rTotalFromR = uFromR !== null ? (rIn + rsi + rse).toFixed(4) : null; + + const activeR = mode === "u-to-r" ? rFromU : rInput || null; + const activeU = mode === "u-to-r" ? uValue || null : uFromR; + const activeRTotal = mode === "u-to-r" ? rTotalFromU : rTotalFromR; + + const t = parseFloat(thickness); const lambda = - rValue !== null && !isNaN(t) && t > 0 - ? (t / 100 / parseFloat(rValue)).toFixed(4) + activeR !== null && !isNaN(t) && t > 0 + ? (t / 100 / parseFloat(activeR)).toFixed(4) : null; return (
+
+ + +
-
- - setUValue(e.target.value)} - className="mt-1" - placeholder="ex: 0.35" - /> -
+ {mode === "u-to-r" ? ( +
+ + setUValue(e.target.value)} + className="mt-1" + placeholder="ex: 0.35" + /> +
+ ) : ( +
+ + setRInput(e.target.value)} + className="mt-1" + placeholder="ex: 2.86" + /> +
+ )}
- {rValue !== null && ( + {(activeR !== null || activeU !== null) && (
-
- R = 1/U -
- - {rValue} m²K/W - - + {mode === "u-to-r" && rFromU !== null && ( +
+ R = 1/U +
+ + {rFromU} m²K/W + + +
-
+ )} + {mode === "r-to-u" && uFromR !== null && ( +
+ U = 1/R +
+ + {uFromR} W/m²K + + +
+
+ )}
Rsi (suprafață interioară) @@ -317,15 +399,17 @@ function UValueConverter() { {rse} m²K/W
-
- R total (cu Rsi + Rse) -
- - {rTotal} m²K/W - - + {activeRTotal !== null && ( +
+ R total (cu Rsi + Rse) +
+ + {activeRTotal} m²K/W + + +
-
+ )} {lambda !== null && (
Conductivitate λ = d/R @@ -429,46 +513,47 @@ function AiArtifactCleaner() { function MdlpaValidator() { return ( -
-
+
+

+ Portalul MDLPA nu permite încadrarea în iframe (blochează via + X-Frame-Options). Folosește linkurile de mai jos pentru a accesa direct + portalul. +

+ -
-