2026-02-23 09:56:31 -06:00
|
|
|
import { useEffect, useMemo, useState, useRef } from "react";
|
2026-03-02 16:44:03 -06:00
|
|
|
import { useParams, useNavigate, useLocation, Navigate } from "@/lib/router";
|
2026-02-20 10:32:32 -06:00
|
|
|
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
|
2026-03-03 08:45:26 -06:00
|
|
|
import { PROJECT_COLORS, isUuidLike } from "@paperclipai/shared";
|
Add detail pages, property panels, and restyle list pages
New pages: AgentDetail, GoalDetail, IssueDetail, ProjectDetail, Inbox,
MyIssues. New feature components: AgentProperties, GoalProperties,
IssueProperties, ProjectProperties, GoalTree, NewIssueDialog. Add
heartbeats API client. Restyle all list pages (Agents, Issues, Goals,
Projects, Dashboard, Costs, Activity, Org) with EntityRow, FilterBar,
and improved layouts. Add routing for detail views.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 09:57:06 -06:00
|
|
|
import { projectsApi } from "../api/projects";
|
|
|
|
|
import { issuesApi } from "../api/issues";
|
2026-02-23 09:56:31 -06:00
|
|
|
import { agentsApi } from "../api/agents";
|
|
|
|
|
import { heartbeatsApi } from "../api/heartbeats";
|
Add MarkdownEditor component, asset image upload, and rich description editing
Introduce MarkdownEditor built on @mdxeditor/editor with headings,
lists, links, quotes, image upload with drag-and-drop, and themed CSS
integration. Add asset image upload API (routes, service, storage) and
wire image upload into InlineEditor multiline mode, NewIssueDialog,
NewProjectDialog, GoalDetail, IssueDetail, and ProjectDetail
description fields. Tighten prompt template editor styling.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 12:50:45 -06:00
|
|
|
import { assetsApi } from "../api/assets";
|
Add detail pages, property panels, and restyle list pages
New pages: AgentDetail, GoalDetail, IssueDetail, ProjectDetail, Inbox,
MyIssues. New feature components: AgentProperties, GoalProperties,
IssueProperties, ProjectProperties, GoalTree, NewIssueDialog. Add
heartbeats API client. Restyle all list pages (Agents, Issues, Goals,
Projects, Dashboard, Costs, Activity, Org) with EntityRow, FilterBar,
and improved layouts. Add routing for detail views.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 09:57:06 -06:00
|
|
|
import { usePanel } from "../context/PanelContext";
|
|
|
|
|
import { useCompany } from "../context/CompanyContext";
|
|
|
|
|
import { useBreadcrumbs } from "../context/BreadcrumbContext";
|
2026-02-17 12:24:48 -06:00
|
|
|
import { queryKeys } from "../lib/queryKeys";
|
Add detail pages, property panels, and restyle list pages
New pages: AgentDetail, GoalDetail, IssueDetail, ProjectDetail, Inbox,
MyIssues. New feature components: AgentProperties, GoalProperties,
IssueProperties, ProjectProperties, GoalTree, NewIssueDialog. Add
heartbeats API client. Restyle all list pages (Agents, Issues, Goals,
Projects, Dashboard, Costs, Activity, Org) with EntityRow, FilterBar,
and improved layouts. Add routing for detail views.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 09:57:06 -06:00
|
|
|
import { ProjectProperties } from "../components/ProjectProperties";
|
2026-02-20 10:32:32 -06:00
|
|
|
import { InlineEditor } from "../components/InlineEditor";
|
Add detail pages, property panels, and restyle list pages
New pages: AgentDetail, GoalDetail, IssueDetail, ProjectDetail, Inbox,
MyIssues. New feature components: AgentProperties, GoalProperties,
IssueProperties, ProjectProperties, GoalTree, NewIssueDialog. Add
heartbeats API client. Restyle all list pages (Agents, Issues, Goals,
Projects, Dashboard, Costs, Activity, Org) with EntityRow, FilterBar,
and improved layouts. Add routing for detail views.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 09:57:06 -06:00
|
|
|
import { StatusBadge } from "../components/StatusBadge";
|
2026-02-23 09:56:31 -06:00
|
|
|
import { IssuesList } from "../components/IssuesList";
|
2026-03-02 16:44:03 -06:00
|
|
|
import { PageSkeleton } from "../components/PageSkeleton";
|
2026-03-05 15:25:19 -06:00
|
|
|
import { projectRouteRef, cn } from "../lib/utils";
|
|
|
|
|
import { Button } from "@/components/ui/button";
|
2026-03-05 18:57:48 -06:00
|
|
|
import { Sheet, SheetContent, SheetHeader, SheetTitle } from "@/components/ui/sheet";
|
|
|
|
|
import { ScrollArea } from "@/components/ui/scroll-area";
|
2026-03-05 15:25:19 -06:00
|
|
|
import { SlidersHorizontal } from "lucide-react";
|
2026-02-23 09:56:31 -06:00
|
|
|
|
|
|
|
|
/* ── Top-level tab types ── */
|
|
|
|
|
|
|
|
|
|
type ProjectTab = "overview" | "list";
|
|
|
|
|
|
|
|
|
|
function resolveProjectTab(pathname: string, projectId: string): ProjectTab | null {
|
2026-03-02 16:44:03 -06:00
|
|
|
const segments = pathname.split("/").filter(Boolean);
|
|
|
|
|
const projectsIdx = segments.indexOf("projects");
|
|
|
|
|
if (projectsIdx === -1 || segments[projectsIdx + 1] !== projectId) return null;
|
|
|
|
|
const tab = segments[projectsIdx + 2];
|
|
|
|
|
if (tab === "overview") return "overview";
|
|
|
|
|
if (tab === "issues") return "list";
|
2026-02-23 09:56:31 -06:00
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* ── Overview tab content ── */
|
|
|
|
|
|
|
|
|
|
function OverviewContent({
|
|
|
|
|
project,
|
|
|
|
|
onUpdate,
|
|
|
|
|
imageUploadHandler,
|
|
|
|
|
}: {
|
|
|
|
|
project: { description: string | null; status: string; targetDate: string | null };
|
|
|
|
|
onUpdate: (data: Record<string, unknown>) => void;
|
|
|
|
|
imageUploadHandler?: (file: File) => Promise<string>;
|
|
|
|
|
}) {
|
|
|
|
|
return (
|
|
|
|
|
<div className="space-y-6">
|
|
|
|
|
<InlineEditor
|
|
|
|
|
value={project.description ?? ""}
|
|
|
|
|
onSave={(description) => onUpdate({ description })}
|
|
|
|
|
as="p"
|
|
|
|
|
className="text-sm text-muted-foreground"
|
|
|
|
|
placeholder="Add a description..."
|
|
|
|
|
multiline
|
|
|
|
|
imageUploadHandler={imageUploadHandler}
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4 text-sm">
|
|
|
|
|
<div>
|
|
|
|
|
<span className="text-muted-foreground">Status</span>
|
|
|
|
|
<div className="mt-1">
|
|
|
|
|
<StatusBadge status={project.status} />
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
{project.targetDate && (
|
|
|
|
|
<div>
|
|
|
|
|
<span className="text-muted-foreground">Target Date</span>
|
|
|
|
|
<p>{project.targetDate}</p>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* ── Color picker popover ── */
|
|
|
|
|
|
|
|
|
|
function ColorPicker({
|
|
|
|
|
currentColor,
|
|
|
|
|
onSelect,
|
|
|
|
|
}: {
|
|
|
|
|
currentColor: string;
|
|
|
|
|
onSelect: (color: string) => void;
|
|
|
|
|
}) {
|
|
|
|
|
const [open, setOpen] = useState(false);
|
|
|
|
|
const ref = useRef<HTMLDivElement>(null);
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
if (!open) return;
|
|
|
|
|
function handleClick(e: MouseEvent) {
|
|
|
|
|
if (ref.current && !ref.current.contains(e.target as Node)) {
|
|
|
|
|
setOpen(false);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
document.addEventListener("mousedown", handleClick);
|
|
|
|
|
return () => document.removeEventListener("mousedown", handleClick);
|
|
|
|
|
}, [open]);
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div className="relative" ref={ref}>
|
|
|
|
|
<button
|
|
|
|
|
onClick={() => setOpen(!open)}
|
2026-03-02 16:44:03 -06:00
|
|
|
className="shrink-0 h-5 w-5 rounded-md cursor-pointer hover:ring-2 hover:ring-foreground/20 transition-[box-shadow]"
|
2026-02-23 09:56:31 -06:00
|
|
|
style={{ backgroundColor: currentColor }}
|
|
|
|
|
aria-label="Change project color"
|
|
|
|
|
/>
|
|
|
|
|
{open && (
|
2026-02-23 14:41:21 -06:00
|
|
|
<div className="absolute top-full left-0 mt-2 p-2 bg-popover border border-border rounded-lg shadow-lg z-50 w-max">
|
2026-02-23 09:56:31 -06:00
|
|
|
<div className="grid grid-cols-5 gap-1.5">
|
|
|
|
|
{PROJECT_COLORS.map((color) => (
|
|
|
|
|
<button
|
|
|
|
|
key={color}
|
|
|
|
|
onClick={() => {
|
|
|
|
|
onSelect(color);
|
|
|
|
|
setOpen(false);
|
|
|
|
|
}}
|
2026-03-02 16:44:03 -06:00
|
|
|
className={`h-6 w-6 rounded-md cursor-pointer transition-[transform,box-shadow] duration-150 hover:scale-110 ${
|
2026-02-23 09:56:31 -06:00
|
|
|
color === currentColor
|
|
|
|
|
? "ring-2 ring-foreground ring-offset-1 ring-offset-background"
|
|
|
|
|
: "hover:ring-2 hover:ring-foreground/30"
|
|
|
|
|
}`}
|
|
|
|
|
style={{ backgroundColor: color }}
|
|
|
|
|
aria-label={`Select color ${color}`}
|
|
|
|
|
/>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* ── List (issues) tab content ── */
|
|
|
|
|
|
2026-03-02 16:44:03 -06:00
|
|
|
function ProjectIssuesList({ projectId, companyId }: { projectId: string; companyId: string }) {
|
2026-02-23 09:56:31 -06:00
|
|
|
const queryClient = useQueryClient();
|
|
|
|
|
|
|
|
|
|
const { data: agents } = useQuery({
|
2026-03-02 16:44:03 -06:00
|
|
|
queryKey: queryKeys.agents.list(companyId),
|
|
|
|
|
queryFn: () => agentsApi.list(companyId),
|
|
|
|
|
enabled: !!companyId,
|
2026-02-23 09:56:31 -06:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const { data: liveRuns } = useQuery({
|
2026-03-02 16:44:03 -06:00
|
|
|
queryKey: queryKeys.liveRuns(companyId),
|
|
|
|
|
queryFn: () => heartbeatsApi.liveRunsForCompany(companyId),
|
|
|
|
|
enabled: !!companyId,
|
2026-02-23 09:56:31 -06:00
|
|
|
refetchInterval: 5000,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const liveIssueIds = useMemo(() => {
|
|
|
|
|
const ids = new Set<string>();
|
|
|
|
|
for (const run of liveRuns ?? []) {
|
|
|
|
|
if (run.issueId) ids.add(run.issueId);
|
|
|
|
|
}
|
|
|
|
|
return ids;
|
|
|
|
|
}, [liveRuns]);
|
|
|
|
|
|
|
|
|
|
const { data: issues, isLoading, error } = useQuery({
|
2026-03-02 16:44:03 -06:00
|
|
|
queryKey: queryKeys.issues.listByProject(companyId, projectId),
|
|
|
|
|
queryFn: () => issuesApi.list(companyId, { projectId }),
|
|
|
|
|
enabled: !!companyId,
|
2026-02-23 09:56:31 -06:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const updateIssue = useMutation({
|
|
|
|
|
mutationFn: ({ id, data }: { id: string; data: Record<string, unknown> }) =>
|
|
|
|
|
issuesApi.update(id, data),
|
|
|
|
|
onSuccess: () => {
|
2026-03-02 16:44:03 -06:00
|
|
|
queryClient.invalidateQueries({ queryKey: queryKeys.issues.listByProject(companyId, projectId) });
|
|
|
|
|
queryClient.invalidateQueries({ queryKey: queryKeys.issues.list(companyId) });
|
2026-02-23 09:56:31 -06:00
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<IssuesList
|
|
|
|
|
issues={issues ?? []}
|
|
|
|
|
isLoading={isLoading}
|
|
|
|
|
error={error as Error | null}
|
|
|
|
|
agents={agents}
|
|
|
|
|
liveIssueIds={liveIssueIds}
|
|
|
|
|
projectId={projectId}
|
|
|
|
|
viewStateKey={`paperclip:project-view:${projectId}`}
|
|
|
|
|
onUpdateIssue={(id, data) => updateIssue.mutate({ id, data })}
|
|
|
|
|
/>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* ── Main project page ── */
|
Add detail pages, property panels, and restyle list pages
New pages: AgentDetail, GoalDetail, IssueDetail, ProjectDetail, Inbox,
MyIssues. New feature components: AgentProperties, GoalProperties,
IssueProperties, ProjectProperties, GoalTree, NewIssueDialog. Add
heartbeats API client. Restyle all list pages (Agents, Issues, Goals,
Projects, Dashboard, Costs, Activity, Org) with EntityRow, FilterBar,
and improved layouts. Add routing for detail views.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 09:57:06 -06:00
|
|
|
|
|
|
|
|
export function ProjectDetail() {
|
2026-03-02 16:44:03 -06:00
|
|
|
const { companyPrefix, projectId, filter } = useParams<{
|
|
|
|
|
companyPrefix?: string;
|
|
|
|
|
projectId: string;
|
|
|
|
|
filter?: string;
|
|
|
|
|
}>();
|
|
|
|
|
const { companies, selectedCompanyId, setSelectedCompanyId } = useCompany();
|
2026-03-05 15:25:19 -06:00
|
|
|
const { openPanel, closePanel, panelVisible, setPanelVisible } = usePanel();
|
Add detail pages, property panels, and restyle list pages
New pages: AgentDetail, GoalDetail, IssueDetail, ProjectDetail, Inbox,
MyIssues. New feature components: AgentProperties, GoalProperties,
IssueProperties, ProjectProperties, GoalTree, NewIssueDialog. Add
heartbeats API client. Restyle all list pages (Agents, Issues, Goals,
Projects, Dashboard, Costs, Activity, Org) with EntityRow, FilterBar,
and improved layouts. Add routing for detail views.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 09:57:06 -06:00
|
|
|
const { setBreadcrumbs } = useBreadcrumbs();
|
2026-03-05 18:57:48 -06:00
|
|
|
const [mobilePropsOpen, setMobilePropsOpen] = useState(false);
|
2026-02-20 10:32:32 -06:00
|
|
|
const queryClient = useQueryClient();
|
|
|
|
|
const navigate = useNavigate();
|
2026-02-23 09:56:31 -06:00
|
|
|
const location = useLocation();
|
2026-03-02 16:44:03 -06:00
|
|
|
const routeProjectRef = projectId ?? "";
|
|
|
|
|
const routeCompanyId = useMemo(() => {
|
|
|
|
|
if (!companyPrefix) return null;
|
|
|
|
|
const requestedPrefix = companyPrefix.toUpperCase();
|
|
|
|
|
return companies.find((company) => company.issuePrefix.toUpperCase() === requestedPrefix)?.id ?? null;
|
|
|
|
|
}, [companies, companyPrefix]);
|
|
|
|
|
const lookupCompanyId = routeCompanyId ?? selectedCompanyId ?? undefined;
|
|
|
|
|
const canFetchProject = routeProjectRef.length > 0 && (isUuidLike(routeProjectRef) || Boolean(lookupCompanyId));
|
2026-02-23 09:56:31 -06:00
|
|
|
|
2026-03-02 16:44:03 -06:00
|
|
|
const activeTab = routeProjectRef ? resolveProjectTab(location.pathname, routeProjectRef) : null;
|
Add detail pages, property panels, and restyle list pages
New pages: AgentDetail, GoalDetail, IssueDetail, ProjectDetail, Inbox,
MyIssues. New feature components: AgentProperties, GoalProperties,
IssueProperties, ProjectProperties, GoalTree, NewIssueDialog. Add
heartbeats API client. Restyle all list pages (Agents, Issues, Goals,
Projects, Dashboard, Costs, Activity, Org) with EntityRow, FilterBar,
and improved layouts. Add routing for detail views.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 09:57:06 -06:00
|
|
|
|
2026-02-17 12:24:48 -06:00
|
|
|
const { data: project, isLoading, error } = useQuery({
|
2026-03-02 16:44:03 -06:00
|
|
|
queryKey: [...queryKeys.projects.detail(routeProjectRef), lookupCompanyId ?? null],
|
|
|
|
|
queryFn: () => projectsApi.get(routeProjectRef, lookupCompanyId),
|
|
|
|
|
enabled: canFetchProject,
|
2026-02-17 12:24:48 -06:00
|
|
|
});
|
2026-03-02 16:44:03 -06:00
|
|
|
const canonicalProjectRef = project ? projectRouteRef(project) : routeProjectRef;
|
|
|
|
|
const projectLookupRef = project?.id ?? routeProjectRef;
|
|
|
|
|
const resolvedCompanyId = project?.companyId ?? selectedCompanyId;
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
if (!project?.companyId || project.companyId === selectedCompanyId) return;
|
|
|
|
|
setSelectedCompanyId(project.companyId, { source: "route_sync" });
|
|
|
|
|
}, [project?.companyId, selectedCompanyId, setSelectedCompanyId]);
|
Add detail pages, property panels, and restyle list pages
New pages: AgentDetail, GoalDetail, IssueDetail, ProjectDetail, Inbox,
MyIssues. New feature components: AgentProperties, GoalProperties,
IssueProperties, ProjectProperties, GoalTree, NewIssueDialog. Add
heartbeats API client. Restyle all list pages (Agents, Issues, Goals,
Projects, Dashboard, Costs, Activity, Org) with EntityRow, FilterBar,
and improved layouts. Add routing for detail views.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 09:57:06 -06:00
|
|
|
|
2026-02-20 10:32:32 -06:00
|
|
|
const invalidateProject = () => {
|
2026-03-02 16:44:03 -06:00
|
|
|
queryClient.invalidateQueries({ queryKey: queryKeys.projects.detail(routeProjectRef) });
|
|
|
|
|
queryClient.invalidateQueries({ queryKey: queryKeys.projects.detail(projectLookupRef) });
|
|
|
|
|
if (resolvedCompanyId) {
|
|
|
|
|
queryClient.invalidateQueries({ queryKey: queryKeys.projects.list(resolvedCompanyId) });
|
2026-02-20 10:32:32 -06:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const updateProject = useMutation({
|
2026-03-02 16:44:03 -06:00
|
|
|
mutationFn: (data: Record<string, unknown>) =>
|
|
|
|
|
projectsApi.update(projectLookupRef, data, resolvedCompanyId ?? lookupCompanyId),
|
2026-02-20 10:32:32 -06:00
|
|
|
onSuccess: invalidateProject,
|
|
|
|
|
});
|
|
|
|
|
|
Add MarkdownEditor component, asset image upload, and rich description editing
Introduce MarkdownEditor built on @mdxeditor/editor with headings,
lists, links, quotes, image upload with drag-and-drop, and themed CSS
integration. Add asset image upload API (routes, service, storage) and
wire image upload into InlineEditor multiline mode, NewIssueDialog,
NewProjectDialog, GoalDetail, IssueDetail, and ProjectDetail
description fields. Tighten prompt template editor styling.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 12:50:45 -06:00
|
|
|
const uploadImage = useMutation({
|
|
|
|
|
mutationFn: async (file: File) => {
|
2026-03-02 16:44:03 -06:00
|
|
|
if (!resolvedCompanyId) throw new Error("No company selected");
|
|
|
|
|
return assetsApi.uploadImage(resolvedCompanyId, file, `projects/${projectLookupRef || "draft"}`);
|
Add MarkdownEditor component, asset image upload, and rich description editing
Introduce MarkdownEditor built on @mdxeditor/editor with headings,
lists, links, quotes, image upload with drag-and-drop, and themed CSS
integration. Add asset image upload API (routes, service, storage) and
wire image upload into InlineEditor multiline mode, NewIssueDialog,
NewProjectDialog, GoalDetail, IssueDetail, and ProjectDetail
description fields. Tighten prompt template editor styling.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 12:50:45 -06:00
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
Add detail pages, property panels, and restyle list pages
New pages: AgentDetail, GoalDetail, IssueDetail, ProjectDetail, Inbox,
MyIssues. New feature components: AgentProperties, GoalProperties,
IssueProperties, ProjectProperties, GoalTree, NewIssueDialog. Add
heartbeats API client. Restyle all list pages (Agents, Issues, Goals,
Projects, Dashboard, Costs, Activity, Org) with EntityRow, FilterBar,
and improved layouts. Add routing for detail views.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 09:57:06 -06:00
|
|
|
useEffect(() => {
|
|
|
|
|
setBreadcrumbs([
|
|
|
|
|
{ label: "Projects", href: "/projects" },
|
2026-03-02 16:44:03 -06:00
|
|
|
{ label: project?.name ?? routeProjectRef ?? "Project" },
|
Add detail pages, property panels, and restyle list pages
New pages: AgentDetail, GoalDetail, IssueDetail, ProjectDetail, Inbox,
MyIssues. New feature components: AgentProperties, GoalProperties,
IssueProperties, ProjectProperties, GoalTree, NewIssueDialog. Add
heartbeats API client. Restyle all list pages (Agents, Issues, Goals,
Projects, Dashboard, Costs, Activity, Org) with EntityRow, FilterBar,
and improved layouts. Add routing for detail views.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 09:57:06 -06:00
|
|
|
]);
|
2026-03-02 16:44:03 -06:00
|
|
|
}, [setBreadcrumbs, project, routeProjectRef]);
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
if (!project) return;
|
|
|
|
|
if (routeProjectRef === canonicalProjectRef) return;
|
|
|
|
|
if (activeTab === "overview") {
|
|
|
|
|
navigate(`/projects/${canonicalProjectRef}/overview`, { replace: true });
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (activeTab === "list") {
|
|
|
|
|
if (filter) {
|
|
|
|
|
navigate(`/projects/${canonicalProjectRef}/issues/${filter}`, { replace: true });
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
navigate(`/projects/${canonicalProjectRef}/issues`, { replace: true });
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
navigate(`/projects/${canonicalProjectRef}`, { replace: true });
|
|
|
|
|
}, [project, routeProjectRef, canonicalProjectRef, activeTab, filter, navigate]);
|
Add detail pages, property panels, and restyle list pages
New pages: AgentDetail, GoalDetail, IssueDetail, ProjectDetail, Inbox,
MyIssues. New feature components: AgentProperties, GoalProperties,
IssueProperties, ProjectProperties, GoalTree, NewIssueDialog. Add
heartbeats API client. Restyle all list pages (Agents, Issues, Goals,
Projects, Dashboard, Costs, Activity, Org) with EntityRow, FilterBar,
and improved layouts. Add routing for detail views.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 09:57:06 -06:00
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
if (project) {
|
2026-02-20 10:32:32 -06:00
|
|
|
openPanel(<ProjectProperties project={project} onUpdate={(data) => updateProject.mutate(data)} />);
|
Add detail pages, property panels, and restyle list pages
New pages: AgentDetail, GoalDetail, IssueDetail, ProjectDetail, Inbox,
MyIssues. New feature components: AgentProperties, GoalProperties,
IssueProperties, ProjectProperties, GoalTree, NewIssueDialog. Add
heartbeats API client. Restyle all list pages (Agents, Issues, Goals,
Projects, Dashboard, Costs, Activity, Org) with EntityRow, FilterBar,
and improved layouts. Add routing for detail views.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 09:57:06 -06:00
|
|
|
}
|
|
|
|
|
return () => closePanel();
|
|
|
|
|
}, [project]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
|
|
|
|
2026-02-23 09:56:31 -06:00
|
|
|
// Redirect bare /projects/:id to /projects/:id/issues
|
2026-03-02 16:44:03 -06:00
|
|
|
if (routeProjectRef && activeTab === null) {
|
|
|
|
|
return <Navigate to={`/projects/${canonicalProjectRef}/issues`} replace />;
|
2026-02-23 09:56:31 -06:00
|
|
|
}
|
|
|
|
|
|
2026-03-02 16:44:03 -06:00
|
|
|
if (isLoading) return <PageSkeleton variant="detail" />;
|
Add detail pages, property panels, and restyle list pages
New pages: AgentDetail, GoalDetail, IssueDetail, ProjectDetail, Inbox,
MyIssues. New feature components: AgentProperties, GoalProperties,
IssueProperties, ProjectProperties, GoalTree, NewIssueDialog. Add
heartbeats API client. Restyle all list pages (Agents, Issues, Goals,
Projects, Dashboard, Costs, Activity, Org) with EntityRow, FilterBar,
and improved layouts. Add routing for detail views.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 09:57:06 -06:00
|
|
|
if (error) return <p className="text-sm text-destructive">{error.message}</p>;
|
|
|
|
|
if (!project) return null;
|
|
|
|
|
|
2026-02-23 09:56:31 -06:00
|
|
|
const handleTabChange = (tab: ProjectTab) => {
|
|
|
|
|
if (tab === "overview") {
|
2026-03-02 16:44:03 -06:00
|
|
|
navigate(`/projects/${canonicalProjectRef}/overview`);
|
2026-02-23 09:56:31 -06:00
|
|
|
} else {
|
2026-03-02 16:44:03 -06:00
|
|
|
navigate(`/projects/${canonicalProjectRef}/issues`);
|
2026-02-23 09:56:31 -06:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
Add detail pages, property panels, and restyle list pages
New pages: AgentDetail, GoalDetail, IssueDetail, ProjectDetail, Inbox,
MyIssues. New feature components: AgentProperties, GoalProperties,
IssueProperties, ProjectProperties, GoalTree, NewIssueDialog. Add
heartbeats API client. Restyle all list pages (Agents, Issues, Goals,
Projects, Dashboard, Costs, Activity, Org) with EntityRow, FilterBar,
and improved layouts. Add routing for detail views.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 09:57:06 -06:00
|
|
|
return (
|
|
|
|
|
<div className="space-y-6">
|
2026-02-23 14:41:21 -06:00
|
|
|
<div className="flex items-start gap-3">
|
|
|
|
|
<div className="h-7 flex items-center">
|
|
|
|
|
<ColorPicker
|
|
|
|
|
currentColor={project.color ?? "#6366f1"}
|
|
|
|
|
onSelect={(color) => updateProject.mutate({ color })}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
2026-02-20 10:32:32 -06:00
|
|
|
<InlineEditor
|
|
|
|
|
value={project.name}
|
|
|
|
|
onSave={(name) => updateProject.mutate({ name })}
|
|
|
|
|
as="h2"
|
|
|
|
|
className="text-xl font-bold"
|
|
|
|
|
/>
|
2026-03-05 18:57:48 -06:00
|
|
|
<Button
|
|
|
|
|
variant="ghost"
|
|
|
|
|
size="icon-xs"
|
|
|
|
|
className="ml-auto md:hidden shrink-0"
|
|
|
|
|
onClick={() => setMobilePropsOpen(true)}
|
|
|
|
|
title="Properties"
|
|
|
|
|
>
|
|
|
|
|
<SlidersHorizontal className="h-4 w-4" />
|
|
|
|
|
</Button>
|
2026-03-05 15:25:19 -06:00
|
|
|
<Button
|
|
|
|
|
variant="ghost"
|
|
|
|
|
size="icon-xs"
|
|
|
|
|
className={cn(
|
|
|
|
|
"shrink-0 ml-auto transition-opacity duration-200 hidden md:flex",
|
|
|
|
|
panelVisible ? "opacity-0 pointer-events-none w-0 overflow-hidden" : "opacity-100",
|
|
|
|
|
)}
|
|
|
|
|
onClick={() => setPanelVisible(true)}
|
|
|
|
|
title="Show properties"
|
|
|
|
|
>
|
|
|
|
|
<SlidersHorizontal className="h-4 w-4" />
|
|
|
|
|
</Button>
|
2026-02-23 09:56:31 -06:00
|
|
|
</div>
|
2026-02-20 10:32:32 -06:00
|
|
|
|
2026-02-23 09:56:31 -06:00
|
|
|
{/* Top-level project tabs */}
|
|
|
|
|
<div className="flex items-center gap-1 border-b border-border">
|
|
|
|
|
<button
|
|
|
|
|
className={`px-3 py-2 text-sm font-medium transition-colors border-b-2 ${
|
|
|
|
|
activeTab === "overview"
|
|
|
|
|
? "border-foreground text-foreground"
|
|
|
|
|
: "border-transparent text-muted-foreground hover:text-foreground"
|
|
|
|
|
}`}
|
|
|
|
|
onClick={() => handleTabChange("overview")}
|
|
|
|
|
>
|
|
|
|
|
Overview
|
|
|
|
|
</button>
|
|
|
|
|
<button
|
|
|
|
|
className={`px-3 py-2 text-sm font-medium transition-colors border-b-2 ${
|
|
|
|
|
activeTab === "list"
|
|
|
|
|
? "border-foreground text-foreground"
|
|
|
|
|
: "border-transparent text-muted-foreground hover:text-foreground"
|
|
|
|
|
}`}
|
|
|
|
|
onClick={() => handleTabChange("list")}
|
|
|
|
|
>
|
|
|
|
|
List
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* Tab content */}
|
|
|
|
|
{activeTab === "overview" && (
|
|
|
|
|
<OverviewContent
|
|
|
|
|
project={project}
|
|
|
|
|
onUpdate={(data) => updateProject.mutate(data)}
|
Add MarkdownEditor component, asset image upload, and rich description editing
Introduce MarkdownEditor built on @mdxeditor/editor with headings,
lists, links, quotes, image upload with drag-and-drop, and themed CSS
integration. Add asset image upload API (routes, service, storage) and
wire image upload into InlineEditor multiline mode, NewIssueDialog,
NewProjectDialog, GoalDetail, IssueDetail, and ProjectDetail
description fields. Tighten prompt template editor styling.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 12:50:45 -06:00
|
|
|
imageUploadHandler={async (file) => {
|
|
|
|
|
const asset = await uploadImage.mutateAsync(file);
|
|
|
|
|
return asset.contentPath;
|
|
|
|
|
}}
|
2026-02-20 10:32:32 -06:00
|
|
|
/>
|
2026-02-23 09:56:31 -06:00
|
|
|
)}
|
Add detail pages, property panels, and restyle list pages
New pages: AgentDetail, GoalDetail, IssueDetail, ProjectDetail, Inbox,
MyIssues. New feature components: AgentProperties, GoalProperties,
IssueProperties, ProjectProperties, GoalTree, NewIssueDialog. Add
heartbeats API client. Restyle all list pages (Agents, Issues, Goals,
Projects, Dashboard, Costs, Activity, Org) with EntityRow, FilterBar,
and improved layouts. Add routing for detail views.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 09:57:06 -06:00
|
|
|
|
2026-03-02 16:44:03 -06:00
|
|
|
{activeTab === "list" && project?.id && resolvedCompanyId && (
|
|
|
|
|
<ProjectIssuesList projectId={project.id} companyId={resolvedCompanyId} />
|
2026-02-23 09:56:31 -06:00
|
|
|
)}
|
2026-03-05 18:57:48 -06:00
|
|
|
|
|
|
|
|
{/* Mobile properties drawer */}
|
|
|
|
|
<Sheet open={mobilePropsOpen} onOpenChange={setMobilePropsOpen}>
|
|
|
|
|
<SheetContent side="bottom" className="max-h-[85dvh] pb-[env(safe-area-inset-bottom)]">
|
|
|
|
|
<SheetHeader>
|
|
|
|
|
<SheetTitle className="text-sm">Properties</SheetTitle>
|
|
|
|
|
</SheetHeader>
|
|
|
|
|
<ScrollArea className="flex-1 overflow-y-auto">
|
|
|
|
|
<div className="px-4 pb-4">
|
|
|
|
|
<ProjectProperties project={project} onUpdate={(data) => updateProject.mutate(data)} />
|
|
|
|
|
</div>
|
|
|
|
|
</ScrollArea>
|
|
|
|
|
</SheetContent>
|
|
|
|
|
</Sheet>
|
Add detail pages, property panels, and restyle list pages
New pages: AgentDetail, GoalDetail, IssueDetail, ProjectDetail, Inbox,
MyIssues. New feature components: AgentProperties, GoalProperties,
IssueProperties, ProjectProperties, GoalTree, NewIssueDialog. Add
heartbeats API client. Restyle all list pages (Agents, Issues, Goals,
Projects, Dashboard, Costs, Activity, Org) with EntityRow, FilterBar,
and improved layouts. Add routing for detail views.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 09:57:06 -06:00
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|