feat(web): add error boundaries, loading skeletons, render fixes and tree-shaking

- Add error.tsx to all 13 route groups: admin, allocations, analytics, dashboard, estimates, notifications, projects, reports, resources, roles, staffing, timeline, vacations
- Add loading.tsx to 9 routes that were missing them: admin, analytics, dashboard, estimates, notifications, reports, roles, staffing, vacations
- ResourceDetail: wrap vacationStart in useMemo to stabilize query key, remove dead windowEnd variable
- node-renderer.ts: replace barrel import (import * as THREE) with named imports for tree-shaking
- next.config.ts: add framer-motion and @capakraken/shared to optimizePackageImports

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-09 08:35:28 +02:00
parent 1204c186ef
commit 472d87c829
25 changed files with 235 additions and 14 deletions
@@ -1,14 +1,14 @@
import * as THREE from "three";
import { CanvasTexture, Sprite, SpriteMaterial } from "three";
import type { PositionedNode } from "./graph-data";
// ─── Canvas-based node sprites ──────────────────────────────────────────────
const spriteCache = new Map<string, THREE.Sprite>();
const spriteCache = new Map<string, Sprite>();
/**
* Creates a Three.js sprite for a graph node: colored circle with value label.
*/
export function createNodeSprite(node: PositionedNode): THREE.Sprite {
export function createNodeSprite(node: PositionedNode): Sprite {
const cacheKey = `${node.id}:${node.value}:${node.color}`;
const cached = spriteCache.get(cacheKey);
if (cached) return cached.clone();
@@ -78,13 +78,13 @@ export function createNodeSprite(node: PositionedNode): THREE.Sprite {
ctx.fillText(node.unit, cx, cy + 60);
}
const texture = new THREE.CanvasTexture(canvas);
const material = new THREE.SpriteMaterial({
const texture = new CanvasTexture(canvas);
const material = new SpriteMaterial({
map: texture,
transparent: true,
depthWrite: false,
});
const sprite = new THREE.Sprite(material);
const sprite = new Sprite(material);
sprite.scale.set(50, 50, 1);
spriteCache.set(cacheKey, sprite);
@@ -94,7 +94,7 @@ export function createNodeSprite(node: PositionedNode): THREE.Sprite {
/**
* Creates a dimmed version of a node sprite (for non-highlighted nodes).
*/
export function createDimmedNodeSprite(node: PositionedNode): THREE.Sprite {
export function createDimmedNodeSprite(node: PositionedNode): Sprite {
const sprite = createNodeSprite({ ...node, color: "#4b5563" });
sprite.material.opacity = 0.3;
return sprite;