mirror of
https://github.com/dartdavros/chatballs.git
synced 2026-10-05 09:14:58 +03:00
refactor(ui): real ids in client/order detail URLs; drop mock route stubs
Client and order detail were addressed by in-session state only, with
hardcoded CUS-4702 / ORD-10519 placeholder paths — a browser refresh lost
the entity. Now the URL carries the numeric id like employees/products:
- routeFromPath/pathFromRoute parse and emit /departments/sales/clients/:id
and /departments/sales/orders/:id; RouteState carries clientId/orderId.
- App seeds selected ids from the initial route and restores them on
popstate, so deep-link + back/forward + refresh work.
- routes.ts breadcrumb labels for these are now generic ("Клиент"/"Заказ")
instead of the fake name/code.
Memory: recorded the "don't propose FirePage content" preference.
Typecheck + vitest (17) green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
1 parent
c99bfdd828
commit
e35e00ea8c
4 files changed
+38
-13
No files matched your search
@@ -26,8 +26,8 @@ export function App() {
|
||||
const [selectedAgentId, setSelectedAgentId] = useState<number | null>(initialRoute.agentId);
|
||||
const [selectedReleaseId, setSelectedReleaseId] = useState<number | null>(initialRoute.releaseId);
|
||||
const [selectedConversationId, setSelectedConversationId] = useState<number | null>(null);
|
||||
const [selectedClientId, setSelectedClientId] = useState<number | null>(null);
|
||||
const [selectedOrderId, setSelectedOrderId] = useState<number | null>(null);
|
||||
const [selectedClientId, setSelectedClientId] = useState<number | null>(initialRoute.clientId);
|
||||
const [selectedOrderId, setSelectedOrderId] = useState<number | null>(initialRoute.orderId);
|
||||
const [data, setData] = useState<AppData>({ employees: [], departments: [], products: [], agents: [], releases: [] });
|
||||
const [dataError, setDataError] = useState(false);
|
||||
|
||||
@@ -103,6 +103,8 @@ export function App() {
|
||||
setSelectedProductCode(nextRoute.productCode);
|
||||
setSelectedAgentId(nextRoute.agentId);
|
||||
setSelectedReleaseId(nextRoute.releaseId);
|
||||
setSelectedClientId(nextRoute.clientId);
|
||||
setSelectedOrderId(nextRoute.orderId);
|
||||
};
|
||||
window.addEventListener("popstate", onPopState);
|
||||
return () => window.removeEventListener("popstate", onPopState);
|
||||
|
||||
@@ -4,7 +4,7 @@ import { pathFromRoute, routeFromPath } from "./router";
|
||||
|
||||
describe("product routes", () => {
|
||||
it("parses a product detail URL", () => {
|
||||
expect(routeFromPath("/products/42")).toEqual({ route: "productDetail", employeeId: null, productId: 42, productCode: null, agentId: null, releaseId: null });
|
||||
expect(routeFromPath("/products/42")).toEqual({ route: "productDetail", employeeId: null, productId: 42, productCode: null, agentId: null, releaseId: null, clientId: null, orderId: null });
|
||||
});
|
||||
|
||||
it("creates a product detail URL", () => {
|
||||
@@ -12,9 +12,24 @@ describe("product routes", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("sales detail routes", () => {
|
||||
it("parses a client detail URL", () => {
|
||||
expect(routeFromPath("/departments/sales/clients/15")).toEqual({ route: "salesClientDetail", employeeId: null, productId: null, productCode: null, agentId: null, releaseId: null, clientId: 15, orderId: null });
|
||||
});
|
||||
|
||||
it("parses an order detail URL", () => {
|
||||
expect(routeFromPath("/departments/sales/orders/8")).toEqual({ route: "salesOrderDetail", employeeId: null, productId: null, productCode: null, agentId: null, releaseId: null, clientId: null, orderId: 8 });
|
||||
});
|
||||
|
||||
it("creates client and order detail URLs", () => {
|
||||
expect(pathFromRoute("salesClientDetail", 15)).toBe("/departments/sales/clients/15");
|
||||
expect(pathFromRoute("salesOrderDetail", 8)).toBe("/departments/sales/orders/8");
|
||||
});
|
||||
});
|
||||
|
||||
describe("ai agent routes", () => {
|
||||
it("parses an AI agent detail URL", () => {
|
||||
expect(routeFromPath("/ai/agents/7")).toEqual({ route: "aiAgentDetail", employeeId: null, productId: null, productCode: null, agentId: 7, releaseId: null });
|
||||
expect(routeFromPath("/ai/agents/7")).toEqual({ route: "aiAgentDetail", employeeId: null, productId: null, productCode: null, agentId: 7, releaseId: null, clientId: null, orderId: null });
|
||||
});
|
||||
|
||||
it("creates an AI agent detail URL", () => {
|
||||
@@ -22,7 +37,7 @@ describe("ai agent routes", () => {
|
||||
});
|
||||
|
||||
it("parses an AI agent creation URL", () => {
|
||||
expect(routeFromPath("/ai/agents/new", "?product=academy")).toEqual({ route: "aiAgentCreate", employeeId: null, productId: null, productCode: "academy", agentId: null, releaseId: null });
|
||||
expect(routeFromPath("/ai/agents/new", "?product=academy")).toEqual({ route: "aiAgentCreate", employeeId: null, productId: null, productCode: "academy", agentId: null, releaseId: null, clientId: null, orderId: null });
|
||||
});
|
||||
|
||||
it("creates an AI agent creation URL", () => {
|
||||
@@ -32,7 +47,7 @@ describe("ai agent routes", () => {
|
||||
|
||||
describe("ai release routes", () => {
|
||||
it("parses a release URL", () => {
|
||||
expect(routeFromPath("/ai/releases/12")).toEqual({ route: "aiRelease", employeeId: null, productId: null, productCode: null, agentId: null, releaseId: 12 });
|
||||
expect(routeFromPath("/ai/releases/12")).toEqual({ route: "aiRelease", employeeId: null, productId: null, productCode: null, agentId: null, releaseId: 12, clientId: null, orderId: null });
|
||||
});
|
||||
|
||||
it("creates a release URL", () => {
|
||||
|
||||
@@ -7,19 +7,27 @@ export type RouteState = {
|
||||
productCode: string | null;
|
||||
agentId: number | null;
|
||||
releaseId: number | null;
|
||||
clientId: number | null;
|
||||
orderId: number | null;
|
||||
};
|
||||
|
||||
export function routeFromPath(pathname: string, search = ""): RouteState {
|
||||
const path = pathname.replace(/\/+$/, "") || "/";
|
||||
const base = { employeeId: null, productId: null, productCode: null, agentId: null, releaseId: null };
|
||||
const base = { employeeId: null, productId: null, productCode: null, agentId: null, releaseId: null, clientId: null, orderId: null };
|
||||
if (path === "/" || path === "/command") return { route: "command", ...base };
|
||||
if (path === "/departments") return { route: "departments", ...base };
|
||||
if (path === "/departments/sales") return { route: "salesOverview", ...base };
|
||||
if (path === "/departments/sales/clients") return { route: "salesClients", ...base };
|
||||
if (path === "/departments/sales/clients/CUS-4702") return { route: "salesClientDetail", ...base };
|
||||
if (path.startsWith("/departments/sales/clients/")) {
|
||||
const id = Number(path.split("/")[4]);
|
||||
return Number.isInteger(id) && id > 0 ? { ...base, route: "salesClientDetail", clientId: id } : { route: "salesClients", ...base };
|
||||
}
|
||||
if (path === "/departments/sales/dialogs") return { route: "salesDialogs", ...base };
|
||||
if (path === "/departments/sales/orders") return { route: "salesOrders", ...base };
|
||||
if (path === "/departments/sales/orders/ORD-10519") return { route: "salesOrderDetail", ...base };
|
||||
if (path.startsWith("/departments/sales/orders/")) {
|
||||
const id = Number(path.split("/")[4]);
|
||||
return Number.isInteger(id) && id > 0 ? { ...base, route: "salesOrderDetail", orderId: id } : { route: "salesOrders", ...base };
|
||||
}
|
||||
if (path === "/employees") return { route: "employees", ...base };
|
||||
if (path.startsWith("/employees/")) {
|
||||
const id = Number(path.split("/")[2]);
|
||||
@@ -55,10 +63,10 @@ export function pathFromRoute(route: RouteKey, entityId: number | null = null, p
|
||||
if (route === "departments") return "/departments";
|
||||
if (route === "salesOverview") return "/departments/sales";
|
||||
if (route === "salesClients") return "/departments/sales/clients";
|
||||
if (route === "salesClientDetail") return "/departments/sales/clients/CUS-4702";
|
||||
if (route === "salesClientDetail") return entityId ? `/departments/sales/clients/${entityId}` : "/departments/sales/clients";
|
||||
if (route === "salesDialogs") return "/departments/sales/dialogs";
|
||||
if (route === "salesOrders") return "/departments/sales/orders";
|
||||
if (route === "salesOrderDetail") return "/departments/sales/orders/ORD-10519";
|
||||
if (route === "salesOrderDetail") return entityId ? `/departments/sales/orders/${entityId}` : "/departments/sales/orders";
|
||||
if (route === "employees") return "/employees";
|
||||
if (route === "employeeDetail") return entityId ? `/employees/${entityId}` : "/employees";
|
||||
if (route === "products") return "/products";
|
||||
|
||||
@@ -8,10 +8,10 @@ export const routes: Record<RouteKey, string> = {
|
||||
productDetail: "Продукты",
|
||||
products: "Продукты",
|
||||
profile: "Профиль",
|
||||
salesClientDetail: "Елена Кузнецова",
|
||||
salesClientDetail: "Клиент",
|
||||
salesClients: "Клиенты",
|
||||
salesDialogs: "Диалоги",
|
||||
salesOrderDetail: "ORD-10519",
|
||||
salesOrderDetail: "Заказ",
|
||||
salesOrders: "Продажи",
|
||||
salesOverview: "Обзор",
|
||||
aiAgents: "AI-агенты",
|
||||
|
||||
Reference in new issue
Block a user