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 -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 pageSize = 24;
const conditions = [];
const conditions = [eq(components.disabled, false)];
if (type) conditions.push(eq(components.componentType, type));
if (search) {
conditions.push(
@@ -2,7 +2,7 @@ import type { PageServerLoad, Actions } from './$types';
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 } from '@sveltejs/kit';
import { error, fail, redirect } from '@sveltejs/kit';
import { saveDocument, deleteFile } from '$lib/server/uploads.js';
export const load: PageServerLoad = async ({ params }) => {
@@ -12,6 +12,7 @@ export const load: PageServerLoad = async ({ params }) => {
.where(eq(components.id, params.id));
if (!component) error(404, 'Component not found');
if (component.disabled) error(404, 'Component not found');
// All instances with their device/location info
const instances = await db
@@ -161,5 +162,14 @@ export const actions: Actions = {
}
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 showAddInstances = $state(false);
let showDocForm = $state(false);
let showDeleteConfirm = $state(false);
const totalCount = $derived(data.instances.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">
Print
</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>
{#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}
<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}
@@ -35,6 +35,7 @@ export const load: PageServerLoad = async ({ url }) => {
})
.from(componentInstances)
.innerJoin(components, eq(componentInstances.componentId, components.id))
.where(eq(components.disabled, false))
.orderBy(components.title, componentInstances.instanceNumber);
const locationList = await db