diff --git a/src/config/nas-paths.ts b/src/config/nas-paths.ts new file mode 100644 index 0000000..7aeaf67 --- /dev/null +++ b/src/config/nas-paths.ts @@ -0,0 +1,98 @@ +/** + * NAS / network storage path mappings. + * + * The office NAS (\\newamun / 10.10.10.10) exposes several shares. + * Windows maps these to drive letters. This config lets us normalise + * user-pasted paths to UNC and back, and build clickable `file:///` links. + */ + +export interface NasDriveMapping { + /** Drive letter (upper-case, no colon) */ + drive: string; + /** UNC prefix WITHOUT trailing backslash, e.g. \\\\newamun\\Proiecte */ + unc: string; + /** Human-readable label */ + label: string; +} + +/** All known drive-letter → UNC mappings */ +export const NAS_DRIVE_MAPPINGS: NasDriveMapping[] = [ + { drive: "P", unc: "\\\\newamun\\Proiecte", label: "Proiecte" }, + // Add more as needed: + // { drive: "S", unc: "\\\\newamun\\Shared", label: "Shared" }, +]; + +/** NAS hostname / IP — used for display only */ +export const NAS_HOST = "newamun"; +export const NAS_IP = "10.10.10.10"; + +// ── helpers ── + +/** + * Detect whether a string looks like a Windows network / mapped-drive path. + * Accepts: `P:\folder\file.pdf`, `\\newamun\Proiecte\...`, `\\10.10.10.10\...` + */ +export function isNetworkPath(input: string): boolean { + const trimmed = input.trim(); + // UNC path + if (trimmed.startsWith("\\\\")) return true; + // Mapped drive letter that we recognise + const match = trimmed.match(/^([A-Z]):\\/i); + if (match) { + const letter = match[1]!.toUpperCase(); + return NAS_DRIVE_MAPPINGS.some((m) => m.drive === letter); + } + return false; +} + +/** + * Normalise to UNC path (replace drive letter with \\newamun\Share). + * If already UNC or unrecognised → returns the original trimmed. + */ +export function toUncPath(input: string): string { + const trimmed = input.trim(); + if (trimmed.startsWith("\\\\")) return trimmed; + const match = trimmed.match(/^([A-Z]):(\\.*)/i); + if (match) { + const letter = match[1]!.toUpperCase(); + const rest = match[2]!; + const mapping = NAS_DRIVE_MAPPINGS.find((m) => m.drive === letter); + if (mapping) return `${mapping.unc}${rest}`; + } + return trimmed; +} + +/** + * Build a clickable `file:///` URL from a UNC or drive-letter path. + * Windows Explorer opens this natively. + */ +export function toFileUrl(input: string): string { + const unc = toUncPath(input); + // file:///\\server\share\path → file://///server/share/path + const slashed = unc.replace(/\\/g, "/"); + return `file:///${slashed}`; +} + +/** + * Extract a short display name from a full path. + * e.g. `\\newamun\Proiecte\095\doc.pdf` → `doc.pdf` + */ +export function pathFileName(input: string): string { + const trimmed = input.trim(); + const parts = trimmed.split(/[\\/]/); + return parts[parts.length - 1] || trimmed; +} + +/** + * Extract a short display path (share + last 2 segments). + * e.g. `\\newamun\Proiecte\095 - 2020\99_DOC\file.pdf` → `Proiecte\…\99_DOC\file.pdf` + */ +export function shortDisplayPath(input: string): string { + const unc = toUncPath(input); + const parts = unc.replace(/^\\\\/, "").split("\\").filter(Boolean); + // parts: [server, share, folder1, folder2, ..., file] + if (parts.length <= 3) return parts.slice(1).join("\\"); + const share = parts[1] ?? ""; + const last2 = parts.slice(-2).join("\\"); + return `${share}\\…\\${last2}`; +} diff --git a/src/modules/registratura/components/registry-entry-form.tsx b/src/modules/registratura/components/registry-entry-form.tsx index 718fba7..599aa1b 100644 --- a/src/modules/registratura/components/registry-entry-form.tsx +++ b/src/modules/registratura/components/registry-entry-form.tsx @@ -15,6 +15,9 @@ import { Globe, ArrowDownToLine, ArrowUpFromLine, + HardDrive, + FolderOpen, + Link2, } from "lucide-react"; import type { CompanyId } from "@/core/auth/types"; import type { @@ -27,6 +30,13 @@ import type { ACValidityTracking, } from "../types"; import { DEFAULT_DOC_TYPE_LABELS } from "../types"; +import { + isNetworkPath, + toUncPath, + toFileUrl, + pathFileName, + shortDisplayPath, +} from "@/config/nas-paths"; import { Input } from "@/shared/components/ui/input"; import { Label } from "@/shared/components/ui/label"; import { Textarea } from "@/shared/components/ui/textarea"; @@ -367,6 +377,31 @@ export function RegistryEntryForm({ setAttachments((prev) => prev.filter((a) => a.id !== id)); }; + // Network path support + const [networkPathInput, setNetworkPathInput] = useState(""); + const [showNetworkInput, setShowNetworkInput] = useState(false); + + const handleAddNetworkPath = () => { + const raw = networkPathInput.trim(); + if (!raw) return; + const unc = toUncPath(raw); + const fileName = pathFileName(raw); + setAttachments((prev) => [ + ...prev, + { + id: uuid(), + name: fileName, + data: "__network__", + type: "network/path", + size: 0, + addedAt: new Date().toISOString(), + networkPath: unc, + }, + ]); + setNetworkPathInput(""); + setShowNetworkInput(false); + }; + const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); if (isSubmitting || isUploading) return; @@ -1152,15 +1187,27 @@ export function RegistryEntryForm({ )} - +
+ + +
+ + {/* Network path input */} + {showNetworkInput && ( +
+ +
+ setNetworkPathInput(e.target.value)} + placeholder="P:\\095 - 2020 - Duplex\\99_DOC\\CU 1348-2024.pdf" + className="flex-1 font-mono text-xs" + onKeyDown={(e) => { + if (e.key === "Enter") { + e.preventDefault(); + handleAddNetworkPath(); + } + }} + /> + +
+ {networkPathInput.trim() && isNetworkPath(networkPathInput) && ( +

+ → {shortDisplayPath(networkPathInput)} +

+ )} + {networkPathInput.trim() && !isNetworkPath(networkPathInput) && ( +

+ Calea nu pare a fi pe NAS. Introdu o cale de tip P:\\... sau \\newamun\\... +

+ )} +
+ )} + {attachments.length > 0 && (
- {attachments.map((att) => ( -
- - {att.name} - - {(att.size / 1024).toFixed(0)} KB - - -
- ))} + + { + // file:/// links might be blocked by browser; copy path as fallback + e.preventDefault(); + window.open(toFileUrl(att.networkPath!), "_blank"); + }} + > + + + {shortDisplayPath(att.networkPath)} + + + + NAS + + + +
+ ) : ( + // Normal uploaded file attachment +
+ + {att.name} + + {(att.size / 1024).toFixed(0)} KB + + +
+ ), + )} )} diff --git a/src/modules/registratura/types.ts b/src/modules/registratura/types.ts index 76ffa76..2416edd 100644 --- a/src/modules/registratura/types.ts +++ b/src/modules/registratura/types.ts @@ -68,15 +68,17 @@ export interface ClosureInfo { attachment?: RegistryAttachment; } -/** File attachment */ +/** File attachment — either uploaded (base64) or network path reference */ export interface RegistryAttachment { id: string; name: string; - /** base64-encoded content or URL */ + /** base64-encoded content, empty string (stripped), or "__network__" for network paths */ data: string; type: string; size: number; addedAt: string; + /** UNC or drive-letter path to a file on network storage (NAS) */ + networkPath?: string; } // ── Deadline tracking types ──