refactor(renderer): dismantle useAppController 47-prop god-hook
Sections now subscribe directly to the Zustand store via useSectionContext
(shared data derivation) and useAppStore fine-grained selectors. Delete
useAppController, useExecutionController, useOrganizationController, and
useCommunicationController; the three domain forwarders held no logic
beyond re-exporting store slices.
SectionContentView becomes a 50-line layout dispatcher taking only
{section, error, pageTransition}; cross-domain run actions (startRun /
cancelRun) move into App.tsx and are exposed to sections through a new
AppActionsContext alongside refresh / setError / openPathWithFeedback.
App.tsx now calls useUIController + useCompanyController directly and
composes palette + navigation plumbing itself.
This commit is contained in:
1 parent
de77f5f38e
commit
e2253b92fa
12 files changed
+339
-897
No files matched your search
+161
-130
@@ -1,3 +1,4 @@
|
||||
import { useCallback, useMemo } from "react";
|
||||
import { CircleNotch, TerminalWindow } from "@phosphor-icons/react";
|
||||
import { LocaleContext, TranslationContext } from "./i18n";
|
||||
import { CompanyOnboarding } from "./components/CompanyOnboarding";
|
||||
@@ -7,23 +8,107 @@ import { SidebarView } from "./views/SidebarView";
|
||||
import { SectionContentView } from "./views/SectionContentView";
|
||||
import { ConsoleView } from "./views/ConsoleView";
|
||||
import { CommandPaletteView } from "./views/CommandPaletteView";
|
||||
import { useAppController } from "./hooks/useAppController";
|
||||
import { useAppStore } from "./state/store";
|
||||
import { useUIController } from "./hooks/useUIController";
|
||||
import { useCompanyController } from "./hooks/useCompanyController";
|
||||
import { useCommandPaletteHotkeys } from "./hooks/useCommandPaletteHotkeys";
|
||||
import { AppActionsContext, type AppActions } from "./hooks/useAppActions";
|
||||
import { unwrap } from "./lib/desktop";
|
||||
|
||||
export function App() {
|
||||
const c = useAppController();
|
||||
const ui = useUIController();
|
||||
const company = useCompanyController();
|
||||
|
||||
const error = useAppStore((s) => s.error);
|
||||
const setError = useAppStore((s) => s.setError);
|
||||
const setSelectedRunId = useAppStore((s) => s.setSelectedRunId);
|
||||
const setConsoleOpen = useAppStore((s) => s.setConsoleOpen);
|
||||
|
||||
const showCustomTitleBar = window.agentCompany.platform !== "darwin";
|
||||
const isMac = !showCustomTitleBar;
|
||||
|
||||
if (!c.snapshot) {
|
||||
const startRun = useCallback(async (taskId: string) => {
|
||||
if (!company.currentCompany) { setError("Select a company before starting a run."); return; }
|
||||
const result = await window.agentCompany.startTaskRun({ companyId: company.currentCompany.id, taskId });
|
||||
if (!result.ok) { setError(result.error.message); return; }
|
||||
setSelectedRunId(result.data);
|
||||
setConsoleOpen(true);
|
||||
await company.refresh();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps -- Zustand setters and company.refresh are stable
|
||||
}, [company.currentCompany, company.refresh]);
|
||||
|
||||
const cancelRun = useCallback(async (runId: string) => {
|
||||
try {
|
||||
const run = company.runs.find((entry) => entry.id === runId);
|
||||
if (!run) throw new Error("Selected run is no longer available.");
|
||||
unwrap(await window.agentCompany.cancelRun({ companyId: run.companyId, runId }));
|
||||
await company.refresh();
|
||||
setError(null);
|
||||
} catch (err) { setError(err instanceof Error ? err.message : String(err)); }
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps -- setError is a stable Zustand setter
|
||||
}, [company.refresh, company.runs]);
|
||||
|
||||
const { filteredNavItems, matchedAgents, matchedTasks, matchedRuns, matchedGoals, matchedProjects, hasEntityResults } =
|
||||
useMemo(() => ui.buildPaletteResults(
|
||||
ui.navItems,
|
||||
company.companyAgents,
|
||||
company.tasks,
|
||||
company.runs,
|
||||
company.companyGoals,
|
||||
company.companyProjects,
|
||||
), [
|
||||
ui.navItems,
|
||||
ui.paletteQuery,
|
||||
company.companyAgents,
|
||||
company.tasks,
|
||||
company.runs,
|
||||
company.companyGoals,
|
||||
company.companyProjects,
|
||||
|
||||
]);
|
||||
|
||||
const paletteActions = useMemo(
|
||||
() => ui.buildPaletteActions(filteredNavItems, matchedAgents, matchedTasks, matchedRuns, matchedGoals, matchedProjects),
|
||||
[ui, filteredNavItems, matchedAgents, matchedTasks, matchedRuns, matchedGoals, matchedProjects],
|
||||
);
|
||||
|
||||
const paletteActionIndex = useMemo(
|
||||
() => new Map(paletteActions.map((action, index) => [action.key, index])),
|
||||
[paletteActions],
|
||||
);
|
||||
|
||||
const navigateTo = useCallback((targetSection: Parameters<typeof ui.navigateTo>[0], entityId?: string) => {
|
||||
ui.navigateTo(targetSection, entityId, company.runs, company.tasks);
|
||||
}, [ui, company.runs, company.tasks]);
|
||||
|
||||
useCommandPaletteHotkeys({
|
||||
commandPaletteOpen: ui.commandPaletteOpen,
|
||||
setCommandPaletteOpen: ui.setCommandPaletteOpen,
|
||||
setCommandPaletteSearch: ui.setCommandPaletteSearch,
|
||||
setSelectedPaletteIndex: ui.setSelectedPaletteIndex,
|
||||
paletteActions,
|
||||
selectedPaletteIndex: ui.selectedPaletteIndex,
|
||||
navigateTo,
|
||||
commandPaletteSearch: ui.commandPaletteSearch,
|
||||
});
|
||||
|
||||
const appActions = useMemo<AppActions>(() => ({
|
||||
refresh: company.refresh,
|
||||
setError,
|
||||
openPathWithFeedback: company.openPathWithFeedback,
|
||||
startRun,
|
||||
cancelRun,
|
||||
}), [company.refresh, company.openPathWithFeedback, startRun, cancelRun, setError]);
|
||||
|
||||
if (!company.snapshot) {
|
||||
return (
|
||||
<LocaleContext.Provider value={c.locale}>
|
||||
<TranslationContext.Provider value={c.t}>
|
||||
<LocaleContext.Provider value={ui.locale}>
|
||||
<TranslationContext.Provider value={ui.t}>
|
||||
<div className="flex h-[100dvh] flex-col overflow-hidden">
|
||||
{showCustomTitleBar && <TitleBar />}
|
||||
<div className="flex min-h-0 flex-1 flex-col items-center justify-center gap-3">
|
||||
<CircleNotch size={24} className="animate-spin text-primary" />
|
||||
<span className="text-[13px] text-muted-foreground">{c.t("app.loading")}</span>
|
||||
<span className="text-[13px] text-muted-foreground">{ui.t("app.loading")}</span>
|
||||
</div>
|
||||
</div>
|
||||
</TranslationContext.Provider>
|
||||
@@ -31,25 +116,25 @@ export function App() {
|
||||
);
|
||||
}
|
||||
|
||||
if (c.snapshot.companies.length === 0 || c.showOnboarding || c.initialOnboardingLocked) {
|
||||
if (company.snapshot.companies.length === 0 || ui.showOnboarding || ui.initialOnboardingLocked) {
|
||||
return (
|
||||
<LocaleContext.Provider value={c.locale}>
|
||||
<TranslationContext.Provider value={c.t}>
|
||||
<LocaleContext.Provider value={ui.locale}>
|
||||
<TranslationContext.Provider value={ui.t}>
|
||||
<div className="flex h-[100dvh] flex-col overflow-hidden">
|
||||
{showCustomTitleBar && <TitleBar />}
|
||||
<div className="min-h-0 flex-1 overflow-auto">
|
||||
<CompanyOnboarding
|
||||
connectors={c.snapshot.connectors}
|
||||
onDone={c.handleOnboardingDone}
|
||||
canCancel={c.snapshot.companies.length > 0 && !c.initialOnboardingLocked}
|
||||
connectors={company.snapshot.connectors}
|
||||
onDone={company.handleOnboardingDone}
|
||||
canCancel={company.snapshot.companies.length > 0 && !ui.initialOnboardingLocked}
|
||||
onCancel={() => {
|
||||
c.setShowOnboarding(false);
|
||||
c.setInitialOnboardingLocked(false);
|
||||
c.setError(null);
|
||||
ui.setShowOnboarding(false);
|
||||
ui.setInitialOnboardingLocked(false);
|
||||
setError(null);
|
||||
}}
|
||||
currentLocale={c.locale}
|
||||
onLocaleChange={c.handleLocaleChange}
|
||||
isFirstLaunch={c.snapshot.companies.length === 0}
|
||||
currentLocale={ui.locale}
|
||||
onLocaleChange={ui.handleLocaleChange}
|
||||
isFirstLaunch={company.snapshot.companies.length === 0}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -59,154 +144,100 @@ export function App() {
|
||||
}
|
||||
|
||||
return (
|
||||
<LocaleContext.Provider value={c.locale}>
|
||||
<TranslationContext.Provider value={c.t}>
|
||||
<LocaleContext.Provider value={ui.locale}>
|
||||
<TranslationContext.Provider value={ui.t}>
|
||||
<AppActionsContext.Provider value={appActions}>
|
||||
<div className="flex h-[100dvh] flex-col overflow-hidden bg-background">
|
||||
{showCustomTitleBar && <TitleBar />}
|
||||
<div className={`grid min-h-0 flex-1 overflow-hidden transition-[grid-template-columns] duration-200 ${c.sidebarCollapsed ? "grid-cols-[56px_minmax(0,1fr)]" : "grid-cols-[220px_minmax(0,1fr)]"}`} style={{ transitionTimingFunction: "cubic-bezier(0.16, 1, 0.3, 1)" }}>
|
||||
<div className={`grid min-h-0 flex-1 overflow-hidden transition-[grid-template-columns] duration-200 ${ui.sidebarCollapsed ? "grid-cols-[56px_minmax(0,1fr)]" : "grid-cols-[220px_minmax(0,1fr)]"}`} style={{ transitionTimingFunction: "cubic-bezier(0.16, 1, 0.3, 1)" }}>
|
||||
|
||||
<SidebarView
|
||||
isMac={isMac}
|
||||
sidebarCollapsed={c.sidebarCollapsed}
|
||||
setSidebarCollapsed={c.setSidebarCollapsed}
|
||||
section={c.section}
|
||||
setSection={c.setSection}
|
||||
sidebarNav={c.sidebarNav}
|
||||
navBadges={c.navBadges}
|
||||
companySwitcherOpen={c.companySwitcherOpen}
|
||||
setCompanySwitcherOpen={c.setCompanySwitcherOpen}
|
||||
confirmDeleteCompanyId={c.confirmDeleteCompanyId}
|
||||
setConfirmDeleteCompanyId={c.setConfirmDeleteCompanyId}
|
||||
currentCompany={c.currentCompany}
|
||||
snapshot={c.snapshot}
|
||||
setCompany={c.setCompany}
|
||||
deleteCompany={c.deleteCompany}
|
||||
setCommandPaletteOpen={c.setCommandPaletteOpen}
|
||||
t={c.t}
|
||||
sidebarCollapsed={ui.sidebarCollapsed}
|
||||
setSidebarCollapsed={ui.setSidebarCollapsed}
|
||||
section={ui.section}
|
||||
setSection={ui.setSection}
|
||||
sidebarNav={ui.sidebarNav}
|
||||
navBadges={company.navBadges}
|
||||
companySwitcherOpen={ui.companySwitcherOpen}
|
||||
setCompanySwitcherOpen={ui.setCompanySwitcherOpen}
|
||||
confirmDeleteCompanyId={ui.confirmDeleteCompanyId}
|
||||
setConfirmDeleteCompanyId={ui.setConfirmDeleteCompanyId}
|
||||
currentCompany={company.currentCompany}
|
||||
snapshot={company.snapshot}
|
||||
setCompany={company.setCompany}
|
||||
deleteCompany={company.deleteCompany}
|
||||
setCommandPaletteOpen={ui.setCommandPaletteOpen}
|
||||
t={ui.t}
|
||||
/>
|
||||
|
||||
<main aria-label="Content" className="flex min-w-0 flex-col overflow-hidden">
|
||||
<header className="sidebar-drag shrink-0 border-b border-border bg-background">
|
||||
<div className="mx-auto flex w-full max-w-[1040px] items-start justify-between gap-4 px-6 pb-5 pt-6 lg:gap-6 lg:px-10 lg:pt-8">
|
||||
<h1 className="text-[28px] font-bold tracking-[-0.02em] text-foreground">
|
||||
{c.navItems.find((item) => item.id === c.section)?.label ?? c.t(`nav.${c.section}`)}
|
||||
{ui.navItems.find((item) => item.id === ui.section)?.label ?? ui.t(`nav.${ui.section}`)}
|
||||
</h1>
|
||||
<div className="flex shrink-0 items-center gap-3 lg:gap-5">
|
||||
<button
|
||||
type="button"
|
||||
aria-label={c.consoleOpen ? "Hide console" : "Show console"}
|
||||
title={c.consoleOpen ? "Hide console" : "Show console"}
|
||||
aria-label={ui.consoleOpen ? "Hide console" : "Show console"}
|
||||
title={ui.consoleOpen ? "Hide console" : "Show console"}
|
||||
className="focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 inline-flex items-center gap-2 py-1 text-[12px] font-semibold text-muted-foreground transition hover:text-foreground"
|
||||
onClick={() => c.setConsoleOpen(!c.consoleOpen)}
|
||||
onClick={() => ui.setConsoleOpen(!ui.consoleOpen)}
|
||||
>
|
||||
<TerminalWindow size={16} />
|
||||
<span className="hidden lg:inline">{c.consoleOpen ? c.t("app.hideConsole") : c.t("app.showConsole")}</span>
|
||||
<span className="hidden lg:inline">{ui.consoleOpen ? ui.t("app.hideConsole") : ui.t("app.showConsole")}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<SectionContentView
|
||||
section={c.section}
|
||||
error={c.error}
|
||||
snapshot={c.snapshot}
|
||||
currentCompany={c.currentCompany}
|
||||
locale={c.locale}
|
||||
tasks={c.tasks}
|
||||
runs={c.runs}
|
||||
approvals={c.approvals}
|
||||
pendingApprovals={c.pendingApprovals}
|
||||
companyAgents={c.companyAgents}
|
||||
companyGoals={c.companyGoals}
|
||||
companyProjects={c.companyProjects}
|
||||
companyWorkspaces={c.companyWorkspaces}
|
||||
companyCosts={c.companyCosts}
|
||||
filteredActivity={c.filteredActivity}
|
||||
companySecrets={c.companySecrets}
|
||||
companyDocuments={c.companyDocuments}
|
||||
companyKnowledge={c.companyKnowledge}
|
||||
companyMeetings={c.companyMeetings}
|
||||
companySprints={c.companySprints}
|
||||
socialAccounts={c.socialAccounts}
|
||||
browserActions={c.browserActions}
|
||||
inboxItems={c.inboxItems}
|
||||
companyMetrics={c.companyMetrics}
|
||||
standupReport={c.standupReport}
|
||||
selectedTask={c.selectedTask}
|
||||
selectedTaskId={c.selectedTaskId}
|
||||
setSelectedTaskId={c.setSelectedTaskId}
|
||||
selectedConnector={c.selectedConnector}
|
||||
setSelectedConnectorId={c.setSelectedConnectorId}
|
||||
selectedApproval={c.selectedApproval}
|
||||
selectedApprovalId={c.selectedApprovalId}
|
||||
setSelectedApprovalId={c.setSelectedApprovalId}
|
||||
selectedAgent={c.selectedAgent}
|
||||
selectedAgentId={c.selectedAgentId}
|
||||
setSelectedAgentId={c.setSelectedAgentId}
|
||||
selectedGoal={c.selectedGoal}
|
||||
selectedGoalId={c.selectedGoalId}
|
||||
setSelectedGoalId={c.setSelectedGoalId}
|
||||
selectedProject={c.selectedProject}
|
||||
selectedProjectId={c.selectedProjectId}
|
||||
setSelectedProjectId={c.setSelectedProjectId}
|
||||
selectedWorkspace={c.selectedWorkspace}
|
||||
setSelectedWorkspaceId={c.setSelectedWorkspaceId}
|
||||
selectedCommunicationEntityId={c.selectedCommunicationEntityId}
|
||||
setSelectedCommunicationEntityId={c.setSelectedCommunicationEntityId}
|
||||
taskViewMode={c.taskViewMode}
|
||||
setTaskViewMode={c.setTaskViewMode}
|
||||
selectedRunId={c.selectedRunId}
|
||||
navigateTo={c.navigateTo}
|
||||
setSection={c.setSection}
|
||||
setConsoleOpen={c.setConsoleOpen}
|
||||
setSelectedRunId={c.setSelectedRunId}
|
||||
refresh={c.refresh}
|
||||
startRun={c.startRun}
|
||||
cancelRun={c.cancelRun}
|
||||
openPathWithFeedback={c.openPathWithFeedback}
|
||||
setError={c.setError}
|
||||
pageTransition={c.pageTransition}
|
||||
section={ui.section}
|
||||
error={error}
|
||||
pageTransition={ui.pageTransition}
|
||||
/>
|
||||
|
||||
<ConsoleView
|
||||
consoleOpen={c.consoleOpen}
|
||||
setConsoleOpen={c.setConsoleOpen}
|
||||
selectedRun={c.selectedRun}
|
||||
setSelectedRunId={c.setSelectedRunId}
|
||||
runs={c.runs}
|
||||
logBufferRef={c.logBufferRef}
|
||||
openPathWithFeedback={c.openPathWithFeedback}
|
||||
panelTransition={c.panelTransition}
|
||||
t={c.t}
|
||||
consoleOpen={ui.consoleOpen}
|
||||
setConsoleOpen={ui.setConsoleOpen}
|
||||
selectedRun={company.selectedRun}
|
||||
setSelectedRunId={setSelectedRunId}
|
||||
runs={company.runs}
|
||||
logBufferRef={company.logBufferRef}
|
||||
openPathWithFeedback={company.openPathWithFeedback}
|
||||
panelTransition={ui.panelTransition}
|
||||
t={ui.t}
|
||||
/>
|
||||
</main>
|
||||
|
||||
<CommandPaletteView
|
||||
commandPaletteOpen={c.commandPaletteOpen}
|
||||
setCommandPaletteOpen={c.setCommandPaletteOpen}
|
||||
commandPaletteSearch={c.commandPaletteSearch}
|
||||
setCommandPaletteSearch={c.setCommandPaletteSearch}
|
||||
selectedPaletteIndex={c.selectedPaletteIndex}
|
||||
setSelectedPaletteIndex={c.setSelectedPaletteIndex}
|
||||
paletteTrapRef={c.paletteTrapRef}
|
||||
filteredNavItems={c.filteredNavItems}
|
||||
matchedAgents={c.matchedAgents}
|
||||
matchedTasks={c.matchedTasks}
|
||||
matchedRuns={c.matchedRuns}
|
||||
matchedGoals={c.matchedGoals}
|
||||
matchedProjects={c.matchedProjects}
|
||||
hasEntityResults={c.hasEntityResults}
|
||||
paletteActions={c.paletteActions}
|
||||
paletteActionIndex={c.paletteActionIndex}
|
||||
runs={c.runs}
|
||||
navigateTo={c.navigateTo}
|
||||
panelTransition={c.panelTransition}
|
||||
t={c.t}
|
||||
commandPaletteOpen={ui.commandPaletteOpen}
|
||||
setCommandPaletteOpen={ui.setCommandPaletteOpen}
|
||||
commandPaletteSearch={ui.commandPaletteSearch}
|
||||
setCommandPaletteSearch={ui.setCommandPaletteSearch}
|
||||
selectedPaletteIndex={ui.selectedPaletteIndex}
|
||||
setSelectedPaletteIndex={ui.setSelectedPaletteIndex}
|
||||
paletteTrapRef={ui.paletteTrapRef}
|
||||
filteredNavItems={filteredNavItems}
|
||||
matchedAgents={matchedAgents}
|
||||
matchedTasks={matchedTasks}
|
||||
matchedRuns={matchedRuns}
|
||||
matchedGoals={matchedGoals}
|
||||
matchedProjects={matchedProjects}
|
||||
hasEntityResults={hasEntityResults}
|
||||
paletteActions={paletteActions}
|
||||
paletteActionIndex={paletteActionIndex}
|
||||
runs={company.runs}
|
||||
navigateTo={navigateTo}
|
||||
panelTransition={ui.panelTransition}
|
||||
t={ui.t}
|
||||
/>
|
||||
|
||||
<ToastContainer />
|
||||
</div>
|
||||
</div>
|
||||
</AppActionsContext.Provider>
|
||||
</TranslationContext.Provider>
|
||||
</LocaleContext.Provider>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import { createContext, useContext } from "react";
|
||||
|
||||
export interface AppActions {
|
||||
refresh: () => Promise<void>;
|
||||
setError: (error: string | null) => void;
|
||||
openPathWithFeedback: (path: string) => Promise<void>;
|
||||
startRun: (taskId: string) => Promise<void>;
|
||||
cancelRun: (runId: string) => Promise<void>;
|
||||
}
|
||||
|
||||
export const AppActionsContext = createContext<AppActions | null>(null);
|
||||
|
||||
export function useAppActions(): AppActions {
|
||||
const value = useContext(AppActionsContext);
|
||||
if (!value) throw new Error("useAppActions must be used inside <AppActionsContext.Provider>");
|
||||
return value;
|
||||
}
|
||||
@@ -1,209 +0,0 @@
|
||||
import { useCallback, useMemo } from "react";
|
||||
import { useAppStore } from "../state/store";
|
||||
import { useUIController } from "./useUIController";
|
||||
import { useCompanyController } from "./useCompanyController";
|
||||
import { useExecutionController } from "./useExecutionController";
|
||||
import { useOrganizationController } from "./useOrganizationController";
|
||||
import { useCommunicationController } from "./useCommunicationController";
|
||||
import { useCommandPaletteHotkeys } from "./useCommandPaletteHotkeys";
|
||||
import { unwrap } from "../lib/desktop";
|
||||
|
||||
// Re-export types consumed by other modules
|
||||
export type { NavItem, PaletteAction } from "./useUIController";
|
||||
|
||||
export function useAppController() {
|
||||
const ui = useUIController();
|
||||
const company = useCompanyController();
|
||||
const execution = useExecutionController();
|
||||
const organization = useOrganizationController();
|
||||
const communication = useCommunicationController();
|
||||
|
||||
const error = useAppStore((s) => s.error);
|
||||
const setError = useAppStore((s) => s.setError);
|
||||
const inboxItems = useAppStore((s) => s.inboxItems);
|
||||
const companyMetrics = useAppStore((s) => s.companyMetrics);
|
||||
const standupReport = useAppStore((s) => s.standupReport);
|
||||
const socialAccounts = useAppStore((s) => s.socialAccounts);
|
||||
const browserActions = useAppStore((s) => s.browserActions);
|
||||
|
||||
// --- Cross-domain actions composed here ---
|
||||
|
||||
const startRun = useCallback(async (taskId: string) => {
|
||||
if (!company.currentCompany) { setError("Select a company before starting a run."); return; }
|
||||
const result = await window.agentCompany.startTaskRun({ companyId: company.currentCompany.id, taskId });
|
||||
if (!result.ok) { setError(result.error.message); return; }
|
||||
execution.setSelectedRunId(result.data);
|
||||
ui.setConsoleOpen(true);
|
||||
await company.refresh();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps -- Zustand setters and company.refresh are stable
|
||||
}, [company.currentCompany, company.refresh]);
|
||||
|
||||
const cancelRun = useCallback(async (runId: string) => {
|
||||
try {
|
||||
const run = company.runs.find((entry) => entry.id === runId);
|
||||
if (!run) throw new Error("Selected run is no longer available.");
|
||||
unwrap(await window.agentCompany.cancelRun({ companyId: run.companyId, runId }));
|
||||
await company.refresh();
|
||||
setError(null);
|
||||
} catch (err) { setError(err instanceof Error ? err.message : String(err)); }
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps -- setError is a stable Zustand setter
|
||||
}, [company.refresh, company.runs]);
|
||||
|
||||
// --- Command palette results (needs entity lists from company domain) ---
|
||||
|
||||
const { filteredNavItems, matchedAgents, matchedTasks, matchedRuns, matchedGoals, matchedProjects, hasEntityResults } =
|
||||
useMemo(() => ui.buildPaletteResults(
|
||||
ui.navItems,
|
||||
company.companyAgents,
|
||||
company.tasks,
|
||||
company.runs,
|
||||
company.companyGoals,
|
||||
company.companyProjects,
|
||||
), [
|
||||
ui.navItems,
|
||||
ui.paletteQuery,
|
||||
company.companyAgents,
|
||||
company.tasks,
|
||||
company.runs,
|
||||
company.companyGoals,
|
||||
company.companyProjects,
|
||||
]);
|
||||
|
||||
const paletteActions = useMemo(
|
||||
() => ui.buildPaletteActions(filteredNavItems, matchedAgents, matchedTasks, matchedRuns, matchedGoals, matchedProjects),
|
||||
[filteredNavItems, matchedAgents, matchedTasks, matchedRuns, matchedGoals, matchedProjects],
|
||||
);
|
||||
|
||||
const paletteActionIndex = useMemo(
|
||||
() => new Map(paletteActions.map((action, index) => [action.key, index])),
|
||||
[paletteActions],
|
||||
);
|
||||
|
||||
// navigateTo needs entity lists for task/run cross-lookup
|
||||
const navigateTo = useCallback((targetSection: Parameters<typeof ui.navigateTo>[0], entityId?: string) => {
|
||||
ui.navigateTo(targetSection, entityId, company.runs, company.tasks);
|
||||
}, [ui.navigateTo, company.runs, company.tasks]);
|
||||
|
||||
// Wire command palette hotkeys
|
||||
useCommandPaletteHotkeys({
|
||||
commandPaletteOpen: ui.commandPaletteOpen,
|
||||
setCommandPaletteOpen: ui.setCommandPaletteOpen,
|
||||
setCommandPaletteSearch: ui.setCommandPaletteSearch,
|
||||
setSelectedPaletteIndex: ui.setSelectedPaletteIndex,
|
||||
paletteActions,
|
||||
selectedPaletteIndex: ui.selectedPaletteIndex,
|
||||
navigateTo,
|
||||
commandPaletteSearch: ui.commandPaletteSearch,
|
||||
});
|
||||
|
||||
return {
|
||||
// UI / navigation
|
||||
pageTransition: ui.pageTransition,
|
||||
panelTransition: ui.panelTransition,
|
||||
locale: ui.locale,
|
||||
t: ui.t,
|
||||
handleLocaleChange: ui.handleLocaleChange,
|
||||
sidebarNav: ui.sidebarNav,
|
||||
navItems: ui.navItems,
|
||||
section: ui.section,
|
||||
setSection: ui.setSection,
|
||||
consoleOpen: ui.consoleOpen,
|
||||
setConsoleOpen: ui.setConsoleOpen,
|
||||
sidebarCollapsed: ui.sidebarCollapsed,
|
||||
setSidebarCollapsed: ui.setSidebarCollapsed,
|
||||
companySwitcherOpen: ui.companySwitcherOpen,
|
||||
setCompanySwitcherOpen: ui.setCompanySwitcherOpen,
|
||||
confirmDeleteCompanyId: ui.confirmDeleteCompanyId,
|
||||
setConfirmDeleteCompanyId: ui.setConfirmDeleteCompanyId,
|
||||
taskViewMode: ui.taskViewMode,
|
||||
setTaskViewMode: ui.setTaskViewMode,
|
||||
showOnboarding: ui.showOnboarding,
|
||||
setShowOnboarding: ui.setShowOnboarding,
|
||||
initialOnboardingLocked: ui.initialOnboardingLocked,
|
||||
setInitialOnboardingLocked: ui.setInitialOnboardingLocked,
|
||||
commandPaletteOpen: ui.commandPaletteOpen,
|
||||
setCommandPaletteOpen: ui.setCommandPaletteOpen,
|
||||
commandPaletteSearch: ui.commandPaletteSearch,
|
||||
setCommandPaletteSearch: ui.setCommandPaletteSearch,
|
||||
selectedPaletteIndex: ui.selectedPaletteIndex,
|
||||
setSelectedPaletteIndex: ui.setSelectedPaletteIndex,
|
||||
paletteTrapRef: ui.paletteTrapRef,
|
||||
filteredNavItems,
|
||||
matchedAgents,
|
||||
matchedTasks,
|
||||
matchedRuns,
|
||||
matchedGoals,
|
||||
matchedProjects,
|
||||
hasEntityResults,
|
||||
paletteActions,
|
||||
paletteActionIndex,
|
||||
navigateTo,
|
||||
|
||||
// Company / core
|
||||
snapshot: company.snapshot,
|
||||
currentCompany: company.currentCompany,
|
||||
selectedConnector: company.selectedConnector,
|
||||
logBufferRef: company.logBufferRef,
|
||||
refresh: company.refresh,
|
||||
setCompany: company.setCompany,
|
||||
deleteCompany: company.deleteCompany,
|
||||
handleOnboardingDone: company.handleOnboardingDone,
|
||||
openPathWithFeedback: company.openPathWithFeedback,
|
||||
tasks: company.tasks,
|
||||
runs: company.runs,
|
||||
approvals: company.approvals,
|
||||
pendingApprovals: company.pendingApprovals,
|
||||
companyAgents: company.companyAgents,
|
||||
companyGoals: company.companyGoals,
|
||||
companyProjects: company.companyProjects,
|
||||
companyWorkspaces: company.companyWorkspaces,
|
||||
companyCosts: company.companyCosts,
|
||||
filteredActivity: company.filteredActivity,
|
||||
companySecrets: company.companySecrets,
|
||||
companyDocuments: company.companyDocuments,
|
||||
companyKnowledge: company.companyKnowledge,
|
||||
companyMeetings: company.companyMeetings,
|
||||
companySprints: company.companySprints,
|
||||
selectedTask: company.selectedTask,
|
||||
selectedApproval: company.selectedApproval,
|
||||
selectedRun: company.selectedRun,
|
||||
selectedAgent: company.selectedAgent,
|
||||
selectedGoal: company.selectedGoal,
|
||||
selectedProject: company.selectedProject,
|
||||
selectedWorkspace: company.selectedWorkspace,
|
||||
navBadges: company.navBadges,
|
||||
|
||||
// Execution
|
||||
selectedTaskId: execution.selectedTaskId,
|
||||
setSelectedTaskId: execution.setSelectedTaskId,
|
||||
setSelectedConnectorId: execution.setSelectedConnectorId,
|
||||
selectedApprovalId: execution.selectedApprovalId,
|
||||
setSelectedApprovalId: execution.setSelectedApprovalId,
|
||||
selectedRunId: execution.selectedRunId,
|
||||
setSelectedRunId: execution.setSelectedRunId,
|
||||
startRun,
|
||||
cancelRun,
|
||||
|
||||
// Organization
|
||||
selectedAgentId: organization.selectedAgentId,
|
||||
setSelectedAgentId: organization.setSelectedAgentId,
|
||||
selectedGoalId: organization.selectedGoalId,
|
||||
setSelectedGoalId: organization.setSelectedGoalId,
|
||||
selectedProjectId: organization.selectedProjectId,
|
||||
setSelectedProjectId: organization.setSelectedProjectId,
|
||||
setSelectedWorkspaceId: organization.setSelectedWorkspaceId,
|
||||
|
||||
// Communication
|
||||
selectedCommunicationEntityId: communication.selectedCommunicationEntityId,
|
||||
setSelectedCommunicationEntityId: communication.setSelectedCommunicationEntityId,
|
||||
|
||||
// Shared cross-domain state
|
||||
error,
|
||||
setError,
|
||||
inboxItems,
|
||||
companyMetrics,
|
||||
standupReport,
|
||||
socialAccounts,
|
||||
browserActions,
|
||||
};
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useEffect } from "react";
|
||||
import { useAppStore } from "../state/store";
|
||||
import type { PaletteAction } from "./useAppController";
|
||||
import type { PaletteAction } from "./useUIController";
|
||||
|
||||
interface CommandPaletteHotkeysOptions {
|
||||
commandPaletteOpen: boolean;
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
import { useAppStore } from "../state/store";
|
||||
|
||||
/**
|
||||
* Fine-grained subscriptions for communication domain:
|
||||
* messages, channels, threads, search state.
|
||||
*/
|
||||
export function useCommunicationController() {
|
||||
const selectedCommunicationEntityId = useAppStore((s) => s.selectedCommunicationEntityId);
|
||||
const setSelectedCommunicationEntityId = useAppStore((s) => s.setSelectedCommunicationEntityId);
|
||||
const searchQuery = useAppStore((s) => s.searchQuery);
|
||||
const setSearchQuery = useAppStore((s) => s.setSearchQuery);
|
||||
const searchScope = useAppStore((s) => s.searchScope);
|
||||
const setSearchScope = useAppStore((s) => s.setSearchScope);
|
||||
const searchResults = useAppStore((s) => s.searchResults);
|
||||
const setSearchResults = useAppStore((s) => s.setSearchResults);
|
||||
const searchLoading = useAppStore((s) => s.searchLoading);
|
||||
const setSearchLoading = useAppStore((s) => s.setSearchLoading);
|
||||
const clearSearch = useAppStore((s) => s.clearSearch);
|
||||
const activeThreadId = useAppStore((s) => s.activeThreadId);
|
||||
const setActiveThreadId = useAppStore((s) => s.setActiveThreadId);
|
||||
|
||||
return {
|
||||
selectedCommunicationEntityId,
|
||||
setSelectedCommunicationEntityId,
|
||||
searchQuery,
|
||||
setSearchQuery,
|
||||
searchScope,
|
||||
setSearchScope,
|
||||
searchResults,
|
||||
setSearchResults,
|
||||
searchLoading,
|
||||
setSearchLoading,
|
||||
clearSearch,
|
||||
activeThreadId,
|
||||
setActiveThreadId,
|
||||
};
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
import { useAppStore } from "../state/store";
|
||||
|
||||
/**
|
||||
* Fine-grained subscriptions for execution domain:
|
||||
* runs, approvals, costs, budgets, selected run/approval/task/connector.
|
||||
*
|
||||
* Cross-domain actions (startRun, cancelRun) are composed in useAppController
|
||||
* because they need refresh() from useCompanyController.
|
||||
*/
|
||||
export function useExecutionController() {
|
||||
const selectedTaskId = useAppStore((s) => s.selectedTaskId);
|
||||
const setSelectedTaskId = useAppStore((s) => s.setSelectedTaskId);
|
||||
const selectedConnectorId = useAppStore((s) => s.selectedConnectorId);
|
||||
const setSelectedConnectorId = useAppStore((s) => s.setSelectedConnectorId);
|
||||
const selectedApprovalId = useAppStore((s) => s.selectedApprovalId);
|
||||
const setSelectedApprovalId = useAppStore((s) => s.setSelectedApprovalId);
|
||||
const selectedRunId = useAppStore((s) => s.selectedRunId);
|
||||
const setSelectedRunId = useAppStore((s) => s.setSelectedRunId);
|
||||
|
||||
return {
|
||||
selectedTaskId,
|
||||
setSelectedTaskId,
|
||||
selectedConnectorId,
|
||||
setSelectedConnectorId,
|
||||
selectedApprovalId,
|
||||
setSelectedApprovalId,
|
||||
selectedRunId,
|
||||
setSelectedRunId,
|
||||
};
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
import { useAppStore } from "../state/store";
|
||||
|
||||
/**
|
||||
* Fine-grained subscriptions for organization domain:
|
||||
* goals, projects, sprints, departments, workspaces, agents.
|
||||
*/
|
||||
export function useOrganizationController() {
|
||||
const selectedAgentId = useAppStore((s) => s.selectedAgentId);
|
||||
const setSelectedAgentId = useAppStore((s) => s.setSelectedAgentId);
|
||||
const selectedGoalId = useAppStore((s) => s.selectedGoalId);
|
||||
const setSelectedGoalId = useAppStore((s) => s.setSelectedGoalId);
|
||||
const selectedProjectId = useAppStore((s) => s.selectedProjectId);
|
||||
const setSelectedProjectId = useAppStore((s) => s.setSelectedProjectId);
|
||||
const selectedWorkspaceId = useAppStore((s) => s.selectedWorkspaceId);
|
||||
const setSelectedWorkspaceId = useAppStore((s) => s.setSelectedWorkspaceId);
|
||||
|
||||
return {
|
||||
selectedAgentId,
|
||||
setSelectedAgentId,
|
||||
selectedGoalId,
|
||||
setSelectedGoalId,
|
||||
selectedProjectId,
|
||||
setSelectedProjectId,
|
||||
selectedWorkspaceId,
|
||||
setSelectedWorkspaceId,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
import { useMemo } from "react";
|
||||
import type { CompanyRecord } from "@shared/types";
|
||||
import { useAppStore } from "../state/store";
|
||||
import { useCompanyData } from "./useCompanyData";
|
||||
|
||||
/**
|
||||
* Shared data context for section views. Subscribes to the Zustand store
|
||||
* for snapshot + current company, derives per-company collections and
|
||||
* currently-selected entities via useCompanyData.
|
||||
*
|
||||
* Sections call this hook directly instead of receiving 30+ props through
|
||||
* SectionContentView.
|
||||
*/
|
||||
export function useSectionContext() {
|
||||
const snapshot = useAppStore((s) => s.snapshot);
|
||||
|
||||
const currentCompany = useMemo<CompanyRecord | null>(() => {
|
||||
if (!snapshot) return null;
|
||||
return (
|
||||
snapshot.companies.find((company) => company.id === snapshot.currentCompanyId) ??
|
||||
snapshot.companies[0] ??
|
||||
null
|
||||
);
|
||||
}, [snapshot]);
|
||||
|
||||
const companyData = useCompanyData(snapshot, currentCompany);
|
||||
|
||||
const selectedConnectorId = useAppStore((s) => s.selectedConnectorId);
|
||||
const selectedConnector =
|
||||
snapshot?.connectors.find((connector) => connector.id === selectedConnectorId) ??
|
||||
snapshot?.connectors[0] ??
|
||||
null;
|
||||
|
||||
return {
|
||||
snapshot,
|
||||
currentCompany,
|
||||
selectedConnector,
|
||||
...companyData,
|
||||
};
|
||||
}
|
||||
@@ -1,28 +1,6 @@
|
||||
import { AnimatePresence, motion } from "framer-motion";
|
||||
import { WarningCircle } from "@phosphor-icons/react";
|
||||
import type {
|
||||
AgentRecord,
|
||||
ApprovalRecord,
|
||||
BrowserActionRecord,
|
||||
CompanyRecord,
|
||||
CostRecord,
|
||||
DocumentRecord,
|
||||
GoalRecord,
|
||||
InboxItem,
|
||||
CompanyMetrics,
|
||||
KnowledgeEntryRecord,
|
||||
MeetingRecord,
|
||||
ProfileSnapshot,
|
||||
ProjectRecord,
|
||||
RunRecord,
|
||||
SectionId,
|
||||
SocialAccountRecord,
|
||||
SprintRecord,
|
||||
StandupReport,
|
||||
TaskRecord,
|
||||
WorkspaceRecord,
|
||||
} from "@shared/types";
|
||||
import type { AppLocale } from "@shared/locale";
|
||||
import type { SectionId } from "@shared/types";
|
||||
import { ErrorBoundary } from "../components/ui";
|
||||
import { OrganizationSections } from "./sections/OrganizationSections";
|
||||
import { ExecutionSections } from "./sections/ExecutionSections";
|
||||
@@ -31,142 +9,23 @@ import { OperationsSections } from "./sections/OperationsSections";
|
||||
interface SectionContentViewProps {
|
||||
section: SectionId;
|
||||
error: string | null;
|
||||
snapshot: ProfileSnapshot;
|
||||
currentCompany: CompanyRecord | null;
|
||||
locale: AppLocale;
|
||||
// data collections
|
||||
tasks: TaskRecord[];
|
||||
runs: RunRecord[];
|
||||
approvals: ApprovalRecord[];
|
||||
pendingApprovals: ApprovalRecord[];
|
||||
companyAgents: AgentRecord[];
|
||||
companyGoals: GoalRecord[];
|
||||
companyProjects: ProjectRecord[];
|
||||
companyWorkspaces: WorkspaceRecord[];
|
||||
companyCosts: CostRecord[];
|
||||
filteredActivity: ReturnType<ProfileSnapshot["activity"]["filter"]>;
|
||||
companySecrets: ProfileSnapshot["secrets"];
|
||||
companyDocuments: DocumentRecord[];
|
||||
companyKnowledge: KnowledgeEntryRecord[];
|
||||
companyMeetings: MeetingRecord[];
|
||||
companySprints: SprintRecord[];
|
||||
socialAccounts: SocialAccountRecord[];
|
||||
browserActions: BrowserActionRecord[];
|
||||
inboxItems: InboxItem[];
|
||||
companyMetrics: CompanyMetrics | null;
|
||||
standupReport: StandupReport | null;
|
||||
selectedRunId: string | null;
|
||||
// selections
|
||||
selectedTask: TaskRecord | null;
|
||||
selectedTaskId: string | null;
|
||||
setSelectedTaskId: (id: string | null) => void;
|
||||
selectedConnector: ProfileSnapshot["connectors"][number] | null;
|
||||
setSelectedConnectorId: (id: string | null) => void;
|
||||
selectedApproval: ApprovalRecord | null;
|
||||
selectedApprovalId: string | null;
|
||||
setSelectedApprovalId: (id: string | null) => void;
|
||||
selectedAgent: AgentRecord | null;
|
||||
selectedAgentId: string | null;
|
||||
setSelectedAgentId: (id: string | null) => void;
|
||||
selectedGoal: GoalRecord | null;
|
||||
selectedGoalId: string | null;
|
||||
setSelectedGoalId: (id: string | null) => void;
|
||||
selectedProject: ProjectRecord | null;
|
||||
selectedProjectId: string | null;
|
||||
setSelectedProjectId: (id: string | null) => void;
|
||||
selectedWorkspace: WorkspaceRecord | null;
|
||||
setSelectedWorkspaceId: (id: string | null) => void;
|
||||
selectedCommunicationEntityId: string | null;
|
||||
setSelectedCommunicationEntityId: (id: string | null) => void;
|
||||
taskViewMode: "list" | "kanban";
|
||||
setTaskViewMode: (mode: "list" | "kanban") => void;
|
||||
// actions
|
||||
navigateTo: (section: SectionId, entityId?: string) => void;
|
||||
setSection: (section: SectionId) => void;
|
||||
setConsoleOpen: (value: boolean) => void;
|
||||
setSelectedRunId: (id: string | null) => void;
|
||||
refresh: () => Promise<void>;
|
||||
startRun: (taskId: string) => Promise<void>;
|
||||
cancelRun: (runId: string) => Promise<void>;
|
||||
openPathWithFeedback: (path: string) => Promise<void>;
|
||||
setError: (error: string | null) => void;
|
||||
// transitions
|
||||
pageTransition: { duration: number; ease?: readonly [number, number, number, number] } | { duration: number };
|
||||
}
|
||||
|
||||
export function SectionContentView({
|
||||
section,
|
||||
error,
|
||||
snapshot,
|
||||
currentCompany,
|
||||
locale,
|
||||
tasks,
|
||||
runs,
|
||||
approvals,
|
||||
pendingApprovals,
|
||||
companyAgents,
|
||||
companyGoals,
|
||||
companyProjects,
|
||||
companyWorkspaces,
|
||||
companyCosts,
|
||||
filteredActivity,
|
||||
companySecrets,
|
||||
companyDocuments,
|
||||
companyKnowledge,
|
||||
companyMeetings,
|
||||
companySprints,
|
||||
socialAccounts,
|
||||
browserActions,
|
||||
inboxItems,
|
||||
companyMetrics,
|
||||
standupReport,
|
||||
selectedTask,
|
||||
selectedTaskId,
|
||||
setSelectedTaskId,
|
||||
selectedConnector,
|
||||
setSelectedConnectorId,
|
||||
selectedApproval,
|
||||
selectedApprovalId,
|
||||
setSelectedApprovalId,
|
||||
selectedAgent: _selectedAgent,
|
||||
selectedAgentId,
|
||||
setSelectedAgentId,
|
||||
selectedGoal: _selectedGoal,
|
||||
selectedGoalId,
|
||||
setSelectedGoalId,
|
||||
selectedProject: _selectedProject,
|
||||
selectedProjectId,
|
||||
setSelectedProjectId,
|
||||
selectedWorkspace,
|
||||
setSelectedWorkspaceId,
|
||||
selectedCommunicationEntityId,
|
||||
setSelectedCommunicationEntityId,
|
||||
taskViewMode,
|
||||
setTaskViewMode,
|
||||
selectedRunId,
|
||||
navigateTo,
|
||||
setSection,
|
||||
setConsoleOpen,
|
||||
setSelectedRunId,
|
||||
refresh,
|
||||
startRun,
|
||||
cancelRun,
|
||||
openPathWithFeedback,
|
||||
setError,
|
||||
pageTransition,
|
||||
}: SectionContentViewProps) {
|
||||
export function SectionContentView({ section, error, pageTransition }: SectionContentViewProps) {
|
||||
const isCommunication = section === "communication";
|
||||
return (
|
||||
<div className={`flex-1 ${section === "communication" ? "flex flex-col overflow-hidden" : "overflow-auto"}`}>
|
||||
<div className={section === "communication" ? "flex min-h-0 flex-1 flex-col" : "mx-auto flex w-full max-w-[1040px] flex-col px-6 py-6 lg:px-10 lg:py-8"}>
|
||||
<div className={`flex-1 ${isCommunication ? "flex flex-col overflow-hidden" : "overflow-auto"}`}>
|
||||
<div className={isCommunication ? "flex min-h-0 flex-1 flex-col" : "mx-auto flex w-full max-w-[1040px] flex-col px-6 py-6 lg:px-10 lg:py-8"}>
|
||||
|
||||
{error ? (
|
||||
<div className={`flex items-center gap-3 rounded-lg bg-destructive-muted px-4 py-3 text-[13px] text-destructive ${section === "communication" ? "mx-6 mt-4" : "mb-6"}`}>
|
||||
<div className={`flex items-center gap-3 rounded-lg bg-destructive-muted px-4 py-3 text-[13px] text-destructive ${isCommunication ? "mx-6 mt-4" : "mb-6"}`}>
|
||||
<WarningCircle size={18} />
|
||||
<span>{error}</span>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<div className={section === "communication" ? "flex min-h-0 flex-1 flex-col" : "min-h-0 flex-1"}>
|
||||
<div className={isCommunication ? "flex min-h-0 flex-1 flex-col" : "min-h-0 flex-1"}>
|
||||
<ErrorBoundary>
|
||||
<AnimatePresence mode="wait">
|
||||
<motion.div
|
||||
@@ -175,101 +34,12 @@ export function SectionContentView({
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
exit={{ opacity: 0, y: -12 }}
|
||||
transition={pageTransition}
|
||||
className={section === "communication" ? "flex min-h-0 flex-1 flex-col" : "space-y-14"}
|
||||
style={section === "communication" ? { height: "100%" } : undefined}
|
||||
className={isCommunication ? "flex min-h-0 flex-1 flex-col" : "space-y-14"}
|
||||
style={isCommunication ? { height: "100%" } : undefined}
|
||||
>
|
||||
<OrganizationSections
|
||||
section={section}
|
||||
snapshot={snapshot}
|
||||
currentCompany={currentCompany}
|
||||
companyAgents={companyAgents}
|
||||
companyGoals={companyGoals}
|
||||
companyProjects={companyProjects}
|
||||
companyWorkspaces={companyWorkspaces}
|
||||
selectedAgentId={selectedAgentId}
|
||||
setSelectedAgentId={setSelectedAgentId}
|
||||
selectedGoalId={selectedGoalId}
|
||||
setSelectedGoalId={setSelectedGoalId}
|
||||
selectedProjectId={selectedProjectId}
|
||||
setSelectedProjectId={setSelectedProjectId}
|
||||
setSection={setSection}
|
||||
refresh={refresh}
|
||||
/>
|
||||
|
||||
<ExecutionSections
|
||||
section={section}
|
||||
snapshot={snapshot}
|
||||
currentCompany={currentCompany}
|
||||
tasks={tasks}
|
||||
runs={runs}
|
||||
approvals={approvals}
|
||||
pendingApprovals={pendingApprovals}
|
||||
companyAgents={companyAgents}
|
||||
companyGoals={companyGoals}
|
||||
companyProjects={companyProjects}
|
||||
companyWorkspaces={companyWorkspaces}
|
||||
companyCosts={companyCosts}
|
||||
companyDocuments={companyDocuments}
|
||||
filteredActivity={filteredActivity}
|
||||
selectedRunId={selectedRunId}
|
||||
selectedTask={selectedTask}
|
||||
selectedTaskId={selectedTaskId}
|
||||
setSelectedTaskId={setSelectedTaskId}
|
||||
selectedConnector={selectedConnector}
|
||||
setSelectedConnectorId={setSelectedConnectorId}
|
||||
selectedApproval={selectedApproval}
|
||||
selectedApprovalId={selectedApprovalId}
|
||||
setSelectedApprovalId={setSelectedApprovalId}
|
||||
selectedGoalId={selectedGoalId}
|
||||
setSelectedGoalId={setSelectedGoalId}
|
||||
taskViewMode={taskViewMode}
|
||||
setTaskViewMode={setTaskViewMode}
|
||||
setSection={setSection}
|
||||
setConsoleOpen={setConsoleOpen}
|
||||
setSelectedRunId={setSelectedRunId}
|
||||
setSelectedCommunicationEntityId={setSelectedCommunicationEntityId}
|
||||
refresh={refresh}
|
||||
startRun={startRun}
|
||||
cancelRun={cancelRun}
|
||||
openPathWithFeedback={openPathWithFeedback}
|
||||
setError={setError}
|
||||
/>
|
||||
|
||||
<OperationsSections
|
||||
section={section}
|
||||
snapshot={snapshot}
|
||||
currentCompany={currentCompany}
|
||||
locale={locale}
|
||||
tasks={tasks}
|
||||
runs={runs}
|
||||
companyAgents={companyAgents}
|
||||
companyProjects={companyProjects}
|
||||
companyWorkspaces={companyWorkspaces}
|
||||
companyDocuments={companyDocuments}
|
||||
companyKnowledge={companyKnowledge}
|
||||
companyMeetings={companyMeetings}
|
||||
companySprints={companySprints}
|
||||
socialAccounts={socialAccounts}
|
||||
browserActions={browserActions}
|
||||
inboxItems={inboxItems}
|
||||
companyMetrics={companyMetrics}
|
||||
standupReport={standupReport}
|
||||
companySecrets={companySecrets}
|
||||
selectedWorkspace={selectedWorkspace}
|
||||
setSelectedWorkspaceId={setSelectedWorkspaceId}
|
||||
selectedCommunicationEntityId={selectedCommunicationEntityId}
|
||||
setSelectedCommunicationEntityId={setSelectedCommunicationEntityId}
|
||||
setSelectedTaskId={setSelectedTaskId}
|
||||
setSelectedApprovalId={setSelectedApprovalId}
|
||||
setSection={setSection}
|
||||
navigateTo={navigateTo}
|
||||
refresh={refresh}
|
||||
openPathWithFeedback={openPathWithFeedback}
|
||||
setError={setError}
|
||||
filteredActivity={filteredActivity}
|
||||
companyCosts={companyCosts}
|
||||
companyGoals={companyGoals}
|
||||
/>
|
||||
<OrganizationSections section={section} />
|
||||
<ExecutionSections section={section} />
|
||||
<OperationsSections section={section} />
|
||||
</motion.div>
|
||||
</AnimatePresence>
|
||||
</ErrorBoundary>
|
||||
|
||||
@@ -1,19 +1,6 @@
|
||||
import { useState } from "react";
|
||||
import { NEW_ENTITY_ID } from "../../lib/constants";
|
||||
import type {
|
||||
AgentRecord,
|
||||
ApprovalRecord,
|
||||
CompanyRecord,
|
||||
CostRecord,
|
||||
DocumentRecord,
|
||||
GoalRecord,
|
||||
ProjectRecord,
|
||||
RunRecord,
|
||||
SectionId,
|
||||
TaskRecord,
|
||||
WorkspaceRecord,
|
||||
ProfileSnapshot,
|
||||
} from "@shared/types";
|
||||
import type { SectionId } from "@shared/types";
|
||||
import {
|
||||
Kanban,
|
||||
ListChecks,
|
||||
@@ -43,88 +30,52 @@ import {
|
||||
} from "../../components/organization";
|
||||
import { ActionButton, ListFrame, ListRow, Modal, Panel, StatusPill } from "../../components/ui";
|
||||
import { unwrap } from "../../lib/desktop";
|
||||
import { useAppStore } from "../../state/store";
|
||||
import { useSectionContext } from "../../hooks/useSectionContext";
|
||||
import { useAppActions } from "../../hooks/useAppActions";
|
||||
|
||||
interface ExecutionSectionsProps {
|
||||
section: SectionId;
|
||||
snapshot: ProfileSnapshot;
|
||||
currentCompany: CompanyRecord | null;
|
||||
tasks: TaskRecord[];
|
||||
runs: RunRecord[];
|
||||
approvals: ApprovalRecord[];
|
||||
pendingApprovals: ApprovalRecord[];
|
||||
companyAgents: AgentRecord[];
|
||||
companyGoals: GoalRecord[];
|
||||
companyProjects: ProjectRecord[];
|
||||
companyWorkspaces: WorkspaceRecord[];
|
||||
companyCosts: CostRecord[];
|
||||
companyDocuments: DocumentRecord[];
|
||||
filteredActivity: ReturnType<ProfileSnapshot["activity"]["filter"]>;
|
||||
selectedRunId: string | null;
|
||||
selectedTask: TaskRecord | null;
|
||||
selectedTaskId: string | null;
|
||||
setSelectedTaskId: (id: string | null) => void;
|
||||
selectedConnector: ProfileSnapshot["connectors"][number] | null;
|
||||
setSelectedConnectorId: (id: string | null) => void;
|
||||
selectedApproval: ApprovalRecord | null;
|
||||
selectedApprovalId: string | null;
|
||||
setSelectedApprovalId: (id: string | null) => void;
|
||||
selectedGoalId: string | null;
|
||||
setSelectedGoalId: (id: string | null) => void;
|
||||
taskViewMode: "list" | "kanban";
|
||||
setTaskViewMode: (mode: "list" | "kanban") => void;
|
||||
setSection: (section: SectionId) => void;
|
||||
setConsoleOpen: (value: boolean) => void;
|
||||
setSelectedRunId: (id: string | null) => void;
|
||||
setSelectedCommunicationEntityId: (id: string | null) => void;
|
||||
refresh: () => Promise<void>;
|
||||
startRun: (taskId: string) => Promise<void>;
|
||||
cancelRun: (runId: string) => Promise<void>;
|
||||
openPathWithFeedback: (path: string) => Promise<void>;
|
||||
setError: (error: string | null) => void;
|
||||
}
|
||||
|
||||
export function ExecutionSections({
|
||||
section,
|
||||
snapshot,
|
||||
currentCompany,
|
||||
tasks,
|
||||
runs,
|
||||
approvals: _approvals,
|
||||
pendingApprovals,
|
||||
companyAgents,
|
||||
companyGoals,
|
||||
companyProjects,
|
||||
companyWorkspaces,
|
||||
companyCosts,
|
||||
companyDocuments,
|
||||
filteredActivity,
|
||||
selectedRunId,
|
||||
selectedTask,
|
||||
selectedTaskId,
|
||||
setSelectedTaskId,
|
||||
selectedConnector,
|
||||
setSelectedConnectorId,
|
||||
selectedApproval,
|
||||
selectedApprovalId: _selectedApprovalId,
|
||||
setSelectedApprovalId,
|
||||
selectedGoalId,
|
||||
setSelectedGoalId,
|
||||
taskViewMode,
|
||||
setTaskViewMode,
|
||||
setSection,
|
||||
setConsoleOpen,
|
||||
setSelectedRunId,
|
||||
setSelectedCommunicationEntityId,
|
||||
refresh,
|
||||
startRun,
|
||||
cancelRun,
|
||||
openPathWithFeedback,
|
||||
setError: _setError,
|
||||
}: ExecutionSectionsProps) {
|
||||
const [costsTab, setCostsTab] = useState<"costs" | "policies" | "incidents">("costs");
|
||||
const selectedGoal = companyGoals.find((g) => g.id === selectedGoalId) ?? null;
|
||||
export function ExecutionSections({ section }: ExecutionSectionsProps) {
|
||||
const {
|
||||
snapshot,
|
||||
currentCompany,
|
||||
tasks,
|
||||
runs,
|
||||
pendingApprovals,
|
||||
companyAgents,
|
||||
companyGoals,
|
||||
companyProjects,
|
||||
companyWorkspaces,
|
||||
companyCosts,
|
||||
companyDocuments,
|
||||
filteredActivity,
|
||||
selectedTask,
|
||||
selectedConnector,
|
||||
selectedApproval,
|
||||
selectedGoal,
|
||||
} = useSectionContext();
|
||||
const { refresh, startRun, cancelRun, openPathWithFeedback } = useAppActions();
|
||||
|
||||
if (!currentCompany) return null;
|
||||
const selectedRunId = useAppStore((s) => s.selectedRunId);
|
||||
const setSelectedRunId = useAppStore((s) => s.setSelectedRunId);
|
||||
const selectedTaskId = useAppStore((s) => s.selectedTaskId);
|
||||
const setSelectedTaskId = useAppStore((s) => s.setSelectedTaskId);
|
||||
const setSelectedConnectorId = useAppStore((s) => s.setSelectedConnectorId);
|
||||
const setSelectedApprovalId = useAppStore((s) => s.setSelectedApprovalId);
|
||||
const selectedGoalId = useAppStore((s) => s.selectedGoalId);
|
||||
const setSelectedGoalId = useAppStore((s) => s.setSelectedGoalId);
|
||||
const taskViewMode = useAppStore((s) => s.taskViewMode);
|
||||
const setTaskViewMode = useAppStore((s) => s.setTaskViewMode);
|
||||
const setSection = useAppStore((s) => s.setSection);
|
||||
const setConsoleOpen = useAppStore((s) => s.setConsoleOpen);
|
||||
const setSelectedCommunicationEntityId = useAppStore((s) => s.setSelectedCommunicationEntityId);
|
||||
|
||||
const [costsTab, setCostsTab] = useState<"costs" | "policies" | "incidents">("costs");
|
||||
|
||||
if (!snapshot || !currentCompany) return null;
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -1,23 +1,4 @@
|
||||
import type {
|
||||
AgentRecord,
|
||||
BrowserActionRecord,
|
||||
CompanyRecord,
|
||||
DocumentRecord,
|
||||
InboxItem,
|
||||
CompanyMetrics,
|
||||
KnowledgeEntryRecord,
|
||||
MeetingRecord,
|
||||
ProjectRecord,
|
||||
RunRecord,
|
||||
SectionId,
|
||||
SocialAccountRecord,
|
||||
SprintRecord,
|
||||
StandupReport,
|
||||
TaskRecord,
|
||||
WorkspaceRecord,
|
||||
ProfileSnapshot,
|
||||
} from "@shared/types";
|
||||
import type { AppLocale } from "@shared/locale";
|
||||
import type { SectionId } from "@shared/types";
|
||||
import { Inbox, StandupView, PerformanceView } from "../../components/inbox";
|
||||
import { Dashboard } from "../../components/dashboard";
|
||||
import { EntityWorkspace } from "../../components/EntityWorkspace";
|
||||
@@ -35,78 +16,51 @@ import { SkillsSection } from "../../components/skills";
|
||||
import { DocumentCenter, KnowledgeBaseManager, MeetingsManager, SprintManager } from "../../components/company-operations";
|
||||
import { Panel } from "../../components/ui";
|
||||
import { unwrap } from "../../lib/desktop";
|
||||
import { useAppStore } from "../../state/store";
|
||||
import { useSectionContext } from "../../hooks/useSectionContext";
|
||||
import { useAppActions } from "../../hooks/useAppActions";
|
||||
|
||||
interface OperationsSectionsProps {
|
||||
section: SectionId;
|
||||
snapshot: ProfileSnapshot;
|
||||
currentCompany: CompanyRecord | null;
|
||||
locale: AppLocale;
|
||||
tasks: TaskRecord[];
|
||||
runs: RunRecord[];
|
||||
companyAgents: AgentRecord[];
|
||||
companyProjects: ProjectRecord[];
|
||||
companyWorkspaces: WorkspaceRecord[];
|
||||
companyDocuments: DocumentRecord[];
|
||||
companyKnowledge: KnowledgeEntryRecord[];
|
||||
companyMeetings: MeetingRecord[];
|
||||
companySprints: SprintRecord[];
|
||||
socialAccounts: SocialAccountRecord[];
|
||||
browserActions: BrowserActionRecord[];
|
||||
inboxItems: InboxItem[];
|
||||
companyMetrics: CompanyMetrics | null;
|
||||
standupReport: StandupReport | null;
|
||||
companySecrets: ProfileSnapshot["secrets"];
|
||||
selectedWorkspace: WorkspaceRecord | null;
|
||||
setSelectedWorkspaceId: (id: string | null) => void;
|
||||
selectedCommunicationEntityId: string | null;
|
||||
setSelectedCommunicationEntityId: (id: string | null) => void;
|
||||
setSelectedTaskId: (id: string | null) => void;
|
||||
setSelectedApprovalId: (id: string | null) => void;
|
||||
setSection: (section: SectionId) => void;
|
||||
navigateTo: (section: SectionId, entityId?: string) => void;
|
||||
refresh: () => Promise<void>;
|
||||
openPathWithFeedback: (path: string) => Promise<void>;
|
||||
setError: (error: string | null) => void;
|
||||
filteredActivity: ReturnType<ProfileSnapshot["activity"]["filter"]>;
|
||||
companyCosts: ReturnType<ProfileSnapshot["costs"]["filter"]>;
|
||||
companyGoals: ReturnType<NonNullable<ProfileSnapshot["goals"]>["filter"]>;
|
||||
}
|
||||
|
||||
export function OperationsSections({
|
||||
section,
|
||||
snapshot,
|
||||
currentCompany,
|
||||
locale,
|
||||
tasks,
|
||||
runs,
|
||||
companyAgents,
|
||||
companyProjects,
|
||||
companyWorkspaces,
|
||||
companyDocuments,
|
||||
companyKnowledge,
|
||||
companyMeetings,
|
||||
companySprints,
|
||||
socialAccounts,
|
||||
browserActions,
|
||||
inboxItems,
|
||||
companyMetrics,
|
||||
standupReport,
|
||||
companySecrets,
|
||||
selectedWorkspace,
|
||||
setSelectedWorkspaceId,
|
||||
selectedCommunicationEntityId,
|
||||
setSelectedCommunicationEntityId,
|
||||
setSelectedTaskId,
|
||||
setSelectedApprovalId,
|
||||
setSection,
|
||||
navigateTo,
|
||||
refresh,
|
||||
openPathWithFeedback,
|
||||
setError,
|
||||
filteredActivity,
|
||||
companyCosts,
|
||||
companyGoals,
|
||||
}: OperationsSectionsProps) {
|
||||
export function OperationsSections({ section }: OperationsSectionsProps) {
|
||||
const {
|
||||
snapshot,
|
||||
currentCompany,
|
||||
tasks,
|
||||
runs,
|
||||
companyAgents,
|
||||
companyProjects,
|
||||
companyWorkspaces,
|
||||
companyDocuments,
|
||||
companyKnowledge,
|
||||
companyMeetings,
|
||||
companySprints,
|
||||
companyCosts,
|
||||
companyGoals,
|
||||
companySecrets,
|
||||
filteredActivity,
|
||||
selectedWorkspace,
|
||||
} = useSectionContext();
|
||||
const { refresh, openPathWithFeedback, setError } = useAppActions();
|
||||
|
||||
const locale = useAppStore((s) => s.locale);
|
||||
const inboxItems = useAppStore((s) => s.inboxItems);
|
||||
const companyMetrics = useAppStore((s) => s.companyMetrics);
|
||||
const standupReport = useAppStore((s) => s.standupReport);
|
||||
const socialAccounts = useAppStore((s) => s.socialAccounts);
|
||||
const browserActions = useAppStore((s) => s.browserActions);
|
||||
const selectedCommunicationEntityId = useAppStore((s) => s.selectedCommunicationEntityId);
|
||||
const setSelectedCommunicationEntityId = useAppStore((s) => s.setSelectedCommunicationEntityId);
|
||||
const setSelectedWorkspaceId = useAppStore((s) => s.setSelectedWorkspaceId);
|
||||
const setSelectedTaskId = useAppStore((s) => s.setSelectedTaskId);
|
||||
const setSelectedApprovalId = useAppStore((s) => s.setSelectedApprovalId);
|
||||
const setSection = useAppStore((s) => s.setSection);
|
||||
const navigateTo = useAppStore((s) => s.navigateTo);
|
||||
|
||||
if (!snapshot) return null;
|
||||
|
||||
return (
|
||||
<>
|
||||
{section === "overview" ? (
|
||||
|
||||
@@ -1,12 +1,4 @@
|
||||
import type {
|
||||
AgentRecord,
|
||||
CompanyRecord,
|
||||
GoalRecord,
|
||||
ProjectRecord,
|
||||
WorkspaceRecord,
|
||||
ProfileSnapshot,
|
||||
SectionId,
|
||||
} from "@shared/types";
|
||||
import type { SectionId } from "@shared/types";
|
||||
import { NEW_ENTITY_ID } from "../../lib/constants";
|
||||
import {
|
||||
Target,
|
||||
@@ -28,48 +20,38 @@ import {
|
||||
import { HiringPipeline } from "../../components/hiring";
|
||||
import { ActionButton, Modal, Panel } from "../../components/ui";
|
||||
import { useT } from "../../i18n";
|
||||
import { useAppStore } from "../../state/store";
|
||||
import { useSectionContext } from "../../hooks/useSectionContext";
|
||||
import { useAppActions } from "../../hooks/useAppActions";
|
||||
|
||||
interface OrganizationSectionsProps {
|
||||
section: SectionId;
|
||||
snapshot: ProfileSnapshot;
|
||||
currentCompany: CompanyRecord | null;
|
||||
companyAgents: AgentRecord[];
|
||||
companyGoals: GoalRecord[];
|
||||
companyProjects: ProjectRecord[];
|
||||
companyWorkspaces: WorkspaceRecord[];
|
||||
selectedAgentId: string | null;
|
||||
setSelectedAgentId: (id: string | null) => void;
|
||||
selectedGoalId: string | null;
|
||||
setSelectedGoalId: (id: string | null) => void;
|
||||
selectedProjectId: string | null;
|
||||
setSelectedProjectId: (id: string | null) => void;
|
||||
setSection: (section: SectionId) => void;
|
||||
refresh: () => Promise<void>;
|
||||
}
|
||||
|
||||
export function OrganizationSections({
|
||||
section,
|
||||
snapshot,
|
||||
currentCompany,
|
||||
companyAgents,
|
||||
companyGoals,
|
||||
companyProjects,
|
||||
companyWorkspaces,
|
||||
selectedAgentId,
|
||||
setSelectedAgentId,
|
||||
selectedGoalId,
|
||||
setSelectedGoalId,
|
||||
selectedProjectId,
|
||||
setSelectedProjectId,
|
||||
setSection,
|
||||
refresh,
|
||||
}: OrganizationSectionsProps) {
|
||||
export function OrganizationSections({ section }: OrganizationSectionsProps) {
|
||||
const t = useT();
|
||||
const selectedAgent = companyAgents.find((a) => a.id === selectedAgentId) ?? null;
|
||||
const selectedGoal = companyGoals.find((g) => g.id === selectedGoalId) ?? null;
|
||||
const selectedProject = companyProjects.find((p) => p.id === selectedProjectId) ?? null;
|
||||
const {
|
||||
snapshot,
|
||||
currentCompany,
|
||||
companyAgents,
|
||||
companyGoals,
|
||||
companyProjects,
|
||||
companyWorkspaces,
|
||||
selectedAgent,
|
||||
selectedGoal,
|
||||
selectedProject,
|
||||
} = useSectionContext();
|
||||
const { refresh } = useAppActions();
|
||||
|
||||
if (!currentCompany) return null;
|
||||
const selectedAgentId = useAppStore((s) => s.selectedAgentId);
|
||||
const setSelectedAgentId = useAppStore((s) => s.setSelectedAgentId);
|
||||
const selectedGoalId = useAppStore((s) => s.selectedGoalId);
|
||||
const setSelectedGoalId = useAppStore((s) => s.setSelectedGoalId);
|
||||
const selectedProjectId = useAppStore((s) => s.selectedProjectId);
|
||||
const setSelectedProjectId = useAppStore((s) => s.setSelectedProjectId);
|
||||
const setSection = useAppStore((s) => s.setSection);
|
||||
|
||||
if (!snapshot || !currentCompany) return null;
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
Reference in new issue
Block a user