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:
@@ -1,6 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { InfoTooltip } from "~/components/ui/InfoTooltip.js";
|
||||
import { trpc } from "~/lib/trpc/client.js";
|
||||
|
||||
const TRIGGER_TYPES = ["SICK", "VACATION", "PUBLIC_HOLIDAY", "CUSTOM"] as const;
|
||||
@@ -164,13 +165,27 @@ export function CalculationRulesClient() {
|
||||
<table className="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
|
||||
<thead className="bg-gray-50 dark:bg-gray-800">
|
||||
<tr>
|
||||
<th className="px-4 py-3 text-left text-xs font-medium uppercase text-gray-500">Name</th>
|
||||
<th className="px-4 py-3 text-left text-xs font-medium uppercase text-gray-500">Trigger</th>
|
||||
<th className="px-4 py-3 text-left text-xs font-medium uppercase text-gray-500">Cost Effect</th>
|
||||
<th className="px-4 py-3 text-left text-xs font-medium uppercase text-gray-500">Chargeability</th>
|
||||
<th className="px-4 py-3 text-left text-xs font-medium uppercase text-gray-500">Scope</th>
|
||||
<th className="px-4 py-3 text-left text-xs font-medium uppercase text-gray-500">Priority</th>
|
||||
<th className="px-4 py-3 text-left text-xs font-medium uppercase text-gray-500">Active</th>
|
||||
<th className="px-4 py-3 text-left text-xs font-medium uppercase text-gray-500">
|
||||
<span className="flex items-center">Name <InfoTooltip content="A descriptive label for this rule. Use clear names so admins can quickly identify what each rule does." /></span>
|
||||
</th>
|
||||
<th className="px-4 py-3 text-left text-xs font-medium uppercase text-gray-500">
|
||||
<span className="flex items-center">Trigger <InfoTooltip content="The absence type that activates this rule: Sick Leave, Vacation, Public Holiday, or Custom. Determines when the cost/chargeability logic applies." /></span>
|
||||
</th>
|
||||
<th className="px-4 py-3 text-left text-xs font-medium uppercase text-gray-500">
|
||||
<span className="flex items-center">Cost Effect <InfoTooltip content="How this absence affects project costs. 'Charge to Project' bills the project, 'No Project Cost' absorbs the cost centrally, 'Reduced Cost' applies a percentage discount." /></span>
|
||||
</th>
|
||||
<th className="px-4 py-3 text-left text-xs font-medium uppercase text-gray-500">
|
||||
<span className="flex items-center">Chargeability <InfoTooltip content="Whether the person's time counts as chargeable during this absence. 'Person Chargeable' includes the time in chargeability metrics; 'Not Chargeable' excludes it." /></span>
|
||||
</th>
|
||||
<th className="px-4 py-3 text-left text-xs font-medium uppercase text-gray-500">
|
||||
<span className="flex items-center">Scope <InfoTooltip content="Limits the rule to a specific project or order type (BD, Chargeable, Internal, Overhead). 'Global' means the rule applies to all projects." /></span>
|
||||
</th>
|
||||
<th className="px-4 py-3 text-left text-xs font-medium uppercase text-gray-500">
|
||||
<span className="flex items-center">Priority <InfoTooltip content="When multiple rules match the same absence, the one with the highest priority number wins. Use this to create specific overrides for certain projects." /></span>
|
||||
</th>
|
||||
<th className="px-4 py-3 text-left text-xs font-medium uppercase text-gray-500">
|
||||
<span className="flex items-center">Active <InfoTooltip content="Only active rules are evaluated. Deactivate a rule to temporarily disable it without deleting." /></span>
|
||||
</th>
|
||||
<th className="px-4 py-3 text-right text-xs font-medium uppercase text-gray-500">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -233,7 +248,7 @@ export function CalculationRulesClient() {
|
||||
</h2>
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<label className="mb-1 block text-sm font-medium text-gray-700 dark:text-gray-300">Name</label>
|
||||
<label className="mb-1 flex items-center text-sm font-medium text-gray-700 dark:text-gray-300">Name <InfoTooltip content="A unique, descriptive name for this calculation rule." /></label>
|
||||
<input
|
||||
value={editing.name}
|
||||
onChange={(e) => setEditing({ ...editing, name: e.target.value })}
|
||||
@@ -241,7 +256,7 @@ export function CalculationRulesClient() {
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="mb-1 block text-sm font-medium text-gray-700 dark:text-gray-300">Description</label>
|
||||
<label className="mb-1 flex items-center text-sm font-medium text-gray-700 dark:text-gray-300">Description <InfoTooltip content="Optional notes explaining when and why this rule exists." /></label>
|
||||
<textarea
|
||||
value={editing.description}
|
||||
onChange={(e) => setEditing({ ...editing, description: e.target.value })}
|
||||
@@ -251,7 +266,7 @@ export function CalculationRulesClient() {
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="mb-1 block text-sm font-medium text-gray-700 dark:text-gray-300">Trigger Type</label>
|
||||
<label className="mb-1 flex items-center text-sm font-medium text-gray-700 dark:text-gray-300">Trigger Type <InfoTooltip content="The type of absence event that activates this rule." /></label>
|
||||
<select
|
||||
value={editing.triggerType}
|
||||
onChange={(e) => setEditing({ ...editing, triggerType: e.target.value })}
|
||||
@@ -261,7 +276,7 @@ export function CalculationRulesClient() {
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label className="mb-1 block text-sm font-medium text-gray-700 dark:text-gray-300">Order Type (optional)</label>
|
||||
<label className="mb-1 flex items-center text-sm font-medium text-gray-700 dark:text-gray-300">Order Type (optional) <InfoTooltip content="Restricts this rule to a specific order type. Leave as 'All' to apply globally." /></label>
|
||||
<select
|
||||
value={editing.orderType}
|
||||
onChange={(e) => setEditing({ ...editing, orderType: e.target.value })}
|
||||
@@ -274,7 +289,7 @@ export function CalculationRulesClient() {
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="mb-1 block text-sm font-medium text-gray-700 dark:text-gray-300">Cost Effect</label>
|
||||
<label className="mb-1 flex items-center text-sm font-medium text-gray-700 dark:text-gray-300">Cost Effect <InfoTooltip content="Determines how costs are attributed during this absence. 'Charge' bills the project, 'Zero' removes cost, 'Reduce' applies a percentage reduction." /></label>
|
||||
<select
|
||||
value={editing.costEffect}
|
||||
onChange={(e) => setEditing({ ...editing, costEffect: e.target.value })}
|
||||
@@ -284,7 +299,7 @@ export function CalculationRulesClient() {
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label className="mb-1 block text-sm font-medium text-gray-700 dark:text-gray-300">Chargeability</label>
|
||||
<label className="mb-1 flex items-center text-sm font-medium text-gray-700 dark:text-gray-300">Chargeability <InfoTooltip content="Controls whether this absence counts toward the person's chargeability KPI." /></label>
|
||||
<select
|
||||
value={editing.chargeabilityEffect}
|
||||
onChange={(e) => setEditing({ ...editing, chargeabilityEffect: e.target.value })}
|
||||
@@ -296,8 +311,8 @@ export function CalculationRulesClient() {
|
||||
</div>
|
||||
{editing.costEffect === "REDUCE" && (
|
||||
<div>
|
||||
<label className="mb-1 block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
Reduction Percent (0-100)
|
||||
<label className="mb-1 flex items-center text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
Reduction Percent (0-100) <InfoTooltip content="The percentage by which the cost is reduced. E.g. 50 means the project is charged half the normal rate." />
|
||||
</label>
|
||||
<input
|
||||
type="number"
|
||||
@@ -311,7 +326,7 @@ export function CalculationRulesClient() {
|
||||
)}
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="mb-1 block text-sm font-medium text-gray-700 dark:text-gray-300">Priority</label>
|
||||
<label className="mb-1 flex items-center text-sm font-medium text-gray-700 dark:text-gray-300">Priority <InfoTooltip content="Higher numbers take precedence when multiple rules match. Use 0 for default rules and higher values for specific overrides." /></label>
|
||||
<input
|
||||
type="number"
|
||||
min={0}
|
||||
@@ -328,7 +343,7 @@ export function CalculationRulesClient() {
|
||||
onChange={(e) => setEditing({ ...editing, isActive: e.target.checked })}
|
||||
className="rounded border-gray-300"
|
||||
/>
|
||||
Active
|
||||
Active <InfoTooltip content="Inactive rules are ignored during cost calculations." />
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user