feat(properties): list view renders parent/child as a depth-first tree
The flat list ordered by updatedAt scattered apartments away from their building. Server-side flatten now does a depth-first walk — parents render immediately above their children, alphabetical at each level — and tags each row with its depth. The UI indents the name column by 1.5rem per level and prefixes children with "└" for a visible parent/child line. Orphan rows (parent_id pointing outside the live company set) fall back to root depth so nothing is silently dropped, even though the restrict-on-delete FK should prevent that case. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,9 +1,53 @@
|
|||||||
import { error } from '@sveltejs/kit';
|
import { error } from '@sveltejs/kit';
|
||||||
import { listProperties } from '$lib/server/services/properties';
|
import { listProperties } from '$lib/server/services/properties';
|
||||||
|
import type { Property } from '$lib/server/db/schema/properties';
|
||||||
import type { PageServerLoad } from './$types';
|
import type { PageServerLoad } from './$types';
|
||||||
|
|
||||||
|
export type PropertyRow = Property & { depth: number };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reorder the flat company-scoped property list into a depth-first traversal
|
||||||
|
* so parents render immediately above their children. Within each level we
|
||||||
|
* sort by name. Orphan rows (parent_id points outside the visible set —
|
||||||
|
* shouldn't happen with the current restrict-on-delete policy, but defended
|
||||||
|
* here so the UI never silently drops a row) are appended at the end as roots.
|
||||||
|
*/
|
||||||
|
function flattenTree(rows: Property[]): PropertyRow[] {
|
||||||
|
const byParent = new Map<string | null, Property[]>();
|
||||||
|
for (const r of rows) {
|
||||||
|
const key = r.parentId;
|
||||||
|
const list = byParent.get(key);
|
||||||
|
if (list) list.push(r);
|
||||||
|
else byParent.set(key, [r]);
|
||||||
|
}
|
||||||
|
for (const list of byParent.values()) {
|
||||||
|
list.sort((a, b) => a.name.localeCompare(b.name));
|
||||||
|
}
|
||||||
|
|
||||||
|
const out: PropertyRow[] = [];
|
||||||
|
const visible = new Set(rows.map((r) => r.id));
|
||||||
|
function walk(parentId: string | null, depth: number): void {
|
||||||
|
const list = byParent.get(parentId) ?? [];
|
||||||
|
for (const r of list) {
|
||||||
|
out.push({ ...r, depth });
|
||||||
|
walk(r.id, depth + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
walk(null, 0);
|
||||||
|
|
||||||
|
if (out.length < rows.length) {
|
||||||
|
const seen = new Set(out.map((r) => r.id));
|
||||||
|
for (const r of rows) {
|
||||||
|
if (!seen.has(r.id) && (!r.parentId || !visible.has(r.parentId))) {
|
||||||
|
out.push({ ...r, depth: 0 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
export const load: PageServerLoad = async ({ locals }) => {
|
export const load: PageServerLoad = async ({ locals }) => {
|
||||||
if (!locals.company) throw error(400, 'No active company. Pick one from the sidebar.');
|
if (!locals.company) throw error(400, 'No active company. Pick one from the sidebar.');
|
||||||
const rows = await listProperties(locals.company.id);
|
const flat = await listProperties(locals.company.id);
|
||||||
return { properties: rows };
|
return { properties: flattenTree(flat) };
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -42,7 +42,9 @@
|
|||||||
<tbody class="divide-y divide-gray-200 dark:divide-gray-700">
|
<tbody class="divide-y divide-gray-200 dark:divide-gray-700">
|
||||||
{#each data.properties as p}
|
{#each data.properties as p}
|
||||||
<tr class="hover:bg-gray-50 dark:hover:bg-gray-700/30">
|
<tr class="hover:bg-gray-50 dark:hover:bg-gray-700/30">
|
||||||
<td class="px-4 py-2 text-sm font-medium text-gray-900 dark:text-gray-100">
|
<td class="py-2 pr-4 text-sm font-medium text-gray-900 dark:text-gray-100"
|
||||||
|
style:padding-left="{1 + p.depth * 1.5}rem">
|
||||||
|
{#if p.depth > 0}<span class="mr-1 select-none text-gray-400 dark:text-gray-500">└</span>{/if}
|
||||||
<a href="/properties/{p.id}" class="hover:text-primary-600 dark:hover:text-primary-400">{p.name}</a>
|
<a href="/properties/{p.id}" class="hover:text-primary-600 dark:hover:text-primary-400">{p.name}</a>
|
||||||
</td>
|
</td>
|
||||||
<td class="px-4 py-2 text-sm text-gray-500 dark:text-gray-400">{p.kind ?? '—'}</td>
|
<td class="px-4 py-2 text-sm text-gray-500 dark:text-gray-400">{p.kind ?? '—'}</td>
|
||||||
|
|||||||
Reference in New Issue
Block a user