Component cards now display the first uploaded image as a thumbnail, matching the device list card design. Falls back to a placeholder icon when no image is uploaded. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
import type { PageServerLoad } from './$types';
|
import type { PageServerLoad } from './$types';
|
||||||
import { db } from '$lib/server/db/index.js';
|
import { db } from '$lib/server/db/index.js';
|
||||||
import { components, componentInstances, devices } from '$lib/server/db/schema.js';
|
import { components, componentInstances, componentImages } from '$lib/server/db/schema.js';
|
||||||
import { eq, ilike, or, and, isNull, isNotNull, count, desc, sql } from 'drizzle-orm';
|
import { eq, ilike, or, and, count, sql } from 'drizzle-orm';
|
||||||
|
|
||||||
export const load: PageServerLoad = async ({ url }) => {
|
export const load: PageServerLoad = async ({ url }) => {
|
||||||
const type = url.searchParams.get('type');
|
const type = url.searchParams.get('type');
|
||||||
@@ -60,9 +60,29 @@ export const load: PageServerLoad = async ({ url }) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get first image per component
|
||||||
|
let imageMap: Record<string, string> = {};
|
||||||
|
if (componentIds.length > 0) {
|
||||||
|
const images = await db
|
||||||
|
.select({
|
||||||
|
componentId: componentImages.componentId,
|
||||||
|
thumbnailPath: componentImages.thumbnailPath,
|
||||||
|
filePath: componentImages.filePath
|
||||||
|
})
|
||||||
|
.from(componentImages)
|
||||||
|
.where(sql`${componentImages.componentId} IN ${componentIds}`);
|
||||||
|
|
||||||
|
for (const img of images) {
|
||||||
|
if (!imageMap[img.componentId]) {
|
||||||
|
imageMap[img.componentId] = img.thumbnailPath ?? img.filePath;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
components: componentList.map((c) => ({
|
components: componentList.map((c) => ({
|
||||||
...c,
|
...c,
|
||||||
|
thumbnail: imageMap[c.id] ?? null,
|
||||||
total: instanceCounts[c.id]?.total ?? 0,
|
total: instanceCounts[c.id]?.total ?? 0,
|
||||||
installed: instanceCounts[c.id]?.installed ?? 0,
|
installed: instanceCounts[c.id]?.installed ?? 0,
|
||||||
available: (instanceCounts[c.id]?.total ?? 0) - (instanceCounts[c.id]?.installed ?? 0)
|
available: (instanceCounts[c.id]?.total ?? 0) - (instanceCounts[c.id]?.installed ?? 0)
|
||||||
|
|||||||
@@ -61,7 +61,18 @@
|
|||||||
<div class="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
|
<div class="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
|
||||||
{#each data.components as comp}
|
{#each data.components as comp}
|
||||||
<a href="/components/{comp.id}"
|
<a href="/components/{comp.id}"
|
||||||
class="group rounded-lg border border-gray-200 bg-white p-4 transition-shadow hover:shadow-md dark:border-gray-700 dark:bg-gray-800">
|
class="group rounded-lg border border-gray-200 bg-white transition-shadow hover:shadow-md dark:border-gray-700 dark:bg-gray-800 overflow-hidden">
|
||||||
|
<!-- Thumbnail -->
|
||||||
|
<div class="flex h-32 items-center justify-center bg-gray-100 dark:bg-gray-700">
|
||||||
|
{#if comp.thumbnail}
|
||||||
|
<img src={comp.thumbnail} alt={comp.title} class="h-full w-full object-cover" />
|
||||||
|
{:else}
|
||||||
|
<svg class="h-10 w-10 text-gray-300 dark:text-gray-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z" />
|
||||||
|
</svg>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
<div class="p-4">
|
||||||
<div class="mb-2 flex items-start justify-between">
|
<div class="mb-2 flex items-start justify-between">
|
||||||
<h3 class="font-medium text-gray-900 group-hover:text-blue-600 dark:text-white dark:group-hover:text-blue-400">{comp.title}</h3>
|
<h3 class="font-medium text-gray-900 group-hover:text-blue-600 dark:text-white dark:group-hover:text-blue-400">{comp.title}</h3>
|
||||||
<span class="flex-shrink-0 rounded-full bg-gray-100 px-2 py-0.5 text-xs text-gray-600 dark:bg-gray-700 dark:text-gray-400">
|
<span class="flex-shrink-0 rounded-full bg-gray-100 px-2 py-0.5 text-xs text-gray-600 dark:bg-gray-700 dark:text-gray-400">
|
||||||
@@ -83,6 +94,7 @@
|
|||||||
{comp.available} available
|
{comp.available} available
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</a>
|
</a>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user