Files
IdeA/frontend/src/features/layout/useLayouts.ts
Blomios 307ae71857 feat: add main features
Agents for developpement added + frontend add + backend added. Git viewer created + agent and template creator + layout and project creator
2026-06-06 01:27:01 +02:00

154 lines
4.5 KiB
TypeScript

/**
* `useLayouts` — view-model hook for the named-layout tab bar (L4 / #4).
*
* Manages the list of named layouts for a project (list / create / rename /
* delete / set-active). Speaks exclusively to the {@link LayoutGateway} port.
*/
import { useCallback, useEffect, useState } from "react";
import type { GatewayError, LayoutInfo, LayoutKind } from "@/domain";
import { useGateways } from "@/app/di";
export interface LayoutsViewModel {
/** Ordered list of named layouts. */
layouts: LayoutInfo[];
/** Currently active layout id. */
activeId: string | null;
/** Whether a request is in flight. */
busy: boolean;
/** Last error message, or `null`. */
error: string | null;
/** Switches the active layout (does NOT force a re-fetch here; the caller uses the returned activeId). */
setActive: (layoutId: string) => Promise<void>;
/** Creates a new layout with the given name and kind; resolves with the new layoutId. */
create: (name: string, kind?: LayoutKind) => Promise<string | null>;
/** Renames a layout. */
rename: (layoutId: string, name: string) => Promise<void>;
/** Deletes a layout; refuses if it is the last one. */
deleteLayout: (layoutId: string) => Promise<void>;
}
function describe(e: unknown): string {
if (e && typeof e === "object" && "message" in e) {
return String((e as GatewayError).message);
}
return String(e);
}
export function useLayouts(projectId: string | null): LayoutsViewModel {
const { layout: gateway } = useGateways();
const [layouts, setLayouts] = useState<LayoutInfo[]>([]);
const [activeId, setActiveId] = useState<string | null>(null);
const [busy, setBusy] = useState(false);
const [error, setError] = useState<string | null>(null);
// Load the list on mount and whenever projectId changes.
useEffect(() => {
if (!projectId || !gateway) {
setLayouts([]);
setActiveId(null);
return;
}
let cancelled = false;
setBusy(true);
gateway
.listLayouts(projectId)
.then(({ layouts: list, activeId: aid }) => {
if (!cancelled) {
setLayouts(list);
setActiveId(aid);
}
})
.catch((e: unknown) => {
if (!cancelled) setError(describe(e));
})
.finally(() => {
if (!cancelled) setBusy(false);
});
return () => {
cancelled = true;
};
}, [gateway, projectId]);
const setActive = useCallback(
async (layoutId: string) => {
if (!projectId || !gateway) return;
setBusy(true);
setError(null);
try {
await gateway.setActiveLayout(projectId, layoutId);
setActiveId(layoutId);
} catch (e) {
setError(describe(e));
} finally {
setBusy(false);
}
},
[gateway, projectId],
);
const create = useCallback(
async (name: string, kind?: LayoutKind): Promise<string | null> => {
if (!projectId || !gateway) return null;
setBusy(true);
setError(null);
try {
const { layoutId } = await gateway.createLayout(projectId, name, kind);
const updated = await gateway.listLayouts(projectId);
setLayouts(updated.layouts);
return layoutId;
} catch (e) {
setError(describe(e));
return null;
} finally {
setBusy(false);
}
},
[gateway, projectId],
);
const rename = useCallback(
async (layoutId: string, name: string) => {
if (!projectId || !gateway) return;
setBusy(true);
setError(null);
try {
await gateway.renameLayout(projectId, layoutId, name);
setLayouts((prev) =>
prev.map((l) => (l.id === layoutId ? { ...l, name } : l)),
);
} catch (e) {
setError(describe(e));
} finally {
setBusy(false);
}
},
[gateway, projectId],
);
const deleteLayout = useCallback(
async (layoutId: string) => {
if (!projectId || !gateway) return;
if (layouts.length <= 1) {
setError("Cannot delete the last layout.");
return;
}
setBusy(true);
setError(null);
try {
const { activeId: newActiveId } = await gateway.deleteLayout(projectId, layoutId);
setLayouts((prev) => prev.filter((l) => l.id !== layoutId));
setActiveId(newActiveId);
} catch (e) {
setError(describe(e));
} finally {
setBusy(false);
}
},
[gateway, projectId, layouts.length],
);
return { layouts, activeId, busy, error, setActive, create, rename, deleteLayout };
}