Add soft delete for components (disabled flag)
Deploy to LXC / deploy (push) Successful in 22s

- disabled boolean on components table (default false)
- Component list filters out disabled components
- Component detail returns 404 for disabled
- Delete button with confirmation on component detail page
- Installation form filters out instances of disabled components

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-09 14:50:18 +07:00
parent 5c4595ed16
commit 9a59213da0
5 changed files with 34 additions and 2 deletions
+1
View File
@@ -138,6 +138,7 @@ export const components = pgTable(
defaultCondition: text('default_condition').notNull().default('Working'), defaultCondition: text('default_condition').notNull().default('Working'),
defaultFirmwareVersion: text('default_firmware_version'), defaultFirmwareVersion: text('default_firmware_version'),
defaultLocationId: uuid('default_location_id').references(() => locations.id), defaultLocationId: uuid('default_location_id').references(() => locations.id),
disabled: boolean('disabled').default(false).notNull(),
createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(), createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(),
updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull() updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull()
}, },
+1 -1
View File
@@ -9,7 +9,7 @@ export const load: PageServerLoad = async ({ url }) => {
const page = Math.max(1, Number(url.searchParams.get('page') ?? 1)); const page = Math.max(1, Number(url.searchParams.get('page') ?? 1));
const pageSize = 24; const pageSize = 24;
const conditions = []; const conditions = [eq(components.disabled, false)];
if (type) conditions.push(eq(components.componentType, type)); if (type) conditions.push(eq(components.componentType, type));
if (search) { if (search) {
conditions.push( conditions.push(
@@ -2,7 +2,7 @@ import type { PageServerLoad, Actions } from './$types';
import { db } from '$lib/server/db/index.js'; import { db } from '$lib/server/db/index.js';
import { components, componentInstances, devices, locations, installationLog, componentImages, componentDocuments } from '$lib/server/db/schema.js'; import { components, componentInstances, devices, locations, installationLog, componentImages, componentDocuments } from '$lib/server/db/schema.js';
import { eq, desc } from 'drizzle-orm'; import { eq, desc } from 'drizzle-orm';
import { error, fail } from '@sveltejs/kit'; import { error, fail, redirect } from '@sveltejs/kit';
import { saveDocument, deleteFile } from '$lib/server/uploads.js'; import { saveDocument, deleteFile } from '$lib/server/uploads.js';
export const load: PageServerLoad = async ({ params }) => { export const load: PageServerLoad = async ({ params }) => {
@@ -12,6 +12,7 @@ export const load: PageServerLoad = async ({ params }) => {
.where(eq(components.id, params.id)); .where(eq(components.id, params.id));
if (!component) error(404, 'Component not found'); if (!component) error(404, 'Component not found');
if (component.disabled) error(404, 'Component not found');
// All instances with their device/location info // All instances with their device/location info
const instances = await db const instances = await db
@@ -161,5 +162,14 @@ export const actions: Actions = {
} }
return { documentDeleted: true }; return { documentDeleted: true };
},
disable: async ({ params }) => {
await db
.update(components)
.set({ disabled: true, updatedAt: new Date() })
.where(eq(components.id, params.id));
redirect(303, '/components');
} }
}; };
@@ -9,6 +9,7 @@
let editingInstanceId = $state<string | null>(null); let editingInstanceId = $state<string | null>(null);
let showAddInstances = $state(false); let showAddInstances = $state(false);
let showDocForm = $state(false); let showDocForm = $state(false);
let showDeleteConfirm = $state(false);
const totalCount = $derived(data.instances.length); const totalCount = $derived(data.instances.length);
const installedCount = $derived(data.instances.filter((i) => i.currentDeviceId).length); const installedCount = $derived(data.instances.filter((i) => i.currentDeviceId).length);
@@ -56,9 +57,28 @@
class="rounded-md border border-gray-300 px-3 py-1.5 text-sm text-gray-600 hover:bg-gray-100 dark:border-gray-600 dark:text-gray-400 dark:hover:bg-gray-700"> class="rounded-md border border-gray-300 px-3 py-1.5 text-sm text-gray-600 hover:bg-gray-100 dark:border-gray-600 dark:text-gray-400 dark:hover:bg-gray-700">
Print Print
</button> </button>
<button type="button" onclick={() => (showDeleteConfirm = !showDeleteConfirm)}
class="rounded-md border border-red-300 px-3 py-1.5 text-sm text-red-600 hover:bg-red-50 dark:border-red-700 dark:text-red-400 dark:hover:bg-red-900/20">
Delete
</button>
</div> </div>
</div> </div>
{#if showDeleteConfirm}
<div class="mb-6 rounded-lg border border-red-200 bg-red-50 p-5 dark:border-red-800 dark:bg-red-900/20">
<p class="mb-3 text-sm text-red-700 dark:text-red-300">Are you sure you want to delete <strong>{c.title}</strong>? This will hide it from all listings.</p>
<div class="flex gap-2">
<form method="POST" action="?/disable" use:enhance>
<button type="submit" class="rounded-md bg-red-600 px-4 py-2 text-sm font-medium text-white hover:bg-red-700">Yes, delete</button>
</form>
<button type="button" onclick={() => (showDeleteConfirm = false)}
class="rounded-md px-3 py-2 text-sm text-gray-600 hover:bg-gray-100 dark:text-gray-400 dark:hover:bg-gray-700">
Cancel
</button>
</div>
</div>
{/if}
{#if form?.error} {#if form?.error}
<div class="mb-4 rounded-md bg-red-50 p-3 text-sm text-red-700 dark:bg-red-900/30 dark:text-red-300">{form.error}</div> <div class="mb-4 rounded-md bg-red-50 p-3 text-sm text-red-700 dark:bg-red-900/30 dark:text-red-300">{form.error}</div>
{/if} {/if}
@@ -35,6 +35,7 @@ export const load: PageServerLoad = async ({ url }) => {
}) })
.from(componentInstances) .from(componentInstances)
.innerJoin(components, eq(componentInstances.componentId, components.id)) .innerJoin(components, eq(componentInstances.componentId, components.id))
.where(eq(components.disabled, false))
.orderBy(components.title, componentInstances.instanceNumber); .orderBy(components.title, componentInstances.instanceNumber);
const locationList = await db const locationList = await db