2026-02-17 20:46:12 -06:00
|
|
|
import { useState, useEffect, useRef, useCallback } from "react";
|
2026-02-17 12:24:48 -06:00
|
|
|
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
|
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 { useDialog } from "../context/DialogContext";
|
|
|
|
|
import { useCompany } from "../context/CompanyContext";
|
|
|
|
|
import { issuesApi } from "../api/issues";
|
2026-02-17 10:53:20 -06:00
|
|
|
import { projectsApi } from "../api/projects";
|
2026-02-17 12:24:48 -06:00
|
|
|
import { agentsApi } from "../api/agents";
|
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";
|
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 {
|
|
|
|
|
Dialog,
|
|
|
|
|
DialogContent,
|
|
|
|
|
} from "@/components/ui/dialog";
|
|
|
|
|
import { Button } from "@/components/ui/button";
|
|
|
|
|
import {
|
2026-02-17 10:53:20 -06:00
|
|
|
Popover,
|
|
|
|
|
PopoverContent,
|
|
|
|
|
PopoverTrigger,
|
|
|
|
|
} from "@/components/ui/popover";
|
|
|
|
|
import {
|
|
|
|
|
Maximize2,
|
|
|
|
|
Minimize2,
|
|
|
|
|
MoreHorizontal,
|
|
|
|
|
CircleDot,
|
|
|
|
|
Minus,
|
|
|
|
|
ArrowUp,
|
|
|
|
|
ArrowDown,
|
|
|
|
|
AlertTriangle,
|
|
|
|
|
User,
|
|
|
|
|
Hexagon,
|
|
|
|
|
Tag,
|
|
|
|
|
Calendar,
|
|
|
|
|
} from "lucide-react";
|
|
|
|
|
import { cn } from "../lib/utils";
|
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 { MarkdownEditor, type MarkdownEditorRef } from "./MarkdownEditor";
|
2026-02-17 10:53:20 -06:00
|
|
|
import type { Project, Agent } from "@paperclip/shared";
|
|
|
|
|
|
2026-02-17 20:46:12 -06:00
|
|
|
const DRAFT_KEY = "paperclip:issue-draft";
|
|
|
|
|
const DEBOUNCE_MS = 800;
|
|
|
|
|
|
|
|
|
|
interface IssueDraft {
|
|
|
|
|
title: string;
|
|
|
|
|
description: string;
|
|
|
|
|
status: string;
|
|
|
|
|
priority: string;
|
|
|
|
|
assigneeId: string;
|
|
|
|
|
projectId: string;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function loadDraft(): IssueDraft | null {
|
|
|
|
|
try {
|
|
|
|
|
const raw = localStorage.getItem(DRAFT_KEY);
|
|
|
|
|
if (!raw) return null;
|
|
|
|
|
return JSON.parse(raw) as IssueDraft;
|
|
|
|
|
} catch {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function saveDraft(draft: IssueDraft) {
|
|
|
|
|
localStorage.setItem(DRAFT_KEY, JSON.stringify(draft));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function clearDraft() {
|
|
|
|
|
localStorage.removeItem(DRAFT_KEY);
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-17 10:53:20 -06:00
|
|
|
const statuses = [
|
|
|
|
|
{ value: "backlog", label: "Backlog", color: "text-muted-foreground" },
|
|
|
|
|
{ value: "todo", label: "Todo", color: "text-blue-400" },
|
|
|
|
|
{ value: "in_progress", label: "In Progress", color: "text-yellow-400" },
|
|
|
|
|
{ value: "in_review", label: "In Review", color: "text-violet-400" },
|
|
|
|
|
{ value: "done", label: "Done", color: "text-green-400" },
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
const priorities = [
|
|
|
|
|
{ value: "critical", label: "Critical", icon: AlertTriangle, color: "text-red-400" },
|
|
|
|
|
{ value: "high", label: "High", icon: ArrowUp, color: "text-orange-400" },
|
|
|
|
|
{ value: "medium", label: "Medium", icon: Minus, color: "text-yellow-400" },
|
|
|
|
|
{ value: "low", label: "Low", icon: ArrowDown, color: "text-blue-400" },
|
|
|
|
|
];
|
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
|
|
|
export function NewIssueDialog() {
|
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 { newIssueOpen, newIssueDefaults, closeNewIssue } = useDialog();
|
2026-02-17 10:53:20 -06:00
|
|
|
const { selectedCompanyId, selectedCompany } = useCompany();
|
2026-02-17 12:24:48 -06:00
|
|
|
const queryClient = useQueryClient();
|
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 [title, setTitle] = useState("");
|
|
|
|
|
const [description, setDescription] = useState("");
|
2026-02-17 10:53:20 -06:00
|
|
|
const [status, setStatus] = useState("todo");
|
|
|
|
|
const [priority, setPriority] = useState("");
|
|
|
|
|
const [assigneeId, setAssigneeId] = useState("");
|
|
|
|
|
const [projectId, setProjectId] = useState("");
|
|
|
|
|
const [expanded, setExpanded] = useState(false);
|
2026-02-17 20:46:12 -06:00
|
|
|
const draftTimer = useRef<ReturnType<typeof setTimeout> | null>(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 10:53:20 -06:00
|
|
|
// Popover states
|
|
|
|
|
const [statusOpen, setStatusOpen] = useState(false);
|
|
|
|
|
const [priorityOpen, setPriorityOpen] = useState(false);
|
|
|
|
|
const [assigneeOpen, setAssigneeOpen] = useState(false);
|
2026-02-20 10:32:32 -06:00
|
|
|
const [assigneeSearch, setAssigneeSearch] = useState("");
|
2026-02-17 10:53:20 -06:00
|
|
|
const [projectOpen, setProjectOpen] = useState(false);
|
|
|
|
|
const [moreOpen, setMoreOpen] = useState(false);
|
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 descriptionEditorRef = useRef<MarkdownEditorRef>(null);
|
2026-02-17 10:53:20 -06:00
|
|
|
|
2026-02-17 12:24:48 -06:00
|
|
|
const { data: agents } = useQuery({
|
|
|
|
|
queryKey: queryKeys.agents.list(selectedCompanyId!),
|
|
|
|
|
queryFn: () => agentsApi.list(selectedCompanyId!),
|
|
|
|
|
enabled: !!selectedCompanyId && newIssueOpen,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const { data: projects } = useQuery({
|
|
|
|
|
queryKey: queryKeys.projects.list(selectedCompanyId!),
|
|
|
|
|
queryFn: () => projectsApi.list(selectedCompanyId!),
|
|
|
|
|
enabled: !!selectedCompanyId && newIssueOpen,
|
|
|
|
|
});
|
2026-02-17 10:53:20 -06:00
|
|
|
|
2026-02-17 12:24:48 -06:00
|
|
|
const createIssue = useMutation({
|
|
|
|
|
mutationFn: (data: Record<string, unknown>) =>
|
|
|
|
|
issuesApi.create(selectedCompanyId!, data),
|
|
|
|
|
onSuccess: () => {
|
|
|
|
|
queryClient.invalidateQueries({ queryKey: queryKeys.issues.list(selectedCompanyId!) });
|
UI: approval detail page, agent hiring UX, costs breakdown, sidebar badges, and dashboard improvements
Add ApprovalDetail page with comment thread, revision request/resubmit flow,
and ApprovalPayload component for structured payload display. Extend AgentDetail
with permissions management, config revision history, and duplicate action.
Add agent hire dialog with permission-gated access. Rework Costs page with
per-agent breakdown table and period filtering. Add sidebar badge counts for
pending approvals and inbox items. Enhance Dashboard with live metrics and
sparkline trends. Extend Agents list with pending_approval status and bulk
actions. Update IssueDetail with approval linking. Various component improvements
to MetricCard, InlineEditor, CommentThread, and StatusBadge.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 13:03:08 -06:00
|
|
|
if (draftTimer.current) clearTimeout(draftTimer.current);
|
2026-02-17 20:46:12 -06:00
|
|
|
clearDraft();
|
2026-02-17 12:24:48 -06:00
|
|
|
reset();
|
|
|
|
|
closeNewIssue();
|
|
|
|
|
},
|
|
|
|
|
});
|
2026-02-17 10:53:20 -06:00
|
|
|
|
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 uploadDescriptionImage = useMutation({
|
|
|
|
|
mutationFn: async (file: File) => {
|
|
|
|
|
if (!selectedCompanyId) throw new Error("No company selected");
|
|
|
|
|
return assetsApi.uploadImage(selectedCompanyId, file, "issues/drafts");
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
2026-02-17 20:46:12 -06:00
|
|
|
// Debounced draft saving
|
|
|
|
|
const scheduleSave = useCallback(
|
|
|
|
|
(draft: IssueDraft) => {
|
|
|
|
|
if (draftTimer.current) clearTimeout(draftTimer.current);
|
|
|
|
|
draftTimer.current = setTimeout(() => {
|
|
|
|
|
if (draft.title.trim()) saveDraft(draft);
|
|
|
|
|
}, DEBOUNCE_MS);
|
|
|
|
|
},
|
|
|
|
|
[],
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// Save draft on meaningful changes
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
if (!newIssueOpen) return;
|
|
|
|
|
scheduleSave({ title, description, status, priority, assigneeId, projectId });
|
|
|
|
|
}, [title, description, status, priority, assigneeId, projectId, newIssueOpen, scheduleSave]);
|
|
|
|
|
|
|
|
|
|
// Restore draft or apply defaults when dialog opens
|
2026-02-17 10:53:20 -06:00
|
|
|
useEffect(() => {
|
2026-02-17 20:46:12 -06:00
|
|
|
if (!newIssueOpen) return;
|
|
|
|
|
|
|
|
|
|
const draft = loadDraft();
|
|
|
|
|
if (draft && draft.title.trim()) {
|
|
|
|
|
setTitle(draft.title);
|
|
|
|
|
setDescription(draft.description);
|
|
|
|
|
setStatus(draft.status || "todo");
|
|
|
|
|
setPriority(draft.priority);
|
|
|
|
|
setAssigneeId(newIssueDefaults.assigneeAgentId ?? draft.assigneeId);
|
|
|
|
|
setProjectId(newIssueDefaults.projectId ?? draft.projectId);
|
|
|
|
|
} else {
|
2026-02-17 10:53:20 -06:00
|
|
|
setStatus(newIssueDefaults.status ?? "todo");
|
|
|
|
|
setPriority(newIssueDefaults.priority ?? "");
|
|
|
|
|
setProjectId(newIssueDefaults.projectId ?? "");
|
2026-02-17 20:07:49 -06:00
|
|
|
setAssigneeId(newIssueDefaults.assigneeAgentId ?? "");
|
2026-02-17 10:53:20 -06:00
|
|
|
}
|
|
|
|
|
}, [newIssueOpen, newIssueDefaults]);
|
|
|
|
|
|
2026-02-17 20:46:12 -06:00
|
|
|
// Cleanup timer on unmount
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
return () => {
|
|
|
|
|
if (draftTimer.current) clearTimeout(draftTimer.current);
|
|
|
|
|
};
|
|
|
|
|
}, []);
|
|
|
|
|
|
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
|
|
|
function reset() {
|
|
|
|
|
setTitle("");
|
|
|
|
|
setDescription("");
|
|
|
|
|
setStatus("todo");
|
2026-02-17 10:53:20 -06:00
|
|
|
setPriority("");
|
|
|
|
|
setAssigneeId("");
|
|
|
|
|
setProjectId("");
|
|
|
|
|
setExpanded(false);
|
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 20:46:12 -06:00
|
|
|
function discardDraft() {
|
|
|
|
|
clearDraft();
|
|
|
|
|
reset();
|
|
|
|
|
closeNewIssue();
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-17 12:24:48 -06:00
|
|
|
function handleSubmit() {
|
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 (!selectedCompanyId || !title.trim()) return;
|
2026-02-17 12:24:48 -06:00
|
|
|
createIssue.mutate({
|
|
|
|
|
title: title.trim(),
|
|
|
|
|
description: description.trim() || undefined,
|
|
|
|
|
status,
|
|
|
|
|
priority: priority || "medium",
|
|
|
|
|
...(assigneeId ? { assigneeAgentId: assigneeId } : {}),
|
|
|
|
|
...(projectId ? { projectId } : {}),
|
|
|
|
|
});
|
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 10:53:20 -06:00
|
|
|
function handleKeyDown(e: React.KeyboardEvent) {
|
|
|
|
|
if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) {
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
handleSubmit();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-17 20:46:12 -06:00
|
|
|
const hasDraft = title.trim().length > 0 || description.trim().length > 0;
|
2026-02-17 10:53:20 -06:00
|
|
|
const currentStatus = statuses.find((s) => s.value === status) ?? statuses[1]!;
|
|
|
|
|
const currentPriority = priorities.find((p) => p.value === priority);
|
|
|
|
|
const currentAssignee = (agents ?? []).find((a) => a.id === assigneeId);
|
|
|
|
|
const currentProject = (projects ?? []).find((p) => p.id === projectId);
|
|
|
|
|
|
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 (
|
|
|
|
|
<Dialog
|
|
|
|
|
open={newIssueOpen}
|
|
|
|
|
onOpenChange={(open) => {
|
2026-02-17 20:46:12 -06:00
|
|
|
if (!open) closeNewIssue();
|
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 10:53:20 -06:00
|
|
|
<DialogContent
|
|
|
|
|
showCloseButton={false}
|
|
|
|
|
className={cn(
|
2026-02-17 20:46:12 -06:00
|
|
|
"p-0 gap-0 flex flex-col",
|
|
|
|
|
expanded
|
|
|
|
|
? "sm:max-w-2xl h-[calc(100vh-6rem)] max-h-[calc(100vh-6rem)]"
|
|
|
|
|
: "sm:max-w-lg"
|
2026-02-17 10:53:20 -06:00
|
|
|
)}
|
|
|
|
|
onKeyDown={handleKeyDown}
|
|
|
|
|
>
|
|
|
|
|
{/* Header bar */}
|
2026-02-17 20:46:12 -06:00
|
|
|
<div className="flex items-center justify-between px-4 py-2.5 border-b border-border shrink-0">
|
2026-02-17 10:53:20 -06:00
|
|
|
<div className="flex items-center gap-2 text-sm text-muted-foreground">
|
|
|
|
|
{selectedCompany && (
|
|
|
|
|
<span className="bg-muted px-1.5 py-0.5 rounded text-xs font-medium">
|
|
|
|
|
{selectedCompany.name.slice(0, 3).toUpperCase()}
|
|
|
|
|
</span>
|
|
|
|
|
)}
|
|
|
|
|
<span className="text-muted-foreground/60">›</span>
|
|
|
|
|
<span>New issue</span>
|
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>
|
2026-02-17 10:53:20 -06:00
|
|
|
<div className="flex items-center gap-1">
|
|
|
|
|
<Button
|
|
|
|
|
variant="ghost"
|
|
|
|
|
size="icon-xs"
|
|
|
|
|
className="text-muted-foreground"
|
|
|
|
|
onClick={() => setExpanded(!expanded)}
|
|
|
|
|
>
|
|
|
|
|
{expanded ? <Minimize2 className="h-3.5 w-3.5" /> : <Maximize2 className="h-3.5 w-3.5" />}
|
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
|
|
|
</Button>
|
2026-02-17 10:53:20 -06:00
|
|
|
<Button
|
|
|
|
|
variant="ghost"
|
|
|
|
|
size="icon-xs"
|
|
|
|
|
className="text-muted-foreground"
|
2026-02-17 20:46:12 -06:00
|
|
|
onClick={() => closeNewIssue()}
|
2026-02-17 10:53:20 -06:00
|
|
|
>
|
|
|
|
|
<span className="text-lg leading-none">×</span>
|
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
|
|
|
</Button>
|
2026-02-17 10:53:20 -06:00
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* Title */}
|
2026-02-17 20:46:12 -06:00
|
|
|
<div className="px-4 pt-4 pb-2 shrink-0">
|
2026-02-17 10:53:20 -06:00
|
|
|
<input
|
2026-02-17 20:46:12 -06:00
|
|
|
className="w-full text-lg font-semibold bg-transparent outline-none placeholder:text-muted-foreground/50"
|
2026-02-17 10:53:20 -06:00
|
|
|
placeholder="Issue title"
|
|
|
|
|
value={title}
|
|
|
|
|
onChange={(e) => setTitle(e.target.value)}
|
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
|
|
|
onKeyDown={(e) => {
|
|
|
|
|
if (e.key === "Tab" && !e.shiftKey) {
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
descriptionEditorRef.current?.focus();
|
|
|
|
|
}
|
|
|
|
|
}}
|
2026-02-17 10:53:20 -06:00
|
|
|
autoFocus
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* Description */}
|
2026-02-17 20:46:12 -06:00
|
|
|
<div className={cn("px-4 pb-2", expanded ? "flex-1 min-h-0" : "")}>
|
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
|
|
|
<MarkdownEditor
|
|
|
|
|
ref={descriptionEditorRef}
|
2026-02-17 10:53:20 -06:00
|
|
|
value={description}
|
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
|
|
|
onChange={setDescription}
|
|
|
|
|
placeholder="Add description..."
|
|
|
|
|
bordered={false}
|
|
|
|
|
contentClassName={cn("text-sm text-muted-foreground", expanded ? "min-h-[220px]" : "min-h-[120px]")}
|
|
|
|
|
imageUploadHandler={async (file) => {
|
|
|
|
|
const asset = await uploadDescriptionImage.mutateAsync(file);
|
|
|
|
|
return asset.contentPath;
|
|
|
|
|
}}
|
2026-02-17 10:53:20 -06:00
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* Property chips bar */}
|
2026-02-17 20:46:12 -06:00
|
|
|
<div className="flex items-center gap-1.5 px-4 py-2 border-t border-border flex-wrap shrink-0">
|
2026-02-17 10:53:20 -06:00
|
|
|
{/* Status chip */}
|
|
|
|
|
<Popover open={statusOpen} onOpenChange={setStatusOpen}>
|
|
|
|
|
<PopoverTrigger asChild>
|
|
|
|
|
<button className="inline-flex items-center gap-1.5 rounded-md border border-border px-2 py-1 text-xs hover:bg-accent/50 transition-colors">
|
|
|
|
|
<CircleDot className={cn("h-3 w-3", currentStatus.color)} />
|
|
|
|
|
{currentStatus.label}
|
|
|
|
|
</button>
|
|
|
|
|
</PopoverTrigger>
|
|
|
|
|
<PopoverContent className="w-36 p-1" align="start">
|
|
|
|
|
{statuses.map((s) => (
|
|
|
|
|
<button
|
|
|
|
|
key={s.value}
|
|
|
|
|
className={cn(
|
|
|
|
|
"flex items-center gap-2 w-full px-2 py-1.5 text-xs rounded hover:bg-accent/50",
|
|
|
|
|
s.value === status && "bg-accent"
|
|
|
|
|
)}
|
|
|
|
|
onClick={() => { setStatus(s.value); setStatusOpen(false); }}
|
|
|
|
|
>
|
|
|
|
|
<CircleDot className={cn("h-3 w-3", s.color)} />
|
|
|
|
|
{s.label}
|
|
|
|
|
</button>
|
|
|
|
|
))}
|
|
|
|
|
</PopoverContent>
|
|
|
|
|
</Popover>
|
|
|
|
|
|
|
|
|
|
{/* Priority chip */}
|
|
|
|
|
<Popover open={priorityOpen} onOpenChange={setPriorityOpen}>
|
|
|
|
|
<PopoverTrigger asChild>
|
|
|
|
|
<button className="inline-flex items-center gap-1.5 rounded-md border border-border px-2 py-1 text-xs hover:bg-accent/50 transition-colors">
|
|
|
|
|
{currentPriority ? (
|
|
|
|
|
<>
|
|
|
|
|
<currentPriority.icon className={cn("h-3 w-3", currentPriority.color)} />
|
|
|
|
|
{currentPriority.label}
|
|
|
|
|
</>
|
|
|
|
|
) : (
|
|
|
|
|
<>
|
|
|
|
|
<Minus className="h-3 w-3 text-muted-foreground" />
|
|
|
|
|
Priority
|
|
|
|
|
</>
|
|
|
|
|
)}
|
|
|
|
|
</button>
|
|
|
|
|
</PopoverTrigger>
|
|
|
|
|
<PopoverContent className="w-36 p-1" align="start">
|
|
|
|
|
{priorities.map((p) => (
|
|
|
|
|
<button
|
|
|
|
|
key={p.value}
|
|
|
|
|
className={cn(
|
|
|
|
|
"flex items-center gap-2 w-full px-2 py-1.5 text-xs rounded hover:bg-accent/50",
|
|
|
|
|
p.value === priority && "bg-accent"
|
|
|
|
|
)}
|
|
|
|
|
onClick={() => { setPriority(p.value); setPriorityOpen(false); }}
|
|
|
|
|
>
|
|
|
|
|
<p.icon className={cn("h-3 w-3", p.color)} />
|
|
|
|
|
{p.label}
|
|
|
|
|
</button>
|
|
|
|
|
))}
|
|
|
|
|
</PopoverContent>
|
|
|
|
|
</Popover>
|
|
|
|
|
|
|
|
|
|
{/* Assignee chip */}
|
2026-02-20 10:32:32 -06:00
|
|
|
<Popover open={assigneeOpen} onOpenChange={(open) => { setAssigneeOpen(open); if (!open) setAssigneeSearch(""); }}>
|
2026-02-17 10:53:20 -06:00
|
|
|
<PopoverTrigger asChild>
|
|
|
|
|
<button className="inline-flex items-center gap-1.5 rounded-md border border-border px-2 py-1 text-xs hover:bg-accent/50 transition-colors">
|
|
|
|
|
<User className="h-3 w-3 text-muted-foreground" />
|
|
|
|
|
{currentAssignee ? currentAssignee.name : "Assignee"}
|
|
|
|
|
</button>
|
|
|
|
|
</PopoverTrigger>
|
2026-02-20 10:32:32 -06:00
|
|
|
<PopoverContent className="w-52 p-1" align="start">
|
|
|
|
|
<input
|
|
|
|
|
className="w-full px-2 py-1.5 text-xs bg-transparent outline-none border-b border-border mb-1 placeholder:text-muted-foreground/50"
|
|
|
|
|
placeholder="Search agents..."
|
|
|
|
|
value={assigneeSearch}
|
|
|
|
|
onChange={(e) => setAssigneeSearch(e.target.value)}
|
|
|
|
|
autoFocus
|
|
|
|
|
/>
|
2026-02-17 10:53:20 -06:00
|
|
|
<button
|
|
|
|
|
className={cn(
|
|
|
|
|
"flex items-center gap-2 w-full px-2 py-1.5 text-xs rounded hover:bg-accent/50",
|
|
|
|
|
!assigneeId && "bg-accent"
|
|
|
|
|
)}
|
|
|
|
|
onClick={() => { setAssigneeId(""); setAssigneeOpen(false); }}
|
|
|
|
|
>
|
|
|
|
|
No assignee
|
|
|
|
|
</button>
|
2026-02-20 10:32:32 -06:00
|
|
|
{(agents ?? [])
|
|
|
|
|
.filter((a) => a.status !== "terminated")
|
|
|
|
|
.filter((a) => {
|
|
|
|
|
if (!assigneeSearch.trim()) return true;
|
|
|
|
|
const q = assigneeSearch.toLowerCase();
|
|
|
|
|
return a.name.toLowerCase().includes(q);
|
|
|
|
|
})
|
|
|
|
|
.map((a) => (
|
2026-02-17 10:53:20 -06:00
|
|
|
<button
|
|
|
|
|
key={a.id}
|
|
|
|
|
className={cn(
|
|
|
|
|
"flex items-center gap-2 w-full px-2 py-1.5 text-xs rounded hover:bg-accent/50",
|
|
|
|
|
a.id === assigneeId && "bg-accent"
|
|
|
|
|
)}
|
|
|
|
|
onClick={() => { setAssigneeId(a.id); setAssigneeOpen(false); }}
|
|
|
|
|
>
|
|
|
|
|
{a.name}
|
|
|
|
|
</button>
|
|
|
|
|
))}
|
|
|
|
|
</PopoverContent>
|
|
|
|
|
</Popover>
|
|
|
|
|
|
|
|
|
|
{/* Project chip */}
|
|
|
|
|
<Popover open={projectOpen} onOpenChange={setProjectOpen}>
|
|
|
|
|
<PopoverTrigger asChild>
|
|
|
|
|
<button className="inline-flex items-center gap-1.5 rounded-md border border-border px-2 py-1 text-xs hover:bg-accent/50 transition-colors">
|
|
|
|
|
<Hexagon className="h-3 w-3 text-muted-foreground" />
|
|
|
|
|
{currentProject ? currentProject.name : "Project"}
|
|
|
|
|
</button>
|
|
|
|
|
</PopoverTrigger>
|
|
|
|
|
<PopoverContent className="w-44 p-1" align="start">
|
|
|
|
|
<button
|
|
|
|
|
className={cn(
|
|
|
|
|
"flex items-center gap-2 w-full px-2 py-1.5 text-xs rounded hover:bg-accent/50",
|
|
|
|
|
!projectId && "bg-accent"
|
|
|
|
|
)}
|
|
|
|
|
onClick={() => { setProjectId(""); setProjectOpen(false); }}
|
|
|
|
|
>
|
|
|
|
|
No project
|
|
|
|
|
</button>
|
|
|
|
|
{(projects ?? []).map((p) => (
|
|
|
|
|
<button
|
|
|
|
|
key={p.id}
|
|
|
|
|
className={cn(
|
|
|
|
|
"flex items-center gap-2 w-full px-2 py-1.5 text-xs rounded hover:bg-accent/50",
|
|
|
|
|
p.id === projectId && "bg-accent"
|
|
|
|
|
)}
|
|
|
|
|
onClick={() => { setProjectId(p.id); setProjectOpen(false); }}
|
|
|
|
|
>
|
|
|
|
|
{p.name}
|
|
|
|
|
</button>
|
|
|
|
|
))}
|
|
|
|
|
</PopoverContent>
|
|
|
|
|
</Popover>
|
|
|
|
|
|
|
|
|
|
{/* Labels chip (placeholder) */}
|
|
|
|
|
<button className="inline-flex items-center gap-1.5 rounded-md border border-border px-2 py-1 text-xs hover:bg-accent/50 transition-colors text-muted-foreground">
|
|
|
|
|
<Tag className="h-3 w-3" />
|
|
|
|
|
Labels
|
|
|
|
|
</button>
|
|
|
|
|
|
|
|
|
|
{/* More (dates) */}
|
|
|
|
|
<Popover open={moreOpen} onOpenChange={setMoreOpen}>
|
|
|
|
|
<PopoverTrigger asChild>
|
|
|
|
|
<button className="inline-flex items-center justify-center rounded-md border border-border p-1 text-xs hover:bg-accent/50 transition-colors text-muted-foreground">
|
|
|
|
|
<MoreHorizontal className="h-3 w-3" />
|
|
|
|
|
</button>
|
|
|
|
|
</PopoverTrigger>
|
|
|
|
|
<PopoverContent className="w-44 p-1" align="start">
|
|
|
|
|
<button className="flex items-center gap-2 w-full px-2 py-1.5 text-xs rounded hover:bg-accent/50 text-muted-foreground">
|
|
|
|
|
<Calendar className="h-3 w-3" />
|
|
|
|
|
Start date
|
|
|
|
|
</button>
|
|
|
|
|
<button className="flex items-center gap-2 w-full px-2 py-1.5 text-xs rounded hover:bg-accent/50 text-muted-foreground">
|
|
|
|
|
<Calendar className="h-3 w-3" />
|
|
|
|
|
Due date
|
|
|
|
|
</button>
|
|
|
|
|
</PopoverContent>
|
|
|
|
|
</Popover>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* Footer */}
|
2026-02-17 20:46:12 -06:00
|
|
|
<div className="flex items-center justify-between px-4 py-2.5 border-t border-border shrink-0">
|
|
|
|
|
<Button
|
|
|
|
|
variant="ghost"
|
|
|
|
|
size="sm"
|
|
|
|
|
className="text-muted-foreground"
|
|
|
|
|
onClick={discardDraft}
|
|
|
|
|
disabled={!hasDraft && !loadDraft()}
|
|
|
|
|
>
|
|
|
|
|
Discard Draft
|
|
|
|
|
</Button>
|
2026-02-17 10:53:20 -06:00
|
|
|
<Button
|
|
|
|
|
size="sm"
|
2026-02-17 12:24:48 -06:00
|
|
|
disabled={!title.trim() || createIssue.isPending}
|
2026-02-17 10:53:20 -06:00
|
|
|
onClick={handleSubmit}
|
|
|
|
|
>
|
2026-02-17 20:46:12 -06:00
|
|
|
{createIssue.isPending ? "Creating..." : "Create Issue"}
|
2026-02-17 10:53:20 -06:00
|
|
|
</Button>
|
|
|
|
|
</div>
|
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
|
|
|
</DialogContent>
|
|
|
|
|
</Dialog>
|
|
|
|
|
);
|
|
|
|
|
}
|