diff --git a/ui/src/App.tsx b/ui/src/App.tsx index 83025d87..f7f97eeb 100644 --- a/ui/src/App.tsx +++ b/ui/src/App.tsx @@ -83,6 +83,50 @@ function CloudAccessGate() { return ; } +function boardRoutes() { + return ( + <> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + + ); +} + export function App() { return ( @@ -91,44 +135,13 @@ export function App() { } /> }> + {/* Company-prefixed routes: /PAP/issues/PAP-214 */} + }> + {boardRoutes()} + + {/* Non-prefixed routes: /issues/PAP-214 */} }> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> + {boardRoutes()} diff --git a/ui/src/components/CompanyRail.tsx b/ui/src/components/CompanyRail.tsx index f5c184eb..04770686 100644 --- a/ui/src/components/CompanyRail.tsx +++ b/ui/src/components/CompanyRail.tsx @@ -135,19 +135,26 @@ function SortableCompanyItem({ export function CompanyRail() { const { companies, selectedCompanyId, setSelectedCompanyId } = useCompany(); const { openOnboarding } = useDialog(); + const sidebarCompanies = useMemo( + () => companies.filter((company) => company.status !== "archived"), + [companies], + ); // Maintain sorted order in local state, synced from companies + localStorage const [orderedIds, setOrderedIds] = useState(() => - sortByStoredOrder(companies).map((c) => c.id) + sortByStoredOrder(sidebarCompanies).map((c) => c.id) ); // Re-sync orderedIds from localStorage whenever companies changes. // Handles initial data load (companies starts as [] before query resolves) // and subsequent refetches triggered by live updates. useEffect(() => { - if (companies.length === 0) return; - setOrderedIds(sortByStoredOrder(companies).map((c) => c.id)); - }, [companies]); + if (sidebarCompanies.length === 0) { + setOrderedIds([]); + return; + } + setOrderedIds(sortByStoredOrder(sidebarCompanies).map((c) => c.id)); + }, [sidebarCompanies]); // Sync order across tabs via the native storage event useEffect(() => { @@ -164,7 +171,7 @@ export function CompanyRail() { // Re-derive when companies change (new company added/removed) const orderedCompanies = useMemo(() => { - const byId = new Map(companies.map((c) => [c.id, c])); + const byId = new Map(sidebarCompanies.map((c) => [c.id, c])); const result: Company[] = []; for (const id of orderedIds) { const c = byId.get(id); @@ -178,7 +185,7 @@ export function CompanyRail() { result.push(c); } return result; - }, [companies, orderedIds]); + }, [sidebarCompanies, orderedIds]); // Require 8px of movement before starting a drag to avoid interfering with clicks const sensors = useSensors( diff --git a/ui/src/components/CompanySwitcher.tsx b/ui/src/components/CompanySwitcher.tsx index 17ddb3fa..3f68b9e8 100644 --- a/ui/src/components/CompanySwitcher.tsx +++ b/ui/src/components/CompanySwitcher.tsx @@ -26,6 +26,7 @@ function statusDotColor(status?: string): string { export function CompanySwitcher() { const { companies, selectedCompany, setSelectedCompanyId } = useCompany(); + const sidebarCompanies = companies.filter((company) => company.status !== "archived"); return ( @@ -48,7 +49,7 @@ export function CompanySwitcher() { Companies - {companies.map((company) => ( + {sidebarCompanies.map((company) => ( setSelectedCompanyId(company.id)} @@ -58,7 +59,7 @@ export function CompanySwitcher() { {company.name} ))} - {companies.length === 0 && ( + {sidebarCompanies.length === 0 && ( No companies )} diff --git a/ui/src/context/CompanyContext.tsx b/ui/src/context/CompanyContext.tsx index 00a45d98..d22e2b3e 100644 --- a/ui/src/context/CompanyContext.tsx +++ b/ui/src/context/CompanyContext.tsx @@ -67,19 +67,24 @@ export function CompanyProvider({ children }: { children: ReactNode }) { }, retry: false, }); + const sidebarCompanies = useMemo( + () => companies.filter((company) => company.status !== "archived"), + [companies], + ); // Auto-select first company when list loads useEffect(() => { if (companies.length === 0) return; + const selectableCompanies = sidebarCompanies.length > 0 ? sidebarCompanies : companies; const stored = localStorage.getItem(STORAGE_KEY); - if (stored && companies.some((c) => c.id === stored)) return; - if (selectedCompanyId && companies.some((c) => c.id === selectedCompanyId)) return; + if (stored && selectableCompanies.some((c) => c.id === stored)) return; + if (selectedCompanyId && selectableCompanies.some((c) => c.id === selectedCompanyId)) return; - const next = companies[0]!.id; + const next = selectableCompanies[0]!.id; setSelectedCompanyIdState(next); localStorage.setItem(STORAGE_KEY, next); - }, [companies, selectedCompanyId]); + }, [companies, selectedCompanyId, sidebarCompanies]); const setSelectedCompanyId = useCallback((companyId: string) => { setSelectedCompanyIdState(companyId); diff --git a/ui/src/pages/CompanySettings.tsx b/ui/src/pages/CompanySettings.tsx index d20deab7..c510c35f 100644 --- a/ui/src/pages/CompanySettings.tsx +++ b/ui/src/pages/CompanySettings.tsx @@ -11,7 +11,7 @@ import { CompanyPatternIcon } from "../components/CompanyPatternIcon"; import { Field, ToggleField, HintIcon } from "../components/agent-config-primitives"; export function CompanySettings() { - const { selectedCompany, selectedCompanyId } = useCompany(); + const { companies, selectedCompany, selectedCompanyId, setSelectedCompanyId } = useCompany(); const { setBreadcrumbs } = useBreadcrumbs(); const queryClient = useQueryClient(); @@ -74,6 +74,22 @@ export function CompanySettings() { setInviteError(err instanceof Error ? err.message : "Failed to create invite"); }, }); + const archiveMutation = useMutation({ + mutationFn: ({ + companyId, + nextCompanyId, + }: { + companyId: string; + nextCompanyId: string | null; + }) => companiesApi.archive(companyId).then(() => ({ nextCompanyId })), + onSuccess: async ({ nextCompanyId }) => { + if (nextCompanyId) { + setSelectedCompanyId(nextCompanyId); + } + await queryClient.invalidateQueries({ queryKey: queryKeys.companies.all }); + await queryClient.invalidateQueries({ queryKey: queryKeys.companies.stats }); + }, + }); useEffect(() => { setBreadcrumbs([ @@ -256,6 +272,48 @@ export function CompanySettings() { )} + + {/* Archive */} +
+
+ Archive +
+
+

+ Archive this company to hide it from the sidebar. This persists in the database. +

+
+ + {archiveMutation.isError && ( + + {archiveMutation.error instanceof Error + ? archiveMutation.error.message + : "Failed to archive company"} + + )} +
+
+
); }