Expand NewIssueDialog with richer form fields. Add NewProjectDialog. Enhance CommandPalette with more actions and search. Improve CompanySwitcher, EmptyState, and IssueProperties. Flesh out Activity, Companies, Dashboard, and Inbox pages with real content and layouts. Refine sidebar, routing, and dialog context. CSS tweaks for dark theme. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
83 lines
2.7 KiB
TypeScript
83 lines
2.7 KiB
TypeScript
import { useCallback, useEffect } from "react";
|
|
import { useNavigate } from "react-router-dom";
|
|
import { projectsApi } from "../api/projects";
|
|
import { useApi } from "../hooks/useApi";
|
|
import { useCompany } from "../context/CompanyContext";
|
|
import { useDialog } from "../context/DialogContext";
|
|
import { useBreadcrumbs } from "../context/BreadcrumbContext";
|
|
import { EntityRow } from "../components/EntityRow";
|
|
import { StatusBadge } from "../components/StatusBadge";
|
|
import { EmptyState } from "../components/EmptyState";
|
|
import { formatDate } from "../lib/utils";
|
|
import { Button } from "@/components/ui/button";
|
|
import { Hexagon, Plus } from "lucide-react";
|
|
|
|
export function Projects() {
|
|
const { selectedCompanyId } = useCompany();
|
|
const { openNewProject } = useDialog();
|
|
const { setBreadcrumbs } = useBreadcrumbs();
|
|
const navigate = useNavigate();
|
|
|
|
useEffect(() => {
|
|
setBreadcrumbs([{ label: "Projects" }]);
|
|
}, [setBreadcrumbs]);
|
|
|
|
const fetcher = useCallback(() => {
|
|
if (!selectedCompanyId) return Promise.resolve([]);
|
|
return projectsApi.list(selectedCompanyId);
|
|
}, [selectedCompanyId]);
|
|
|
|
const { data: projects, loading, error } = useApi(fetcher);
|
|
|
|
if (!selectedCompanyId) {
|
|
return <EmptyState icon={Hexagon} message="Select a company to view projects." />;
|
|
}
|
|
|
|
return (
|
|
<div className="space-y-4">
|
|
<div className="flex items-center justify-between">
|
|
<h2 className="text-lg font-semibold">Projects</h2>
|
|
<Button size="sm" onClick={openNewProject}>
|
|
<Plus className="h-4 w-4 mr-1" />
|
|
Add Project
|
|
</Button>
|
|
</div>
|
|
|
|
{loading && <p className="text-sm text-muted-foreground">Loading...</p>}
|
|
{error && <p className="text-sm text-destructive">{error.message}</p>}
|
|
|
|
{projects && projects.length === 0 && (
|
|
<EmptyState
|
|
icon={Hexagon}
|
|
message="No projects yet."
|
|
action="Add Project"
|
|
onAction={openNewProject}
|
|
/>
|
|
)}
|
|
|
|
{projects && projects.length > 0 && (
|
|
<div className="border border-border rounded-md">
|
|
{projects.map((project) => (
|
|
<EntityRow
|
|
key={project.id}
|
|
title={project.name}
|
|
subtitle={project.description ?? undefined}
|
|
onClick={() => navigate(`/projects/${project.id}`)}
|
|
trailing={
|
|
<div className="flex items-center gap-3">
|
|
{project.targetDate && (
|
|
<span className="text-xs text-muted-foreground">
|
|
{formatDate(project.targetDate)}
|
|
</span>
|
|
)}
|
|
<StatusBadge status={project.status} />
|
|
</div>
|
|
}
|
|
/>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|