From 29d2aa943ca219e22ade8d6d4030d75ac75c1679 Mon Sep 17 00:00:00 2001 From: grabowski Date: Thu, 9 Apr 2026 15:31:56 +0700 Subject: [PATCH] Add image uploads to components, fix sidebar count for disabled - Image upload/delete on component detail page (same pattern as devices) - Images section with grid, hover-to-delete, 50MB client-side limit - Sidebar component count now excludes disabled components by joining componentInstances with components and filtering disabled=false Co-Authored-By: Claude Opus 4.6 (1M context) --- src/routes/(app)/+layout.server.ts | 8 ++- .../(app)/components/[id]/+page.server.ts | 38 +++++++++++++- src/routes/(app)/components/[id]/+page.svelte | 50 +++++++++++++++++++ 3 files changed, 93 insertions(+), 3 deletions(-) diff --git a/src/routes/(app)/+layout.server.ts b/src/routes/(app)/+layout.server.ts index e9f2790..f731ddc 100644 --- a/src/routes/(app)/+layout.server.ts +++ b/src/routes/(app)/+layout.server.ts @@ -1,7 +1,7 @@ import type { LayoutServerLoad } from './$types'; import { redirect } from '@sveltejs/kit'; import { db } from '$lib/server/db/index.js'; -import { devices, componentInstances } from '$lib/server/db/schema.js'; +import { devices, components, componentInstances } from '$lib/server/db/schema.js'; import { count, eq, or, and } from 'drizzle-orm'; export const load: LayoutServerLoad = async ({ locals }) => { @@ -10,7 +10,11 @@ export const load: LayoutServerLoad = async ({ locals }) => { } const [deviceCount] = await db.select({ value: count() }).from(devices).where(eq(devices.disabled, false)); - const [componentCount] = await db.select({ value: count() }).from(componentInstances); + const [componentCount] = await db + .select({ value: count() }) + .from(componentInstances) + .innerJoin(components, eq(componentInstances.componentId, components.id)) + .where(eq(components.disabled, false)); const [repairCount] = await db .select({ value: count() }) .from(devices) diff --git a/src/routes/(app)/components/[id]/+page.server.ts b/src/routes/(app)/components/[id]/+page.server.ts index 17fa59a..6c570c9 100644 --- a/src/routes/(app)/components/[id]/+page.server.ts +++ b/src/routes/(app)/components/[id]/+page.server.ts @@ -3,7 +3,7 @@ import { db } from '$lib/server/db/index.js'; import { components, componentInstances, devices, locations, installationLog, componentImages, componentDocuments } from '$lib/server/db/schema.js'; import { eq, desc } from 'drizzle-orm'; import { error, fail, redirect } from '@sveltejs/kit'; -import { saveDocument, deleteFile } from '$lib/server/uploads.js'; +import { saveImage, saveDocument, deleteFile } from '$lib/server/uploads.js'; export const load: PageServerLoad = async ({ params }) => { const [component] = await db @@ -164,6 +164,42 @@ export const actions: Actions = { return { documentDeleted: true }; }, + uploadImage: async ({ request, params }) => { + const formData = await request.formData(); + const file = formData.get('image') as File; + if (!file || file.size === 0) return fail(400, { error: 'No file selected' }); + + try { + const { filePath, thumbnailPath } = await saveImage(file, 'components'); + const caption = formData.get('caption') as string; + + await db.insert(componentImages).values({ + componentId: params.id, + filePath, + thumbnailPath, + caption: caption || null + }); + + return { imageUploaded: true }; + } catch (err) { + return fail(400, { error: `Upload failed: ${err instanceof Error ? err.message : 'Unknown error'}` }); + } + }, + + deleteImage: async ({ request }) => { + const formData = await request.formData(); + const imageId = formData.get('imageId') as string; + + const [img] = await db.select().from(componentImages).where(eq(componentImages.id, imageId)); + if (img) { + await deleteFile(img.filePath); + if (img.thumbnailPath) await deleteFile(img.thumbnailPath); + await db.delete(componentImages).where(eq(componentImages.id, imageId)); + } + + return { imageDeleted: true }; + }, + disable: async ({ params }) => { await db .update(components) diff --git a/src/routes/(app)/components/[id]/+page.svelte b/src/routes/(app)/components/[id]/+page.svelte index 43bc9a3..95ef449 100644 --- a/src/routes/(app)/components/[id]/+page.svelte +++ b/src/routes/(app)/components/[id]/+page.svelte @@ -8,6 +8,7 @@ let editingInstanceId = $state(null); let showAddInstances = $state(false); + let showUploadForm = $state(false); let showDocForm = $state(false); let showDeleteConfirm = $state(false); @@ -85,6 +86,55 @@
+ +
+
+

Images

+ +
+ + {#if showUploadForm} +
+ { + const file = (e.target as HTMLInputElement).files?.[0]; + if (file && file.size > 50 * 1024 * 1024) { + alert('File too large. Maximum size is 50MB.'); + (e.target as HTMLInputElement).value = ''; + } + }} + class="text-sm text-gray-600 dark:text-gray-400" /> + + +
+ {/if} + + {#if data.images.length === 0} +

No images yet.

+ {:else} +
+ {#each data.images as img} +
+ {img.caption + +
+ {/each} +
+ {/if} +
+