fix(ai): guard datasheet/gallery/videos against non-array values
Deploy to VPS / deploy (push) Has been cancelled
Deploy to VPS / deploy (push) Has been cancelled
EquipmentConfigurator and CaseStudyViewer crashed with "e.datasheet.find is not a function" when DB nodes had malformed JSON in specificDatasheetJson. Both components now normalize datasheet, gallery, and videos to safe arrays via Array.isArray before any array method calls. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -56,6 +56,11 @@ export default function CaseStudyViewer({ data }: { data: CaseStudyData }) {
|
||||
|
||||
if (!data.found) return null;
|
||||
|
||||
// Defensive: ensure datasheet/gallery/videos are always arrays
|
||||
const datasheet = Array.isArray(data.datasheet) ? data.datasheet : [];
|
||||
const gallery = Array.isArray(data.gallery) ? data.gallery : [];
|
||||
const videos = Array.isArray(data.videos) ? data.videos : [];
|
||||
|
||||
const accent = ACCENTS[data.industry] || ACCENTS.textile;
|
||||
const nodeSlug = nodeToSlug(data.title);
|
||||
const coverSrc = data.mediaFileName ? `/cases/${nodeSlug}/${data.mediaFileName}` : null;
|
||||
@@ -99,14 +104,14 @@ export default function CaseStudyViewer({ data }: { data: CaseStudyData }) {
|
||||
|
||||
{/* Media indicators */}
|
||||
<div className="absolute bottom-3 right-3 flex items-center gap-1.5 z-10">
|
||||
{data.gallery.length > 0 && (
|
||||
{gallery.length > 0 && (
|
||||
<div className="bg-black/40 backdrop-blur-md text-white text-[9px] px-2 py-0.5 rounded-full flex items-center gap-1">
|
||||
<ImageIcon size={9} /> {data.gallery.length}
|
||||
<ImageIcon size={9} /> {gallery.length}
|
||||
</div>
|
||||
)}
|
||||
{data.videos.length > 0 && (
|
||||
{videos.length > 0 && (
|
||||
<div className="bg-black/40 backdrop-blur-md text-white text-[9px] px-2 py-0.5 rounded-full flex items-center gap-1">
|
||||
<Play size={9} /> {data.videos.length}
|
||||
<Play size={9} /> {videos.length}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -133,8 +138,8 @@ export default function CaseStudyViewer({ data }: { data: CaseStudyData }) {
|
||||
)}
|
||||
<Metric icon={Clock} label="Performance" value={data.stats} />
|
||||
<Metric icon={MapPin} label="Region" value={data.location.split(",").pop()?.trim() || data.location} />
|
||||
{data.datasheet.length > 0 && (
|
||||
<Metric icon={FileText} label="Specs" value={`${data.datasheet.length} parameters`} />
|
||||
{datasheet.length > 0 && (
|
||||
<Metric icon={FileText} label="Specs" value={`${datasheet.length} parameters`} />
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -166,21 +171,21 @@ export default function CaseStudyViewer({ data }: { data: CaseStudyData }) {
|
||||
)}
|
||||
|
||||
{/* Equipment Datasheet (from specificDatasheetJson) */}
|
||||
{data.datasheet.length > 0 && (
|
||||
{datasheet.length > 0 && (
|
||||
<div className="bg-white/40 dark:bg-white/[0.03] border border-white/60 dark:border-white/[0.06] rounded-xl p-3 mb-3 transition-colors">
|
||||
<span className="text-[9px] text-[#86868B] dark:text-[#A1A1A6] uppercase tracking-wider font-semibold block mb-2">
|
||||
Equipment Specifications
|
||||
</span>
|
||||
<div className="flex flex-col gap-1">
|
||||
{data.datasheet.slice(0, 6).map((spec, i) => (
|
||||
{datasheet.slice(0, 6).map((spec, i) => (
|
||||
<div key={i} className="flex items-center justify-between py-1 border-b border-black/[0.03] dark:border-white/[0.04] last:border-0">
|
||||
<span className="text-[10px] text-[#86868B] dark:text-[#A1A1A6]">{spec.label}</span>
|
||||
<span className="text-[10px] font-medium text-[#1D1D1F] dark:text-[#F5F5F7]">{spec.value}</span>
|
||||
</div>
|
||||
))}
|
||||
{data.datasheet.length > 6 && (
|
||||
{datasheet.length > 6 && (
|
||||
<span className="text-[9px] text-[#86868B] dark:text-[#A1A1A6] text-center pt-1">
|
||||
+{data.datasheet.length - 6} more specs in full view
|
||||
+{datasheet.length - 6} more specs in full view
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
@@ -188,17 +193,17 @@ export default function CaseStudyViewer({ data }: { data: CaseStudyData }) {
|
||||
)}
|
||||
|
||||
{/* Gallery Preview */}
|
||||
{data.gallery.length > 0 && (
|
||||
{gallery.length > 0 && (
|
||||
<div className="mb-3">
|
||||
<button
|
||||
onClick={() => setShowGallery(!showGallery)}
|
||||
className="text-[10px] text-[#0066CC] dark:text-[#4DA6FF] font-medium flex items-center gap-1 mb-2"
|
||||
>
|
||||
<ImageIcon size={10} /> {showGallery ? 'Hide' : 'Show'} Gallery ({data.gallery.length} images)
|
||||
<ImageIcon size={10} /> {showGallery ? 'Hide' : 'Show'} Gallery ({gallery.length} images)
|
||||
</button>
|
||||
{showGallery && (
|
||||
<div className="grid grid-cols-3 gap-1.5 rounded-lg overflow-hidden">
|
||||
{data.gallery.slice(0, 6).map((img, i) => (
|
||||
{gallery.slice(0, 6).map((img, i) => (
|
||||
<div key={i} className="relative aspect-square bg-[#F5F5F7] dark:bg-[#1D1D1F] rounded-md overflow-hidden">
|
||||
<Image src={`/cases/${nodeSlug}/${img}`} alt={`Gallery ${i + 1}`} fill className="object-cover" sizes="120px" />
|
||||
</div>
|
||||
|
||||
@@ -49,9 +49,12 @@ export default function EquipmentConfigurator({ data }: { data: EquipmentData })
|
||||
const coverSrc = data.mediaFileName ? `/cases/${nodeSlug}/${data.mediaFileName}` : null;
|
||||
const appLabel = data.application.replace(/-/g, " ").replace(/\b\w/g, c => c.toUpperCase());
|
||||
|
||||
// Defensive: ensure datasheet is always an array (DB may store malformed JSON)
|
||||
const datasheet = Array.isArray(data.datasheet) ? data.datasheet : [];
|
||||
|
||||
// Find key specs for header pills (power, frequency, model — from datasheet)
|
||||
const findSpec = (keywords: string[]) => {
|
||||
return data.datasheet.find(s =>
|
||||
return datasheet.find(s =>
|
||||
keywords.some(kw => s.label.toLowerCase().includes(kw))
|
||||
)?.value;
|
||||
};
|
||||
@@ -61,8 +64,8 @@ export default function EquipmentConfigurator({ data }: { data: EquipmentData })
|
||||
const modelSpec = findSpec(['model', 'modelo', 'type', 'tipo', 'series']);
|
||||
|
||||
// Split datasheet into primary (first 4) and extended
|
||||
const primarySpecs = data.datasheet.slice(0, 4);
|
||||
const extendedSpecs = data.datasheet.slice(4);
|
||||
const primarySpecs = datasheet.slice(0, 4);
|
||||
const extendedSpecs = datasheet.slice(4);
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
@@ -175,7 +178,7 @@ export default function EquipmentConfigurator({ data }: { data: EquipmentData })
|
||||
>
|
||||
<span className="flex items-center gap-1.5">
|
||||
<Settings2 size={12} />
|
||||
All Specifications ({data.datasheet.length})
|
||||
All Specifications ({datasheet.length})
|
||||
</span>
|
||||
<ChevronDown size={14} className={`transition-transform duration-300 ${showAllSpecs ? "rotate-180" : ""}`} />
|
||||
</button>
|
||||
|
||||
Reference in New Issue
Block a user