diff --git a/src/lib/server/db/schema.ts b/src/lib/server/db/schema.ts index 5caaf1f..3f14bfa 100644 --- a/src/lib/server/db/schema.ts +++ b/src/lib/server/db/schema.ts @@ -170,6 +170,24 @@ export const componentImages = pgTable( (table) => [index('component_images_component_idx').on(table.componentId)] ); +// ─── Component Documents ──────────────────────────────────────────── + +export const componentDocuments = pgTable( + 'component_documents', + { + id: uuid('id').defaultRandom().primaryKey(), + componentId: uuid('component_id') + .notNull() + .references(() => components.id, { onDelete: 'cascade' }), + filePath: text('file_path').notNull(), + originalFilename: text('original_filename').notNull(), + fileType: text('file_type'), + description: text('description'), + uploadedAt: timestamp('uploaded_at', { withTimezone: true }).defaultNow().notNull() + }, + (table) => [index('component_documents_component_idx').on(table.componentId)] +); + // ─── Installation Log (append-only) ───────────────────────────────── export const installationLog = pgTable( diff --git a/src/routes/(app)/components/[id]/+page.server.ts b/src/routes/(app)/components/[id]/+page.server.ts index c38da76..9d405d7 100644 --- a/src/routes/(app)/components/[id]/+page.server.ts +++ b/src/routes/(app)/components/[id]/+page.server.ts @@ -1,8 +1,9 @@ -import type { PageServerLoad } from './$types'; +import type { PageServerLoad, Actions } from './$types'; import { db } from '$lib/server/db/index.js'; -import { components, devices, locations, installationLog, componentImages } from '$lib/server/db/schema.js'; +import { components, devices, locations, installationLog, componentImages, componentDocuments } from '$lib/server/db/schema.js'; import { eq, desc } from 'drizzle-orm'; -import { error } from '@sveltejs/kit'; +import { error, fail } from '@sveltejs/kit'; +import { saveDocument, deleteFile } from '$lib/server/uploads.js'; export const load: PageServerLoad = async ({ params }) => { const [component] = await db @@ -36,6 +37,11 @@ export const load: PageServerLoad = async ({ params }) => { .from(componentImages) .where(eq(componentImages.componentId, params.id)); + const documents = await db + .select() + .from(componentDocuments) + .where(eq(componentDocuments.componentId, params.id)); + const history = await db .select({ id: installationLog.id, @@ -51,5 +57,39 @@ export const load: PageServerLoad = async ({ params }) => { .where(eq(installationLog.componentId, params.id)) .orderBy(desc(installationLog.performedAt)); - return { component, images, history }; + return { component, images, documents, history }; +}; + +export const actions: Actions = { + uploadDocument: async ({ request, params }) => { + const formData = await request.formData(); + const file = formData.get('document') as File; + if (!file || file.size === 0) return fail(400, { error: 'No file selected' }); + + const { filePath, originalFilename } = await saveDocument(file); + const description = formData.get('description') as string; + + await db.insert(componentDocuments).values({ + componentId: params.id, + filePath, + originalFilename, + fileType: file.type, + description: description || null + }); + + return { documentUploaded: true }; + }, + + deleteDocument: async ({ request }) => { + const formData = await request.formData(); + const docId = formData.get('docId') as string; + + const [doc] = await db.select().from(componentDocuments).where(eq(componentDocuments.id, docId)); + if (doc) { + await deleteFile(doc.filePath); + await db.delete(componentDocuments).where(eq(componentDocuments.id, docId)); + } + + return { documentDeleted: true }; + } }; diff --git a/src/routes/(app)/components/[id]/+page.svelte b/src/routes/(app)/components/[id]/+page.svelte index f519573..5521732 100644 --- a/src/routes/(app)/components/[id]/+page.svelte +++ b/src/routes/(app)/components/[id]/+page.svelte @@ -1,8 +1,10 @@ @@ -142,6 +144,52 @@ + + + + Documents + (showDocForm = !showDocForm)} + class="text-sm text-blue-600 hover:text-blue-700 dark:text-blue-400"> + {showDocForm ? 'Cancel' : 'Upload'} + + + + {#if showDocForm} + + + + Upload + + {/if} + + {#if data.documents.length === 0} + No documents. + {:else} + + {#each data.documents as doc} + + + + + + {doc.originalFilename} + + + + + + + + + + + {/each} + + {/if} + + {#if c.notes} Notes
No documents.