feat(registratura): NAS network path attachments (\\newamun / P:\)
- New nas-paths.ts config: drive mappings, UNC normalization, file:/// URL builder - RegistryAttachment type extended with optional networkPath field - 'Link NAS' button in attachment section opens inline path input - Network path entries shown with blue HardDrive icon + NAS badge - Click opens in Explorer via file:/// URL, copy path button on hover - P:\ auto-converted to \\newamun\Proiecte UNC path - Short display path shows share + last 2 segments - Validation: warns if path doesn't match known NAS mappings
This commit is contained in:
@@ -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({
|
||||
</span>
|
||||
)}
|
||||
</Label>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => fileInputRef.current?.click()}
|
||||
disabled={isSubmitting}
|
||||
>
|
||||
<Paperclip className="mr-1 h-3.5 w-3.5" /> Adaugă fișier
|
||||
</Button>
|
||||
<div className="flex gap-1.5">
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => setShowNetworkInput((v) => !v)}
|
||||
disabled={isSubmitting}
|
||||
title="Link către fișier pe NAS"
|
||||
>
|
||||
<HardDrive className="mr-1 h-3.5 w-3.5" /> Link NAS
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => fileInputRef.current?.click()}
|
||||
disabled={isSubmitting}
|
||||
>
|
||||
<Paperclip className="mr-1 h-3.5 w-3.5" /> Adaugă fișier
|
||||
</Button>
|
||||
</div>
|
||||
<input
|
||||
ref={fileInputRef}
|
||||
type="file"
|
||||
@@ -1170,27 +1217,119 @@ export function RegistryEntryForm({
|
||||
className="hidden"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Network path input */}
|
||||
{showNetworkInput && (
|
||||
<div className="mt-2 rounded-md border border-blue-200 bg-blue-50/50 dark:border-blue-800 dark:bg-blue-950/30 p-3 space-y-2">
|
||||
<Label className="text-xs flex items-center gap-1">
|
||||
<HardDrive className="h-3 w-3" />
|
||||
Cale fișier pe NAS (\\newamun sau P:\\...)
|
||||
</Label>
|
||||
<div className="flex gap-2">
|
||||
<Input
|
||||
value={networkPathInput}
|
||||
onChange={(e) => 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();
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
type="button"
|
||||
size="sm"
|
||||
onClick={handleAddNetworkPath}
|
||||
disabled={!networkPathInput.trim()}
|
||||
>
|
||||
<Link2 className="mr-1 h-3.5 w-3.5" /> Adaugă
|
||||
</Button>
|
||||
</div>
|
||||
{networkPathInput.trim() && isNetworkPath(networkPathInput) && (
|
||||
<p className="text-[10px] text-muted-foreground">
|
||||
→ {shortDisplayPath(networkPathInput)}
|
||||
</p>
|
||||
)}
|
||||
{networkPathInput.trim() && !isNetworkPath(networkPathInput) && (
|
||||
<p className="text-[10px] text-amber-600 dark:text-amber-400">
|
||||
Calea nu pare a fi pe NAS. Introdu o cale de tip P:\\... sau \\newamun\\...
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{attachments.length > 0 && (
|
||||
<div className="mt-2 space-y-1">
|
||||
{attachments.map((att) => (
|
||||
<div
|
||||
key={att.id}
|
||||
className="flex items-center gap-2 rounded border px-2 py-1 text-sm"
|
||||
>
|
||||
<Paperclip className="h-3 w-3 text-muted-foreground" />
|
||||
<span className="flex-1 truncate">{att.name}</span>
|
||||
<Badge variant="outline" className="text-[10px]">
|
||||
{(att.size / 1024).toFixed(0)} KB
|
||||
</Badge>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => removeAttachment(att.id)}
|
||||
className="text-destructive"
|
||||
{attachments.map((att) =>
|
||||
att.networkPath ? (
|
||||
// Network path attachment — distinct visual
|
||||
<div
|
||||
key={att.id}
|
||||
className="flex items-center gap-2 rounded border border-blue-200 dark:border-blue-800 bg-blue-50/50 dark:bg-blue-950/20 px-2 py-1.5 text-sm group"
|
||||
>
|
||||
<X className="h-3.5 w-3.5" />
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
<HardDrive className="h-3.5 w-3.5 text-blue-600 dark:text-blue-400 shrink-0" />
|
||||
<a
|
||||
href={toFileUrl(att.networkPath)}
|
||||
className="flex-1 min-w-0 flex items-center gap-1 text-blue-700 dark:text-blue-300 hover:underline cursor-pointer"
|
||||
title={`Deschide în Explorer: ${att.networkPath}`}
|
||||
onClick={(e) => {
|
||||
// file:/// links might be blocked by browser; copy path as fallback
|
||||
e.preventDefault();
|
||||
window.open(toFileUrl(att.networkPath!), "_blank");
|
||||
}}
|
||||
>
|
||||
<FolderOpen className="h-3 w-3 shrink-0" />
|
||||
<span className="truncate font-mono text-xs">
|
||||
{shortDisplayPath(att.networkPath)}
|
||||
</span>
|
||||
</a>
|
||||
<Badge
|
||||
variant="outline"
|
||||
className="text-[10px] border-blue-300 dark:border-blue-700 text-blue-600 dark:text-blue-400 shrink-0"
|
||||
>
|
||||
NAS
|
||||
</Badge>
|
||||
<button
|
||||
type="button"
|
||||
className="text-blue-400 hover:text-destructive opacity-0 group-hover:opacity-100 transition-opacity"
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(att.networkPath!);
|
||||
}}
|
||||
title="Copiază calea"
|
||||
>
|
||||
<Link2 className="h-3 w-3" />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => removeAttachment(att.id)}
|
||||
className="text-destructive opacity-0 group-hover:opacity-100 transition-opacity"
|
||||
>
|
||||
<X className="h-3.5 w-3.5" />
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
// Normal uploaded file attachment
|
||||
<div
|
||||
key={att.id}
|
||||
className="flex items-center gap-2 rounded border px-2 py-1 text-sm"
|
||||
>
|
||||
<Paperclip className="h-3 w-3 text-muted-foreground" />
|
||||
<span className="flex-1 truncate">{att.name}</span>
|
||||
<Badge variant="outline" className="text-[10px]">
|
||||
{(att.size / 1024).toFixed(0)} KB
|
||||
</Badge>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => removeAttachment(att.id)}
|
||||
className="text-destructive"
|
||||
>
|
||||
<X className="h-3.5 w-3.5" />
|
||||
</button>
|
||||
</div>
|
||||
),
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user