Files
buildfor_life_repair/src/routes/(app)/locations/+page.server.ts
T
grabowski 9102ffd8b4
Deploy to LXC / deploy (push) Successful in 18s
Show parent > child location on detail pages, add location move
- 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>
2026-04-07 17:07:08 +07:00

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 };
}
};