9102ffd8b4
Deploy to LXC / deploy (push) Successful in 18s
- Device/component detail pages show "Parent › Child" for locations - Device list cards show full location path - Location edit form now includes a Parent selector to move locations between parents or make them top-level - Prevents setting a location as its own parent Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
99 lines
3.1 KiB
TypeScript
99 lines
3.1 KiB
TypeScript
import type { PageServerLoad, Actions } from './$types';
|
|
import { db } from '$lib/server/db/index.js';
|
|
import { locations, devices, components } from '$lib/server/db/schema.js';
|
|
import { eq, count, isNull } from 'drizzle-orm';
|
|
import { fail } from '@sveltejs/kit';
|
|
import { z } from 'zod';
|
|
|
|
export const load: PageServerLoad = async () => {
|
|
const locationList = await db
|
|
.select({
|
|
id: locations.id,
|
|
name: locations.name,
|
|
description: locations.description,
|
|
parentId: locations.parentId
|
|
})
|
|
.from(locations)
|
|
.orderBy(locations.name);
|
|
|
|
// Count devices and components per location
|
|
const deviceCounts = await db
|
|
.select({ locationId: devices.locationId, count: count() })
|
|
.from(devices)
|
|
.groupBy(devices.locationId);
|
|
|
|
const componentCounts = await db
|
|
.select({ locationId: components.locationId, count: count() })
|
|
.from(components)
|
|
.where(isNull(components.currentDeviceId))
|
|
.groupBy(components.locationId);
|
|
|
|
const dcMap: Record<string, number> = {};
|
|
for (const r of deviceCounts) {
|
|
if (r.locationId) dcMap[r.locationId] = r.count;
|
|
}
|
|
const ccMap: Record<string, number> = {};
|
|
for (const r of componentCounts) {
|
|
if (r.locationId) ccMap[r.locationId] = r.count;
|
|
}
|
|
|
|
return {
|
|
locations: locationList.map((l) => ({
|
|
...l,
|
|
deviceCount: dcMap[l.id] ?? 0,
|
|
componentCount: ccMap[l.id] ?? 0
|
|
}))
|
|
};
|
|
};
|
|
|
|
const locationSchema = z.object({
|
|
name: z.string().min(1, 'Name is required'),
|
|
description: z.string().optional(),
|
|
parentId: z.string().uuid().optional().or(z.literal(''))
|
|
});
|
|
|
|
export const actions: Actions = {
|
|
create: async ({ request }) => {
|
|
const formData = await request.formData();
|
|
const raw = Object.fromEntries(formData);
|
|
const result = locationSchema.safeParse(raw);
|
|
if (!result.success) {
|
|
return fail(400, { error: result.error.errors[0]?.message });
|
|
}
|
|
await db.insert(locations).values({
|
|
name: result.data.name,
|
|
description: result.data.description || null,
|
|
parentId: result.data.parentId || null
|
|
});
|
|
return { created: true };
|
|
},
|
|
|
|
rename: async ({ request }) => {
|
|
const formData = await request.formData();
|
|
const id = formData.get('id') as string;
|
|
const name = (formData.get('name') as string)?.trim();
|
|
const description = (formData.get('description') as string)?.trim();
|
|
const parentId = (formData.get('parentId') as string) || null;
|
|
if (!name) return fail(400, { error: 'Name is required' });
|
|
|
|
// Prevent setting self as parent
|
|
if (parentId === id) return fail(400, { error: 'Cannot set location as its own parent' });
|
|
|
|
await db
|
|
.update(locations)
|
|
.set({ name, description: description || null, parentId, updatedAt: new Date() })
|
|
.where(eq(locations.id, id));
|
|
return { renamed: true };
|
|
},
|
|
|
|
delete: async ({ request }) => {
|
|
const formData = await request.formData();
|
|
const id = formData.get('id') as string;
|
|
// Clear references first
|
|
await db.update(devices).set({ locationId: null }).where(eq(devices.locationId, id));
|
|
await db.update(components).set({ locationId: null }).where(eq(components.locationId, id));
|
|
await db.delete(locations).where(eq(locations.id, id));
|
|
return { deleted: true };
|
|
}
|
|
};
|