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>
</div>
)}
{contact.contactPersons.length > 0 && (
{(contact.contactPersons ?? []).length > 0 && (
<div className="mt-1 border-t pt-1">
<p className="text-[10px] font-medium text-muted-foreground uppercase tracking-wider mb-1">
Persoane de contact ({contact.contactPersons.length})

View File

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

View File

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

View File

@@ -60,7 +60,7 @@ export function useSignatures() {
const existing = assets.find((a) => a.id === assetId);
if (!existing) return;
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 });
}, [assets, updateAsset]);

View File

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

View File

@@ -50,7 +50,7 @@ export function RegistraturaModule() {
const handleCloseRequest = (id: string) => {
const entry = allEntries.find((e) => e.id === id);
if (entry && entry.linkedEntryIds.length > 0) {
if (entry && (entry.linkedEntryIds ?? []).length > 0) {
setClosingId(id);
} else {
closeEntry(id, false);
@@ -158,7 +158,7 @@ export function RegistraturaModule() {
</DialogHeader>
<div className="py-2">
<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?
</p>
</div>

View File

@@ -69,7 +69,7 @@ export function RegistryTable({ entries, loading, onEdit, onDelete, onClose }: R
</thead>
<tbody>
{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;
return (
<tr
@@ -86,16 +86,16 @@ export function RegistryTable({ entries, loading, onEdit, onDelete, onClose }: R
variant={entry.direction === 'intrat' ? 'default' : 'secondary'}
className="text-xs"
>
{DIRECTION_LABELS[entry.direction]}
{DIRECTION_LABELS[entry.direction] ?? entry.direction ?? '—'}
</Badge>
</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">
{entry.subject}
{entry.linkedEntryIds.length > 0 && (
{(entry.linkedEntryIds ?? []).length > 0 && (
<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">
{entry.attachments.length} fișiere
</Badge>

View File

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

View File

@@ -124,9 +124,9 @@ export function WordTemplatesModule() {
{tpl.clonedFrom && <Badge variant="secondary" className="text-[10px]">Clonă</Badge>}
</div>
{/* Placeholders display */}
{tpl.placeholders.length > 0 && (
{(tpl.placeholders ?? []).length > 0 && (
<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>
))}
</div>
@@ -181,7 +181,7 @@ function TemplateForm({ initial, onSubmit, onCancel }: {
const [fileUrl, setFileUrl] = useState(initial?.fileUrl ?? '');
const [company, setCompany] = useState<CompanyId>(initial?.company ?? 'beletage');
const [version, setVersion] = useState(initial?.version ?? '1.0.0');
const [placeholdersText, setPlaceholdersText] = useState(initial?.placeholders.join(', ') ?? '');
const [placeholdersText, setPlaceholdersText] = useState((initial?.placeholders ?? []).join(', '));
return (
<form onSubmit={(e) => {