Files
buildfor_life_repair/src/routes/(app)/devices/+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

108 lines
3.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import type { PageServerLoad } from './$types';
import { db } from '$lib/server/db/index.js';
import { devices, deviceImages, locations } from '$lib/server/db/schema.js';
import { eq, ilike, or, count, desc, and, sql } from 'drizzle-orm';
export const load: PageServerLoad = async ({ url }) => {
const category = url.searchParams.get('category');
const condition = url.searchParams.get('condition');
const search = url.searchParams.get('q');
const page = Math.max(1, Number(url.searchParams.get('page') ?? 1));
const pageSize = 24;
const conditions = [eq(devices.disabled, false)];
if (category) {
conditions.push(eq(devices.category, category));
}
if (condition === 'needs-repair') {
conditions.push(
or(eq(devices.condition, 'In Repair'), eq(devices.condition, 'Waiting for Repair'))!
);
} else if (condition) {
conditions.push(eq(devices.condition, condition));
}
if (search) {
conditions.push(
or(
ilike(devices.title, `%${search}%`),
ilike(devices.brand, `%${search}%`),
ilike(devices.model, `%${search}%`),
ilike(devices.serialNumber, `%${search}%`)
)!
);
}
const where = conditions.length > 0 ? and(...conditions) : undefined;
const [totalResult] = await db.select({ value: count() }).from(devices).where(where);
const total = totalResult?.value ?? 0;
const deviceList = await db
.select({
id: devices.id,
title: devices.title,
category: devices.category,
brand: devices.brand,
model: devices.model,
condition: devices.condition,
year: devices.year,
locationName: locations.name,
locationParentId: locations.parentId
})
.from(devices)
.leftJoin(locations, eq(devices.locationId, locations.id))
.where(where)
.orderBy(desc(devices.updatedAt))
.limit(pageSize)
.offset((page - 1) * pageSize);
// Resolve parent location names
const parentIds = [...new Set(deviceList.filter(d => d.locationParentId).map(d => d.locationParentId!))];
let parentNameMap: Record<string, string> = {};
if (parentIds.length > 0) {
const parents = await db
.select({ id: locations.id, name: locations.name })
.from(locations)
.where(sql`${locations.id} IN ${parentIds}`);
for (const p of parents) parentNameMap[p.id] = p.name;
}
// Fetch first image for each device
const deviceIds = deviceList.map((d) => d.id);
let imageMap: Record<string, string> = {};
if (deviceIds.length > 0) {
const images = await db
.select({
deviceId: deviceImages.deviceId,
thumbnailPath: deviceImages.thumbnailPath,
filePath: deviceImages.filePath
})
.from(deviceImages)
.where(sql`${deviceImages.deviceId} IN ${deviceIds}`)
.orderBy(deviceImages.sortOrder);
for (const img of images) {
if (!imageMap[img.deviceId]) {
imageMap[img.deviceId] = img.thumbnailPath ?? img.filePath;
}
}
}
return {
devices: deviceList.map((d) => ({
...d,
thumbnail: imageMap[d.id] ?? null,
fullLocation: d.locationName
? (d.locationParentId && parentNameMap[d.locationParentId]
? `${parentNameMap[d.locationParentId]} ${d.locationName}`
: d.locationName)
: null
})),
total,
page,
pageSize,
filters: { category, condition, search }
};
};