feat: project cover art with AI generation, branding rename, RBAC fix, computation graph

- Add DALL-E cover art generation for projects (Azure OpenAI + standard OpenAI)
- CoverArtSection component with generate/upload/remove/focus-point controls
- Client-side image compression (10MB input → WebP/JPEG, max 1920px)
- DALL-E settings in admin panel (deployment, endpoint, API key)
- MCP assistant tools for cover art (generate_project_cover, remove_project_cover)
- Rename "Planarchy" → "plANARCHY" across all UI-facing text (13 files)
- Fix hardcoded canEdit={true} on project detail page — now checks user role
- Computation graph visualization (2D/3D) for calculation rules
- OG image and OpenGraph metadata

Co-Authored-By: claude-flow <ruv@ruv.net>
This commit is contained in:
2026-03-18 11:31:56 +01:00
parent 21af720f90
commit 093e13b88f
86 changed files with 5623 additions and 744 deletions
@@ -1,6 +1,7 @@
"use client";
import { useState } from "react";
import { InfoTooltip } from "~/components/ui/InfoTooltip.js";
import { trpc } from "~/lib/trpc/client.js";
type LevelRow = { id: string; name: string; groupId: string };
@@ -115,7 +116,7 @@ export function ManagementLevelsClient() {
<div>
<h1 className="text-2xl font-bold text-gray-900 dark:text-gray-50">Management Levels</h1>
<p className="text-sm text-gray-500 dark:text-gray-400 mt-1">
Level groups with chargeability targets and individual levels
Level groups with chargeability targets and individual levels <InfoTooltip content="Management levels define seniority groups (e.g. Senior Management, Team Lead). Each group has a chargeability target that appears in chargeability reports. Individual levels within a group are assigned to resources." />
</p>
</div>
<button
@@ -146,6 +147,7 @@ export function ManagementLevelsClient() {
<span className="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-blue-100 text-blue-700 dark:bg-blue-900/40 dark:text-blue-400">
Target: {Math.round(group.targetPercentage * 100)}%
</span>
<InfoTooltip content="The chargeability target for this group. Resources in this group are expected to achieve this percentage of chargeable hours. Used in chargeability reports and dashboards." />
</div>
<div className="flex gap-2">
<button
@@ -217,7 +219,7 @@ export function ManagementLevelsClient() {
<div className="px-6 py-5 space-y-4">
<div>
<label className="block text-xs font-medium text-gray-600 dark:text-gray-400 mb-1">Name</label>
<label className="flex items-center text-xs font-medium text-gray-600 dark:text-gray-400 mb-1">Name <InfoTooltip content="The name of this management level group (e.g. Senior Management, Team Leads)." /></label>
<input
type="text"
value={editingGroup.name}
@@ -229,7 +231,7 @@ export function ManagementLevelsClient() {
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-xs font-medium text-gray-600 dark:text-gray-400 mb-1">Target % (0-100)</label>
<label className="flex items-center text-xs font-medium text-gray-600 dark:text-gray-400 mb-1">Target % (0-100) <InfoTooltip content="The chargeability target for resources in this group. Enter as a percentage (e.g. 70 for 70%). Used in chargeability reports." /></label>
<input
type="number"
value={Math.round(editingGroup.targetPercentage * 100)}
@@ -240,7 +242,7 @@ export function ManagementLevelsClient() {
/>
</div>
<div>
<label className="block text-xs font-medium text-gray-600 dark:text-gray-400 mb-1">Sort Order</label>
<label className="flex items-center text-xs font-medium text-gray-600 dark:text-gray-400 mb-1">Sort Order <InfoTooltip content="Controls the display order of groups. Lower numbers appear first." /></label>
<input
type="number"
value={editingGroup.sortOrder}
@@ -279,7 +281,7 @@ export function ManagementLevelsClient() {
<div className="px-6 py-5 space-y-4">
<div>
<label className="block text-xs font-medium text-gray-600 dark:text-gray-400 mb-1">Level Name</label>
<label className="flex items-center text-xs font-medium text-gray-600 dark:text-gray-400 mb-1">Level Name <InfoTooltip content="The specific title within the group (e.g. Managing Director, VP). This is assigned to individual resources." /></label>
<input
type="text"
value={editingLevel.name}
@@ -290,7 +292,7 @@ export function ManagementLevelsClient() {
</div>
<div>
<label className="block text-xs font-medium text-gray-600 dark:text-gray-400 mb-1">Group</label>
<label className="flex items-center text-xs font-medium text-gray-600 dark:text-gray-400 mb-1">Group <InfoTooltip content="The management level group this level belongs to. Determines chargeability target and reporting." /></label>
<select
value={editingLevel.groupId}
onChange={(e) => setEditingLevel({ ...editingLevel, groupId: e.target.value })}