diff --git a/apps/web/next.config.ts b/apps/web/next.config.ts
index 41da849..f6972b0 100644
--- a/apps/web/next.config.ts
+++ b/apps/web/next.config.ts
@@ -7,7 +7,7 @@ const nextConfig: NextConfig = {
outputFileTracingRoot: path.resolve(__dirname, "../.."),
devIndicators: false,
experimental: {
- optimizePackageImports: ["recharts", "date-fns"],
+ optimizePackageImports: ["recharts", "date-fns", "framer-motion", "@capakraken/shared"],
},
transpilePackages: [
"@capakraken/api",
@@ -15,7 +15,6 @@ const nextConfig: NextConfig = {
"@capakraken/engine",
"@capakraken/shared",
"@capakraken/staffing",
- "@capakraken/ui",
],
typedRoutes: true,
async redirects() {
diff --git a/apps/web/src/app/(app)/admin/error.tsx b/apps/web/src/app/(app)/admin/error.tsx
new file mode 100644
index 0000000..cc7388e
--- /dev/null
+++ b/apps/web/src/app/(app)/admin/error.tsx
@@ -0,0 +1,10 @@
+"use client";
+export default function RouteError({ error, reset }: { error: Error; reset: () => void }) {
+ return (
+
+
Something went wrong
+
{error.message}
+
+
+ );
+}
diff --git a/apps/web/src/app/(app)/admin/loading.tsx b/apps/web/src/app/(app)/admin/loading.tsx
new file mode 100644
index 0000000..3c21848
--- /dev/null
+++ b/apps/web/src/app/(app)/admin/loading.tsx
@@ -0,0 +1,10 @@
+export default function Loading() {
+ return (
+
+ );
+}
diff --git a/apps/web/src/app/(app)/allocations/error.tsx b/apps/web/src/app/(app)/allocations/error.tsx
new file mode 100644
index 0000000..cc7388e
--- /dev/null
+++ b/apps/web/src/app/(app)/allocations/error.tsx
@@ -0,0 +1,10 @@
+"use client";
+export default function RouteError({ error, reset }: { error: Error; reset: () => void }) {
+ return (
+
+
Something went wrong
+
{error.message}
+
+
+ );
+}
diff --git a/apps/web/src/app/(app)/analytics/error.tsx b/apps/web/src/app/(app)/analytics/error.tsx
new file mode 100644
index 0000000..cc7388e
--- /dev/null
+++ b/apps/web/src/app/(app)/analytics/error.tsx
@@ -0,0 +1,10 @@
+"use client";
+export default function RouteError({ error, reset }: { error: Error; reset: () => void }) {
+ return (
+
+
Something went wrong
+
{error.message}
+
+
+ );
+}
diff --git a/apps/web/src/app/(app)/analytics/loading.tsx b/apps/web/src/app/(app)/analytics/loading.tsx
new file mode 100644
index 0000000..3c21848
--- /dev/null
+++ b/apps/web/src/app/(app)/analytics/loading.tsx
@@ -0,0 +1,10 @@
+export default function Loading() {
+ return (
+
+ );
+}
diff --git a/apps/web/src/app/(app)/dashboard/error.tsx b/apps/web/src/app/(app)/dashboard/error.tsx
new file mode 100644
index 0000000..cc7388e
--- /dev/null
+++ b/apps/web/src/app/(app)/dashboard/error.tsx
@@ -0,0 +1,10 @@
+"use client";
+export default function RouteError({ error, reset }: { error: Error; reset: () => void }) {
+ return (
+
+
Something went wrong
+
{error.message}
+
+
+ );
+}
diff --git a/apps/web/src/app/(app)/dashboard/loading.tsx b/apps/web/src/app/(app)/dashboard/loading.tsx
new file mode 100644
index 0000000..3c21848
--- /dev/null
+++ b/apps/web/src/app/(app)/dashboard/loading.tsx
@@ -0,0 +1,10 @@
+export default function Loading() {
+ return (
+
+ );
+}
diff --git a/apps/web/src/app/(app)/estimates/error.tsx b/apps/web/src/app/(app)/estimates/error.tsx
new file mode 100644
index 0000000..cc7388e
--- /dev/null
+++ b/apps/web/src/app/(app)/estimates/error.tsx
@@ -0,0 +1,10 @@
+"use client";
+export default function RouteError({ error, reset }: { error: Error; reset: () => void }) {
+ return (
+
+
Something went wrong
+
{error.message}
+
+
+ );
+}
diff --git a/apps/web/src/app/(app)/estimates/loading.tsx b/apps/web/src/app/(app)/estimates/loading.tsx
new file mode 100644
index 0000000..3c21848
--- /dev/null
+++ b/apps/web/src/app/(app)/estimates/loading.tsx
@@ -0,0 +1,10 @@
+export default function Loading() {
+ return (
+
+ );
+}
diff --git a/apps/web/src/app/(app)/notifications/error.tsx b/apps/web/src/app/(app)/notifications/error.tsx
new file mode 100644
index 0000000..cc7388e
--- /dev/null
+++ b/apps/web/src/app/(app)/notifications/error.tsx
@@ -0,0 +1,10 @@
+"use client";
+export default function RouteError({ error, reset }: { error: Error; reset: () => void }) {
+ return (
+
+
Something went wrong
+
{error.message}
+
+
+ );
+}
diff --git a/apps/web/src/app/(app)/notifications/loading.tsx b/apps/web/src/app/(app)/notifications/loading.tsx
new file mode 100644
index 0000000..3c21848
--- /dev/null
+++ b/apps/web/src/app/(app)/notifications/loading.tsx
@@ -0,0 +1,10 @@
+export default function Loading() {
+ return (
+
+ );
+}
diff --git a/apps/web/src/app/(app)/projects/error.tsx b/apps/web/src/app/(app)/projects/error.tsx
new file mode 100644
index 0000000..cc7388e
--- /dev/null
+++ b/apps/web/src/app/(app)/projects/error.tsx
@@ -0,0 +1,10 @@
+"use client";
+export default function RouteError({ error, reset }: { error: Error; reset: () => void }) {
+ return (
+
+
Something went wrong
+
{error.message}
+
+
+ );
+}
diff --git a/apps/web/src/app/(app)/reports/error.tsx b/apps/web/src/app/(app)/reports/error.tsx
new file mode 100644
index 0000000..cc7388e
--- /dev/null
+++ b/apps/web/src/app/(app)/reports/error.tsx
@@ -0,0 +1,10 @@
+"use client";
+export default function RouteError({ error, reset }: { error: Error; reset: () => void }) {
+ return (
+
+
Something went wrong
+
{error.message}
+
+
+ );
+}
diff --git a/apps/web/src/app/(app)/reports/loading.tsx b/apps/web/src/app/(app)/reports/loading.tsx
new file mode 100644
index 0000000..3c21848
--- /dev/null
+++ b/apps/web/src/app/(app)/reports/loading.tsx
@@ -0,0 +1,10 @@
+export default function Loading() {
+ return (
+
+ );
+}
diff --git a/apps/web/src/app/(app)/resources/error.tsx b/apps/web/src/app/(app)/resources/error.tsx
new file mode 100644
index 0000000..cc7388e
--- /dev/null
+++ b/apps/web/src/app/(app)/resources/error.tsx
@@ -0,0 +1,10 @@
+"use client";
+export default function RouteError({ error, reset }: { error: Error; reset: () => void }) {
+ return (
+
+
Something went wrong
+
{error.message}
+
+
+ );
+}
diff --git a/apps/web/src/app/(app)/roles/error.tsx b/apps/web/src/app/(app)/roles/error.tsx
new file mode 100644
index 0000000..cc7388e
--- /dev/null
+++ b/apps/web/src/app/(app)/roles/error.tsx
@@ -0,0 +1,10 @@
+"use client";
+export default function RouteError({ error, reset }: { error: Error; reset: () => void }) {
+ return (
+
+
Something went wrong
+
{error.message}
+
+
+ );
+}
diff --git a/apps/web/src/app/(app)/roles/loading.tsx b/apps/web/src/app/(app)/roles/loading.tsx
new file mode 100644
index 0000000..3c21848
--- /dev/null
+++ b/apps/web/src/app/(app)/roles/loading.tsx
@@ -0,0 +1,10 @@
+export default function Loading() {
+ return (
+
+ );
+}
diff --git a/apps/web/src/app/(app)/staffing/error.tsx b/apps/web/src/app/(app)/staffing/error.tsx
new file mode 100644
index 0000000..cc7388e
--- /dev/null
+++ b/apps/web/src/app/(app)/staffing/error.tsx
@@ -0,0 +1,10 @@
+"use client";
+export default function RouteError({ error, reset }: { error: Error; reset: () => void }) {
+ return (
+
+
Something went wrong
+
{error.message}
+
+
+ );
+}
diff --git a/apps/web/src/app/(app)/staffing/loading.tsx b/apps/web/src/app/(app)/staffing/loading.tsx
new file mode 100644
index 0000000..3c21848
--- /dev/null
+++ b/apps/web/src/app/(app)/staffing/loading.tsx
@@ -0,0 +1,10 @@
+export default function Loading() {
+ return (
+
+ );
+}
diff --git a/apps/web/src/app/(app)/timeline/error.tsx b/apps/web/src/app/(app)/timeline/error.tsx
new file mode 100644
index 0000000..cc7388e
--- /dev/null
+++ b/apps/web/src/app/(app)/timeline/error.tsx
@@ -0,0 +1,10 @@
+"use client";
+export default function RouteError({ error, reset }: { error: Error; reset: () => void }) {
+ return (
+
+
Something went wrong
+
{error.message}
+
+
+ );
+}
diff --git a/apps/web/src/app/(app)/vacations/error.tsx b/apps/web/src/app/(app)/vacations/error.tsx
new file mode 100644
index 0000000..cc7388e
--- /dev/null
+++ b/apps/web/src/app/(app)/vacations/error.tsx
@@ -0,0 +1,10 @@
+"use client";
+export default function RouteError({ error, reset }: { error: Error; reset: () => void }) {
+ return (
+
+
Something went wrong
+
{error.message}
+
+
+ );
+}
diff --git a/apps/web/src/app/(app)/vacations/loading.tsx b/apps/web/src/app/(app)/vacations/loading.tsx
new file mode 100644
index 0000000..3c21848
--- /dev/null
+++ b/apps/web/src/app/(app)/vacations/loading.tsx
@@ -0,0 +1,10 @@
+export default function Loading() {
+ return (
+
+ );
+}
diff --git a/apps/web/src/components/analytics/computation-graph/node-renderer.ts b/apps/web/src/components/analytics/computation-graph/node-renderer.ts
index de4aa28..e3975c4 100644
--- a/apps/web/src/components/analytics/computation-graph/node-renderer.ts
+++ b/apps/web/src/components/analytics/computation-graph/node-renderer.ts
@@ -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();
+const spriteCache = new Map();
/**
* 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;
diff --git a/apps/web/src/components/resources/ResourceDetail.tsx b/apps/web/src/components/resources/ResourceDetail.tsx
index b18ff70..7064ed0 100644
--- a/apps/web/src/components/resources/ResourceDetail.tsx
+++ b/apps/web/src/components/resources/ResourceDetail.tsx
@@ -1,6 +1,6 @@
"use client";
-import { useState } from "react";
+import { useMemo, useState } from "react";
import Link from "next/link";
import dynamic from "next/dynamic";
import type { AllocationLike, AllocationReadModel, AllocationWithDetails, Resource, SkillEntry } from "@capakraken/shared";
@@ -96,8 +96,6 @@ export function ResourceDetail({ resourceId }: ResourceDetailProps) {
// Fetch allocations for this resource (all non-cancelled)
const now = new Date();
- const windowEnd = new Date(now);
- windowEnd.setDate(windowEnd.getDate() + 90);
const _allocQuery = trpc.allocation.listView.useQuery(
{ resourceId },
@@ -110,8 +108,12 @@ export function ResourceDetail({ resourceId }: ResourceDetailProps) {
const loadingAllocations = _allocQuery.isLoading;
// Fetch upcoming/recent vacations
- const vacationStart = new Date(now);
- vacationStart.setMonth(vacationStart.getMonth() - 1);
+ const vacationStart = useMemo(() => {
+ const d = new Date();
+ d.setMonth(d.getMonth() - 1);
+ d.setHours(0, 0, 0, 0);
+ return d;
+ }, []);
const { data: vacations, isLoading: loadingVacations } = trpc.vacation.list.useQuery(
{