fix: guard against undefined fields when loading old localStorage data

Old entries in localStorage lack new fields (department, role, contactPersons,
linkedEntryIds, attachments, versions, placeholders, ipAddress, vendor, model).
Add null-coalescing guards to prevent client-side crashes on property access.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Marius Tarau
2026-02-18 06:49:08 +02:00
parent 2c9f0bc6b7
commit f0b878cf00
9 changed files with 22 additions and 21 deletions

View File

@@ -174,7 +174,7 @@ function ContactCard({ contact, onEdit, onDelete }: {
<Globe className="h-3 w-3 shrink-0" /><span className="truncate">{contact.website}</span> <Globe className="h-3 w-3 shrink-0" /><span className="truncate">{contact.website}</span>
</div> </div>
)} )}
{contact.contactPersons.length > 0 && ( {(contact.contactPersons ?? []).length > 0 && (
<div className="mt-1 border-t pt-1"> <div className="mt-1 border-t pt-1">
<p className="text-[10px] font-medium text-muted-foreground uppercase tracking-wider mb-1"> <p className="text-[10px] font-medium text-muted-foreground uppercase tracking-wider mb-1">
Persoane de contact ({contact.contactPersons.length}) Persoane de contact ({contact.contactPersons.length})

View File

@@ -76,8 +76,8 @@ export function useContacts() {
c.company.toLowerCase().includes(q) || c.company.toLowerCase().includes(q) ||
c.email.toLowerCase().includes(q) || c.email.toLowerCase().includes(q) ||
c.phone.includes(q) || c.phone.includes(q) ||
c.department.toLowerCase().includes(q) || (c.department ?? '').toLowerCase().includes(q) ||
c.role.toLowerCase().includes(q) (c.role ?? '').toLowerCase().includes(q)
); );
} }
return true; return true;

View File

@@ -157,8 +157,8 @@ export function DigitalSignaturesModule() {
{asset.usageNotes && ( {asset.usageNotes && (
<p className="text-xs text-muted-foreground line-clamp-1">Note: {asset.usageNotes}</p> <p className="text-xs text-muted-foreground line-clamp-1">Note: {asset.usageNotes}</p>
)} )}
{asset.versions.length > 0 && ( {(asset.versions ?? []).length > 0 && (
<p className="text-xs text-muted-foreground">Versiuni: {asset.versions.length + 1}</p> <p className="text-xs text-muted-foreground">Versiuni: {(asset.versions ?? []).length + 1}</p>
)} )}
</div> </div>
</CardContent> </CardContent>

View File

@@ -60,7 +60,7 @@ export function useSignatures() {
const existing = assets.find((a) => a.id === assetId); const existing = assets.find((a) => a.id === assetId);
if (!existing) return; if (!existing) return;
const version: AssetVersion = { id: uuid(), imageUrl, notes, createdAt: new Date().toISOString() }; const version: AssetVersion = { id: uuid(), imageUrl, notes, createdAt: new Date().toISOString() };
const updatedVersions = [...existing.versions, version]; const updatedVersions = [...(existing.versions ?? []), version];
await updateAsset(assetId, { imageUrl, versions: updatedVersions }); await updateAsset(assetId, { imageUrl, versions: updatedVersions });
}, [assets, updateAsset]); }, [assets, updateAsset]);

View File

@@ -79,9 +79,9 @@ export function useInventory() {
item.name.toLowerCase().includes(q) || item.name.toLowerCase().includes(q) ||
item.serialNumber.toLowerCase().includes(q) || item.serialNumber.toLowerCase().includes(q) ||
item.assignedTo.toLowerCase().includes(q) || item.assignedTo.toLowerCase().includes(q) ||
item.ipAddress.toLowerCase().includes(q) || (item.ipAddress ?? '').toLowerCase().includes(q) ||
item.vendor.toLowerCase().includes(q) || (item.vendor ?? '').toLowerCase().includes(q) ||
item.model.toLowerCase().includes(q) (item.model ?? '').toLowerCase().includes(q)
); );
} }
return true; return true;

View File

@@ -50,7 +50,7 @@ export function RegistraturaModule() {
const handleCloseRequest = (id: string) => { const handleCloseRequest = (id: string) => {
const entry = allEntries.find((e) => e.id === id); const entry = allEntries.find((e) => e.id === id);
if (entry && entry.linkedEntryIds.length > 0) { if (entry && (entry.linkedEntryIds ?? []).length > 0) {
setClosingId(id); setClosingId(id);
} else { } else {
closeEntry(id, false); closeEntry(id, false);
@@ -158,7 +158,7 @@ export function RegistraturaModule() {
</DialogHeader> </DialogHeader>
<div className="py-2"> <div className="py-2">
<p className="text-sm"> <p className="text-sm">
Această înregistrare are {closingEntry?.linkedEntryIds.length ?? 0} înregistrări legate. Această înregistrare are {closingEntry?.linkedEntryIds?.length ?? 0} înregistrări legate.
Vrei le închizi și pe acestea? Vrei le închizi și pe acestea?
</p> </p>
</div> </div>

View File

@@ -69,7 +69,7 @@ export function RegistryTable({ entries, loading, onEdit, onDelete, onClose }: R
</thead> </thead>
<tbody> <tbody>
{entries.map((entry) => { {entries.map((entry) => {
const overdueDays = entry.status === 'deschis' ? getOverdueDays(entry.deadline) : null; const overdueDays = (entry.status === 'deschis' || !entry.status) ? getOverdueDays(entry.deadline) : null;
const isOverdue = overdueDays !== null && overdueDays > 0; const isOverdue = overdueDays !== null && overdueDays > 0;
return ( return (
<tr <tr
@@ -86,16 +86,16 @@ export function RegistryTable({ entries, loading, onEdit, onDelete, onClose }: R
variant={entry.direction === 'intrat' ? 'default' : 'secondary'} variant={entry.direction === 'intrat' ? 'default' : 'secondary'}
className="text-xs" className="text-xs"
> >
{DIRECTION_LABELS[entry.direction]} {DIRECTION_LABELS[entry.direction] ?? entry.direction ?? '—'}
</Badge> </Badge>
</td> </td>
<td className="px-3 py-2 text-xs">{DOC_TYPE_LABELS[entry.documentType]}</td> <td className="px-3 py-2 text-xs">{DOC_TYPE_LABELS[entry.documentType] ?? entry.documentType ?? '—'}</td>
<td className="px-3 py-2 max-w-[200px] truncate"> <td className="px-3 py-2 max-w-[200px] truncate">
{entry.subject} {entry.subject}
{entry.linkedEntryIds.length > 0 && ( {(entry.linkedEntryIds ?? []).length > 0 && (
<Link2 className="ml-1 inline h-3 w-3 text-muted-foreground" /> <Link2 className="ml-1 inline h-3 w-3 text-muted-foreground" />
)} )}
{entry.attachments.length > 0 && ( {(entry.attachments ?? []).length > 0 && (
<Badge variant="outline" className="ml-1 text-[10px] px-1"> <Badge variant="outline" className="ml-1 text-[10px] px-1">
{entry.attachments.length} fișiere {entry.attachments.length} fișiere
</Badge> </Badge>

View File

@@ -76,8 +76,9 @@ export function useRegistry() {
const entry = entries.find((e) => e.id === id); const entry = entries.find((e) => e.id === id);
if (!entry) return; if (!entry) return;
await updateEntry(id, { status: 'inchis' }); await updateEntry(id, { status: 'inchis' });
if (closeLinked && entry.linkedEntryIds.length > 0) { const linked = entry.linkedEntryIds ?? [];
for (const linkedId of entry.linkedEntryIds) { if (closeLinked && linked.length > 0) {
for (const linkedId of linked) {
const linked = entries.find((e) => e.id === linkedId); const linked = entries.find((e) => e.id === linkedId);
if (linked && linked.status !== 'inchis') { if (linked && linked.status !== 'inchis') {
const updatedLinked: RegistryEntry = { const updatedLinked: RegistryEntry = {

View File

@@ -124,9 +124,9 @@ export function WordTemplatesModule() {
{tpl.clonedFrom && <Badge variant="secondary" className="text-[10px]">Clonă</Badge>} {tpl.clonedFrom && <Badge variant="secondary" className="text-[10px]">Clonă</Badge>}
</div> </div>
{/* Placeholders display */} {/* Placeholders display */}
{tpl.placeholders.length > 0 && ( {(tpl.placeholders ?? []).length > 0 && (
<div className="mt-1.5 flex flex-wrap gap-1"> <div className="mt-1.5 flex flex-wrap gap-1">
{tpl.placeholders.map((p) => ( {(tpl.placeholders ?? []).map((p) => (
<span key={p} className="rounded bg-muted px-1 py-0.5 font-mono text-[10px] text-muted-foreground">{`{{${p}}}`}</span> <span key={p} className="rounded bg-muted px-1 py-0.5 font-mono text-[10px] text-muted-foreground">{`{{${p}}}`}</span>
))} ))}
</div> </div>
@@ -181,7 +181,7 @@ function TemplateForm({ initial, onSubmit, onCancel }: {
const [fileUrl, setFileUrl] = useState(initial?.fileUrl ?? ''); const [fileUrl, setFileUrl] = useState(initial?.fileUrl ?? '');
const [company, setCompany] = useState<CompanyId>(initial?.company ?? 'beletage'); const [company, setCompany] = useState<CompanyId>(initial?.company ?? 'beletage');
const [version, setVersion] = useState(initial?.version ?? '1.0.0'); const [version, setVersion] = useState(initial?.version ?? '1.0.0');
const [placeholdersText, setPlaceholdersText] = useState(initial?.placeholders.join(', ') ?? ''); const [placeholdersText, setPlaceholdersText] = useState((initial?.placeholders ?? []).join(', '));
return ( return (
<form onSubmit={(e) => { <form onSubmit={(e) => {