Merge public-gh/master into paperclip-subissues

This commit is contained in:
Dotta
2026-03-17 09:42:31 -05:00
20 changed files with 825 additions and 79 deletions

View File

@@ -211,10 +211,10 @@ export function ProjectDetail() {
const { companies, selectedCompanyId, setSelectedCompanyId } = useCompany();
const { closePanel } = usePanel();
const { setBreadcrumbs } = useBreadcrumbs();
const { pushToast } = useToast();
const queryClient = useQueryClient();
const navigate = useNavigate();
const location = useLocation();
const { pushToast } = useToast();
const [fieldSaveStates, setFieldSaveStates] = useState<Partial<Record<ProjectConfigFieldKey, ProjectFieldSaveState>>>({});
const fieldSaveRequestIds = useRef<Partial<Record<ProjectConfigFieldKey, number>>>({});
const fieldSaveTimers = useRef<Partial<Record<ProjectConfigFieldKey, ReturnType<typeof setTimeout>>>>({});
@@ -286,13 +286,14 @@ export function ProjectDetail() {
{ archivedAt: archived ? new Date().toISOString() : null },
resolvedCompanyId ?? lookupCompanyId,
),
onSuccess: (_, archived) => {
onSuccess: (updatedProject, archived) => {
invalidateProject();
const name = updatedProject?.name ?? project?.name ?? "Project";
if (archived) {
pushToast({ title: "Project archived", tone: "success" });
pushToast({ title: `"${name}" has been archived`, tone: "success" });
navigate("/dashboard");
} else {
pushToast({ title: "Project unarchived", tone: "success" });
pushToast({ title: `"${name}" has been unarchived`, tone: "success" });
}
},
onError: (_, archived) => {
@@ -454,8 +455,24 @@ export function ProjectDetail() {
return <Navigate to={`/projects/${canonicalProjectRef}/issues`} replace />;
}
// Redirect bare /projects/:id to /projects/:id/issues
// Redirect bare /projects/:id to cached tab or default /issues
if (routeProjectRef && activeTab === null) {
let cachedTab: string | null = null;
if (project?.id) {
try { cachedTab = localStorage.getItem(`paperclip:project-tab:${project.id}`); } catch {}
}
if (cachedTab === "overview") {
return <Navigate to={`/projects/${canonicalProjectRef}/overview`} replace />;
}
if (cachedTab === "configuration") {
return <Navigate to={`/projects/${canonicalProjectRef}/configuration`} replace />;
}
if (cachedTab === "budget") {
return <Navigate to={`/projects/${canonicalProjectRef}/budget`} replace />;
}
if (isProjectPluginTab(cachedTab)) {
return <Navigate to={`/projects/${canonicalProjectRef}?tab=${encodeURIComponent(cachedTab)}`} replace />;
}
return <Navigate to={`/projects/${canonicalProjectRef}/issues`} replace />;
}
@@ -464,6 +481,10 @@ export function ProjectDetail() {
if (!project) return null;
const handleTabChange = (tab: ProjectTab) => {
// Cache the active tab per project
if (project?.id) {
try { localStorage.setItem(`paperclip:project-tab:${project.id}`, tab); } catch {}
}
if (isProjectPluginTab(tab)) {
navigate(`/projects/${canonicalProjectRef}?tab=${encodeURIComponent(tab)}`);
return;
@@ -538,8 +559,8 @@ export function ProjectDetail() {
<Tabs value={activeTab ?? "list"} onValueChange={(value) => handleTabChange(value as ProjectTab)}>
<PageTabBar
items={[
{ value: "list", label: "Issues" },
{ value: "overview", label: "Overview" },
{ value: "list", label: "List" },
{ value: "configuration", label: "Configuration" },
{ value: "budget", label: "Budget" },
...pluginTabItems.map((item) => ({