From 7fa211966129e54ce57e0c8a64f8cf5dfcf99bf9 Mon Sep 17 00:00:00 2001 From: gsknnft Date: Fri, 3 Apr 2026 23:49:01 -0400 Subject: [PATCH] moved floor nav --- .../office/components/OfficeFloorNav.tsx | 151 ++++++++++++++++++ src/features/office/screens/OfficeScreen.tsx | 63 +------- src/lib/office/floors.ts | 28 ++++ tests/unit/officeFloors.test.ts | 16 ++ 4 files changed, 201 insertions(+), 57 deletions(-) create mode 100644 src/features/office/components/OfficeFloorNav.tsx diff --git a/src/features/office/components/OfficeFloorNav.tsx b/src/features/office/components/OfficeFloorNav.tsx new file mode 100644 index 0000000..0afbb28 --- /dev/null +++ b/src/features/office/components/OfficeFloorNav.tsx @@ -0,0 +1,151 @@ +import { + OFFICE_FLOORS, + getAdjacentEnabledOfficeFloorId, + listOfficeFloorsForZone, + type FloorDefinition, + type FloorId, +} from "@/lib/office/floors"; +import type { FloorRosterState } from "@/lib/office/floorRoster"; + +type OfficeFloorNavProps = { + activeFloorId: FloorId; + floorRosterCache: Record; + onSelectFloor: (floorId: FloorId) => void; +}; + +const PROVIDER_LABEL: Record = { + demo: "Demo", + openclaw: "OpenClaw", + hermes: "Hermes", + custom: "Custom", +}; + +const renderFloorButton = (params: { + floor: FloorDefinition; + activeFloorId: FloorId; + floorRosterCache: Record; + onSelectFloor: (floorId: FloorId) => void; +}) => { + const { floor, activeFloorId, floorRosterCache, onSelectFloor } = params; + const active = floor.id === activeFloorId; + const rosterState = floorRosterCache[floor.id]; + const rosterCount = rosterState?.entries.length ?? 0; + const rosterStatus = rosterState?.status ?? "idle"; + + return ( + + ); +}; + +export function OfficeFloorNav({ + activeFloorId, + floorRosterCache, + onSelectFloor, +}: OfficeFloorNavProps) { + const buildingFloors = listOfficeFloorsForZone("building"); + const outsideFloors = listOfficeFloorsForZone("outside"); + const activeFloor = OFFICE_FLOORS.find((floor) => floor.id === activeFloorId) ?? OFFICE_FLOORS[0]; + const activeRoster = floorRosterCache[activeFloor.id]; + + return ( + + ); +} diff --git a/src/features/office/screens/OfficeScreen.tsx b/src/features/office/screens/OfficeScreen.tsx index 9f12441..e0bd93c 100644 --- a/src/features/office/screens/OfficeScreen.tsx +++ b/src/features/office/screens/OfficeScreen.tsx @@ -143,6 +143,7 @@ import { KanbanDisabledPanel } from "@/features/office/components/panels/KanbanD import { PlaybooksPanel } from "@/features/office/components/panels/PlaybooksPanel"; import { SkillsMarketplaceModal } from "@/features/office/components/panels/SkillsMarketplaceModal"; import { TaskBoardPanel } from "@/features/office/components/panels/TaskBoardPanel"; +import { OfficeFloorNav } from "@/features/office/components/OfficeFloorNav"; import { JukeboxPanel } from "@/features/spotify-jukebox/components/JukeboxPanel"; import { JukeboxDisabledPanel } from "@/features/spotify-jukebox/components/JukeboxDisabledPanel"; import { executeBrowserJukeboxCommand } from "@/features/spotify-jukebox/agentBridge"; @@ -183,12 +184,9 @@ import { buildFloorRosterErrorState, buildFloorRosterState, createFloorRosterCache, - defaultFloorRosterState, } from "@/lib/office/floorRoster"; import { - getAdjacentEnabledOfficeFloorId, getOfficeFloor, - listEnabledOfficeFloors, resolveActiveOfficeFloorId, type FloorId, } from "@/lib/office/floors"; @@ -1036,13 +1034,10 @@ export function OfficeScreen({ const { showOnboarding, completeOnboarding, resetOnboarding } = useOnboardingState(); const [forceShowOnboarding, setForceShowOnboarding] = useState(false); - const enabledFloors = useMemo(() => listEnabledOfficeFloors(), []); const activeFloor = useMemo( () => getOfficeFloor(resolveActiveOfficeFloorId(activeFloorId)), [activeFloorId], ); - const activeFloorRoster = - floorRosterCache[activeFloor.id] ?? defaultFloorRosterState(activeFloor.id); useEffect(() => { initJukeboxStore(); }, [initJukeboxStore]); @@ -4619,57 +4614,11 @@ export function OfficeScreen({ ) : null} -
-
- -
-
- Active floor -
-
- - - {activeFloor.provider} - -
-
- roster {activeFloorRoster.entries.length} • {activeFloorRoster.status} -
-
- -
-
+ {deleteAgentStatusLine ? (
diff --git a/src/lib/office/floors.ts b/src/lib/office/floors.ts index b8dc02d..332b644 100644 --- a/src/lib/office/floors.ts +++ b/src/lib/office/floors.ts @@ -1,4 +1,5 @@ export type FloorProvider = "openclaw" | "hermes" | "custom" | "demo"; +export type FloorZone = "building" | "outside"; export type FloorId = | "lobby" @@ -19,9 +20,12 @@ export type FloorKind = export type FloorDefinition = { id: FloorId; label: string; + shortLabel: string; provider: FloorProvider; kind: FloorKind; + zone: FloorZone; enabled: boolean; + sortOrder: number; runtimeProfileId: string | null; }; @@ -29,57 +33,78 @@ export const OFFICE_FLOORS: readonly FloorDefinition[] = [ { id: "lobby", label: "Lobby", + shortLabel: "Lobby", provider: "demo", kind: "lobby", + zone: "building", enabled: true, + sortOrder: 0, runtimeProfileId: null, }, { id: "openclaw-ground", label: "OpenClaw Floor", + shortLabel: "OpenClaw", provider: "openclaw", kind: "runtime", + zone: "building", enabled: true, + sortOrder: 10, runtimeProfileId: "openclaw-default", }, { id: "hermes-first", label: "Hermes Floor", + shortLabel: "Hermes", provider: "hermes", kind: "runtime", + zone: "building", enabled: true, + sortOrder: 20, runtimeProfileId: "hermes-default", }, { id: "custom-second", label: "Custom Floor", + shortLabel: "Custom", provider: "custom", kind: "runtime", + zone: "building", enabled: true, + sortOrder: 30, runtimeProfileId: "custom-default", }, { id: "training", label: "Training Floor", + shortLabel: "Training", provider: "demo", kind: "training", + zone: "building", enabled: false, + sortOrder: 40, runtimeProfileId: null, }, { id: "traders-floor", label: "Trader's Floor", + shortLabel: "Traders", provider: "demo", kind: "market", + zone: "building", enabled: false, + sortOrder: 50, runtimeProfileId: null, }, { id: "campus", label: "Outside / Campus", + shortLabel: "Campus", provider: "demo", kind: "campus", + zone: "outside", enabled: false, + sortOrder: 100, runtimeProfileId: null, }, ] as const; @@ -102,6 +127,9 @@ export const listEnabledOfficeFloors = (): FloorDefinition[] => export const listOfficeFloorsForProvider = (provider: FloorProvider): FloorDefinition[] => OFFICE_FLOORS.filter((floor) => floor.provider === provider); +export const listOfficeFloorsForZone = (zone: FloorZone): FloorDefinition[] => + OFFICE_FLOORS.filter((floor) => floor.zone === zone); + export const resolveActiveOfficeFloorId = (floorId: FloorId | null | undefined): FloorId => { if (floorId && FLOOR_BY_ID[floorId]?.enabled) { return floorId; diff --git a/tests/unit/officeFloors.test.ts b/tests/unit/officeFloors.test.ts index f420b28..ac86e3c 100644 --- a/tests/unit/officeFloors.test.ts +++ b/tests/unit/officeFloors.test.ts @@ -6,6 +6,7 @@ import { getAdjacentEnabledOfficeFloorId, listEnabledOfficeFloors, listOfficeFloorsForProvider, + listOfficeFloorsForZone, OFFICE_FLOORS, resolveActiveOfficeFloorId, } from "@/lib/office/floors"; @@ -26,9 +27,12 @@ describe("office floor registry", () => { it("looks up floors by id", () => { expect(getOfficeFloor("hermes-first")).toMatchObject({ label: "Hermes Floor", + shortLabel: "Hermes", provider: "hermes", kind: "runtime", + zone: "building", enabled: true, + sortOrder: 20, runtimeProfileId: "hermes-default", }); }); @@ -51,6 +55,18 @@ describe("office floor registry", () => { ]); }); + it("groups floors by zone for building navigation", () => { + expect(listOfficeFloorsForZone("building").map((floor) => floor.id)).toEqual([ + "lobby", + "openclaw-ground", + "hermes-first", + "custom-second", + "training", + "traders-floor", + ]); + expect(listOfficeFloorsForZone("outside").map((floor) => floor.id)).toEqual(["campus"]); + }); + it("resolves active floor ids against enabled floors", () => { expect(DEFAULT_ACTIVE_FLOOR_ID).toBe("lobby"); expect(resolveActiveOfficeFloorId("hermes-first")).toBe("hermes-first");