2025-12-17 23:05:49 +03:00
|
|
|
|
// lib/api/menu-api.ts
|
|
|
|
|
|
|
2025-12-11 01:12:45 +03:00
|
|
|
|
import { fetchGraphQL } from './graphql-client.js';
|
|
|
|
|
|
|
2025-12-17 23:05:49 +03:00
|
|
|
|
export interface MenuItem {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
databaseId: number;
|
|
|
|
|
|
uri: string;
|
|
|
|
|
|
url: string;
|
|
|
|
|
|
order: number;
|
|
|
|
|
|
label: string;
|
|
|
|
|
|
parentId: string | null;
|
|
|
|
|
|
target: string;
|
|
|
|
|
|
cssClasses: string[];
|
|
|
|
|
|
description: string;
|
|
|
|
|
|
childItems?: {
|
|
|
|
|
|
nodes: MenuItem[];
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
2025-12-11 01:12:45 +03:00
|
|
|
|
|
2025-12-17 23:05:49 +03:00
|
|
|
|
export interface Menu {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
databaseId: number;
|
|
|
|
|
|
name: string;
|
|
|
|
|
|
slug: string;
|
|
|
|
|
|
locations: string[];
|
|
|
|
|
|
menuItems: {
|
|
|
|
|
|
nodes: MenuItem[];
|
|
|
|
|
|
};
|
2025-12-13 23:29:25 +03:00
|
|
|
|
}
|
2025-12-11 01:12:45 +03:00
|
|
|
|
|
2025-12-17 23:05:49 +03:00
|
|
|
|
export type MenuIdentifier =
|
|
|
|
|
|
| { id: number } // По ID меню
|
|
|
|
|
|
| { location: string } // По локации
|
|
|
|
|
|
| { slug: string } // По слагу
|
|
|
|
|
|
| { name: string }; // По имени
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Получить меню по идентификатору
|
|
|
|
|
|
*/
|
|
|
|
|
|
export async function fetchMenu(identifier: MenuIdentifier): Promise<Menu | null> {
|
|
|
|
|
|
try {
|
|
|
|
|
|
// Определяем тип запроса на основе переданного идентификатора
|
|
|
|
|
|
if ('id' in identifier) {
|
|
|
|
|
|
return await fetchMenuById(identifier.id);
|
|
|
|
|
|
}
|
|
|
|
|
|
if ('location' in identifier) {
|
|
|
|
|
|
return await fetchMenuByLocation(identifier.location);
|
|
|
|
|
|
}
|
|
|
|
|
|
if ('slug' in identifier) {
|
|
|
|
|
|
return await fetchMenuBySlug(identifier.slug);
|
|
|
|
|
|
}
|
|
|
|
|
|
if ('name' in identifier) {
|
|
|
|
|
|
return await fetchMenuByName(identifier.name);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Error fetching menu:', error);
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
2025-12-13 23:29:25 +03:00
|
|
|
|
}
|
2025-12-11 01:12:45 +03:00
|
|
|
|
|
2025-12-17 23:05:49 +03:00
|
|
|
|
/**
|
|
|
|
|
|
* Получить меню по ID (самый надежный способ)
|
|
|
|
|
|
*/
|
|
|
|
|
|
async function fetchMenuById(id: number): Promise<Menu | null> {
|
|
|
|
|
|
const query = `
|
|
|
|
|
|
query GetMenuById($id: ID!) {
|
|
|
|
|
|
menu(id: $id, idType: DATABASE_ID) {
|
|
|
|
|
|
id
|
|
|
|
|
|
databaseId
|
|
|
|
|
|
name
|
|
|
|
|
|
slug
|
|
|
|
|
|
locations
|
|
|
|
|
|
menuItems(first: 100) {
|
|
|
|
|
|
nodes {
|
|
|
|
|
|
id
|
|
|
|
|
|
databaseId
|
|
|
|
|
|
uri
|
|
|
|
|
|
url
|
|
|
|
|
|
order
|
|
|
|
|
|
label
|
|
|
|
|
|
parentId
|
|
|
|
|
|
target
|
|
|
|
|
|
cssClasses
|
|
|
|
|
|
description
|
|
|
|
|
|
childItems(first: 50) {
|
|
|
|
|
|
nodes {
|
|
|
|
|
|
id
|
|
|
|
|
|
databaseId
|
|
|
|
|
|
label
|
|
|
|
|
|
uri
|
|
|
|
|
|
url
|
|
|
|
|
|
order
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
`;
|
|
|
|
|
|
|
|
|
|
|
|
const variables = { id };
|
|
|
|
|
|
const data = await fetchGraphQL(query, variables);
|
|
|
|
|
|
|
|
|
|
|
|
if (data?.menu) {
|
|
|
|
|
|
return {
|
|
|
|
|
|
...data.menu,
|
|
|
|
|
|
menuItems: data.menu.menuItems || { nodes: [] }
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Получить меню по локации
|
|
|
|
|
|
*/
|
|
|
|
|
|
async function fetchMenuByLocation(location: string): Promise<Menu | null> {
|
|
|
|
|
|
const query = `
|
|
|
|
|
|
query GetMenuByLocation($location: MenuLocationEnum!) {
|
|
|
|
|
|
menus(where: { location: $location }, first: 1) {
|
|
|
|
|
|
nodes {
|
|
|
|
|
|
id
|
|
|
|
|
|
databaseId
|
|
|
|
|
|
name
|
|
|
|
|
|
slug
|
|
|
|
|
|
locations
|
|
|
|
|
|
menuItems(first: 100) {
|
|
|
|
|
|
nodes {
|
|
|
|
|
|
id
|
|
|
|
|
|
databaseId
|
|
|
|
|
|
uri
|
|
|
|
|
|
url
|
|
|
|
|
|
order
|
|
|
|
|
|
label
|
|
|
|
|
|
parentId
|
|
|
|
|
|
target
|
|
|
|
|
|
cssClasses
|
|
|
|
|
|
description
|
|
|
|
|
|
childItems(first: 50) {
|
|
|
|
|
|
nodes {
|
|
|
|
|
|
id
|
|
|
|
|
|
databaseId
|
|
|
|
|
|
label
|
|
|
|
|
|
uri
|
|
|
|
|
|
url
|
|
|
|
|
|
order
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
`;
|
|
|
|
|
|
|
|
|
|
|
|
const variables = { location };
|
|
|
|
|
|
const data = await fetchGraphQL(query, variables);
|
|
|
|
|
|
return data?.menus?.nodes?.[0] || null;
|
2025-12-13 23:29:25 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2025-12-17 23:05:49 +03:00
|
|
|
|
* Получить меню по слагу
|
2025-12-13 23:29:25 +03:00
|
|
|
|
*/
|
2025-12-17 23:05:49 +03:00
|
|
|
|
async function fetchMenuBySlug(slug: string): Promise<Menu | null> {
|
|
|
|
|
|
const query = `
|
|
|
|
|
|
query GetMenuBySlug($slug: String!) {
|
|
|
|
|
|
menus(where: { slug: $slug }, first: 1) {
|
|
|
|
|
|
nodes {
|
|
|
|
|
|
id
|
|
|
|
|
|
databaseId
|
|
|
|
|
|
name
|
|
|
|
|
|
slug
|
|
|
|
|
|
locations
|
|
|
|
|
|
menuItems(first: 100) {
|
|
|
|
|
|
nodes {
|
|
|
|
|
|
id
|
|
|
|
|
|
databaseId
|
|
|
|
|
|
uri
|
|
|
|
|
|
url
|
|
|
|
|
|
order
|
|
|
|
|
|
label
|
|
|
|
|
|
parentId
|
|
|
|
|
|
target
|
|
|
|
|
|
cssClasses
|
|
|
|
|
|
description
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-12-13 23:29:25 +03:00
|
|
|
|
}
|
2025-12-11 01:12:45 +03:00
|
|
|
|
}
|
2025-12-17 23:05:49 +03:00
|
|
|
|
`;
|
|
|
|
|
|
|
|
|
|
|
|
const variables = { slug };
|
|
|
|
|
|
const data = await fetchGraphQL(query, variables);
|
|
|
|
|
|
return data?.menus?.nodes?.[0] || null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Получить меню по имени
|
|
|
|
|
|
*/
|
|
|
|
|
|
async function fetchMenuByName(name: string): Promise<Menu | null> {
|
|
|
|
|
|
const query = `
|
|
|
|
|
|
query GetMenuByName($name: String!) {
|
|
|
|
|
|
menus(where: { name: $name }, first: 1) {
|
|
|
|
|
|
nodes {
|
|
|
|
|
|
id
|
|
|
|
|
|
databaseId
|
|
|
|
|
|
name
|
|
|
|
|
|
slug
|
|
|
|
|
|
locations
|
|
|
|
|
|
menuItems(first: 100) {
|
|
|
|
|
|
nodes {
|
|
|
|
|
|
id
|
|
|
|
|
|
databaseId
|
|
|
|
|
|
uri
|
|
|
|
|
|
url
|
|
|
|
|
|
order
|
|
|
|
|
|
label
|
|
|
|
|
|
parentId
|
|
|
|
|
|
target
|
|
|
|
|
|
cssClasses
|
|
|
|
|
|
description
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
`;
|
|
|
|
|
|
|
|
|
|
|
|
const variables = { name };
|
|
|
|
|
|
const data = await fetchGraphQL(query, variables);
|
|
|
|
|
|
return data?.menus?.nodes?.[0] || null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Преобразовать плоский список элементов меню в иерархическую структуру
|
|
|
|
|
|
*/
|
|
|
|
|
|
export function buildMenuHierarchy(menuItems: MenuItem[]): MenuItem[] {
|
|
|
|
|
|
const itemsMap = new Map<string, MenuItem>();
|
|
|
|
|
|
const rootItems: MenuItem[] = [];
|
|
|
|
|
|
|
|
|
|
|
|
// Создаем map всех элементов
|
|
|
|
|
|
menuItems.forEach(item => {
|
|
|
|
|
|
itemsMap.set(item.id, {
|
|
|
|
|
|
...item,
|
|
|
|
|
|
childItems: { nodes: [] }
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// Строим иерархию
|
|
|
|
|
|
menuItems.forEach(item => {
|
|
|
|
|
|
const menuItem = itemsMap.get(item.id)!;
|
|
|
|
|
|
|
|
|
|
|
|
if (item.parentId && itemsMap.has(item.parentId)) {
|
|
|
|
|
|
const parent = itemsMap.get(item.parentId)!;
|
|
|
|
|
|
if (!parent.childItems) {
|
|
|
|
|
|
parent.childItems = { nodes: [] };
|
|
|
|
|
|
}
|
|
|
|
|
|
parent.childItems.nodes.push(menuItem);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
rootItems.push(menuItem);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// Сортируем элементы по order
|
|
|
|
|
|
const sortByOrder = (items: MenuItem[]) =>
|
|
|
|
|
|
items.sort((a, b) => a.order - b.order);
|
|
|
|
|
|
|
|
|
|
|
|
// Рекурсивно сортируем все уровни
|
|
|
|
|
|
function sortRecursive(items: MenuItem[]) {
|
|
|
|
|
|
sortByOrder(items);
|
|
|
|
|
|
items.forEach(item => {
|
|
|
|
|
|
if (item.childItems?.nodes.length) {
|
|
|
|
|
|
sortRecursive(item.childItems.nodes);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
sortRecursive(rootItems);
|
|
|
|
|
|
return rootItems;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Получить меню в виде иерархической структуры
|
|
|
|
|
|
*/
|
|
|
|
|
|
export async function getHierarchicalMenu(identifier: MenuIdentifier): Promise<MenuItem[]> {
|
|
|
|
|
|
const menu = await fetchMenu(identifier);
|
|
|
|
|
|
if (!menu || !menu.menuItems?.nodes?.length) {
|
|
|
|
|
|
return [];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return buildMenuHierarchy(menu.menuItems.nodes);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Получить меню в виде плоского списка
|
|
|
|
|
|
*/
|
|
|
|
|
|
export async function getFlatMenu(identifier: MenuIdentifier): Promise<MenuItem[]> {
|
|
|
|
|
|
const menu = await fetchMenu(identifier);
|
|
|
|
|
|
if (!menu || !menu.menuItems?.nodes?.length) {
|
|
|
|
|
|
return [];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return menu.menuItems.nodes.sort((a, b) => a.order - b.order);
|
2025-12-11 01:12:45 +03:00
|
|
|
|
}
|