рефакторинг структуры
This commit is contained in:
@@ -5,10 +5,12 @@ import { LoginPage, RegisterPage, ResetPasswordPage } from "@/pages/auth";
|
||||
import { HomePage } from "@/pages/home";
|
||||
import { MailingById, MailingsPage } from "@/pages/mailings";
|
||||
import { SuppliersSearchPage } from "@/pages/suppliers-search";
|
||||
import { SuppliersSearchProvider } from "@/pages/suppliers-search/model/SuppliersSearchContext";
|
||||
import { ViewingRequestById, ViewingRequestsPage } from "@/pages/viewing-requests";
|
||||
import { AuthProvider, SuppliersSearchProvider, useAuth } from "@/shared/model";
|
||||
import { UILoader } from "@/shared/ui";
|
||||
|
||||
import { AuthProvider, useAuth } from "./providers/AuthContext";
|
||||
|
||||
const AppContent = () => {
|
||||
const { user, loading } = useAuth();
|
||||
|
||||
|
||||
@@ -2,11 +2,12 @@ import { Menu } from 'lucide-react';
|
||||
import { useState } from 'react';
|
||||
import { Outlet } from 'react-router-dom';
|
||||
|
||||
import { useDevices } from "@/shared/lib";
|
||||
import { Sidebar } from '@/widgets/sidebar'
|
||||
|
||||
export const AppLayout = () => {
|
||||
const [isSidebarOpen, setIsSidebarOpen] = useState(true);
|
||||
|
||||
const [isSidebarOpen, setIsSidebarOpen] = useState(false);
|
||||
const { isMobile } = useDevices()
|
||||
// Функция переключения состояния сайдбара
|
||||
const toggleSidebar = () => {
|
||||
setIsSidebarOpen(!isSidebarOpen);
|
||||
@@ -21,11 +22,13 @@ export const AppLayout = () => {
|
||||
<div className="min-h-screen bg-gray-50 flex">
|
||||
<Sidebar isOpen={isSidebarOpen} onClose={closeSidebar} />
|
||||
|
||||
<div className="flex-1 flex flex-col min-h-screen">
|
||||
<div className="flex-1 flex flex-col">
|
||||
{/* Кнопка в хедере только для мобильных */}
|
||||
{isMobile && (
|
||||
<header className="lg:hidden bg-white border-b border-gray-200 p-4">
|
||||
<Menu className="w-6 h-6 cursor-pointer hover:bg-gray-200" onClick={() => toggleSidebar()}/>
|
||||
</header>
|
||||
)}
|
||||
|
||||
<main className="flex-1 p-6 lg:p-8">
|
||||
<Outlet />
|
||||
|
||||
1
src/entities/mailing/index.ts
Normal file
1
src/entities/mailing/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { MailingItem } from './ui/MailingItem'
|
||||
@@ -9,7 +9,7 @@ interface IProps {
|
||||
onSelectItem: (id: string | number) => void
|
||||
}
|
||||
|
||||
export const MailingsItem = (props: IProps) => {
|
||||
export const MailingItem = (props: IProps) => {
|
||||
const { item, onSelectItem } = props;
|
||||
|
||||
const statusIcon = useMemo(() => {
|
||||
1
src/entities/supplier/index.ts
Normal file
1
src/entities/supplier/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { SuppliersTable } from './ui/SuppliersTable'
|
||||
@@ -1,7 +1,7 @@
|
||||
import { ArrowBigLeft } from "lucide-react";
|
||||
|
||||
import { useSuppliersSearch } from "@/pages/suppliers-search/model/SuppliersSearchContext";
|
||||
import { ISupplier } from "@/shared/api";
|
||||
import { useSuppliersSearch } from "@/shared/model";
|
||||
import {
|
||||
ButtonSize,
|
||||
ButtonVariant,
|
||||
1
src/entities/viewing-requests/index.ts
Normal file
1
src/entities/viewing-requests/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { ViewingRequestItem } from './ui/ViewingRequestItem'
|
||||
1
src/features/suppliers-search/request-input/index.ts
Normal file
1
src/features/suppliers-search/request-input/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { RequestInputForm } from './ui/RequestInputForm'
|
||||
@@ -1,8 +1,9 @@
|
||||
import { Send } from 'lucide-react';
|
||||
import { useState } from "react";
|
||||
|
||||
import { useAuth } from "@/app/providers/AuthContext";
|
||||
import { useSuppliersSearch } from "@/pages/suppliers-search/model/SuppliersSearchContext";
|
||||
import { suppliersSearchApi } from '@/shared/api';
|
||||
import { useAuth, useSuppliersSearch } from "@/shared/model";
|
||||
import {
|
||||
ButtonVariant,
|
||||
NotificationAppearance,
|
||||
@@ -0,0 +1 @@
|
||||
export { TechnicalSpecification } from './ui/TechnicalSpecification'
|
||||
@@ -1,8 +1,9 @@
|
||||
import { ArrowBigLeft, Search } from 'lucide-react';
|
||||
import { useState } from 'react';
|
||||
|
||||
import { useAuth } from "@/app/providers/AuthContext";
|
||||
import { useSuppliersSearch } from "@/pages/suppliers-search/model/SuppliersSearchContext";
|
||||
import { suppliersSearchApi } from '@/shared/api';
|
||||
import { useAuth, useSuppliersSearch } from "@/shared/model";
|
||||
import {
|
||||
ButtonVariant,
|
||||
NotificationAppearance,
|
||||
@@ -1,8 +1,9 @@
|
||||
import { useState } from 'react';
|
||||
import { Link, useNavigate } from "react-router";
|
||||
|
||||
import { useAuth } from "@/app/providers/AuthContext";
|
||||
import { authApi } from "@/shared/api/endpoints/auth/auth-api";
|
||||
import { ROUTES, useAuth } from "@/shared/model";
|
||||
import { ROUTES } from "@/shared/model";
|
||||
import { ButtonType, InputType, UIButton, UIInput } from "@/shared/ui";
|
||||
|
||||
export const LoginPage = () => {
|
||||
|
||||
@@ -2,8 +2,9 @@ import { useState } from 'react';
|
||||
import { Link } from "react-router";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
import { useAuth } from "@/app/providers/AuthContext";
|
||||
import { authApi } from '@/shared/api';
|
||||
import { ROUTES, useAuth } from "@/shared/model";
|
||||
import { ROUTES } from "@/shared/model";
|
||||
import { ButtonType, InputType, UIButton, UIInput, UIPhoneNumber } from '@/shared/ui';
|
||||
|
||||
export const RegisterPage = () => {
|
||||
|
||||
@@ -3,7 +3,7 @@ import { useParams } from "react-router";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
import { IMailingInfo, ISupplier, mailingApi } from "@/shared/api";
|
||||
import { ROUTES } from "@/shared/model/routes";
|
||||
import { ROUTES } from "@/shared/model";
|
||||
import {
|
||||
ButtonVariant, Column,
|
||||
NotificationAppearance,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
import { MailingsItem } from "@/pages/mailings/ui/MailingsItem";
|
||||
import { MailingItem } from "@/entities/mailing";
|
||||
import { IMailingItem, mailingApi } from "@/shared/api";
|
||||
import { ROUTES } from "@/shared/model";
|
||||
import {
|
||||
@@ -62,7 +62,7 @@ export const MailingsPage = () => {
|
||||
<div className="flex flex-col gap-4">
|
||||
{mailingList.length > 0 && (
|
||||
mailingList.map((item) => (
|
||||
<MailingsItem
|
||||
<MailingItem
|
||||
key={item.request_id}
|
||||
item={item}
|
||||
onSelectItem={() => navigate(`${ROUTES.MAILINGS}/${item.request_id}`)}
|
||||
|
||||
@@ -20,7 +20,7 @@ const SuppliersSearchContext = createContext<ISuppliersSearchContext>({
|
||||
export const useSuppliersSearch = () => {
|
||||
const context = useContext(SuppliersSearchContext);
|
||||
if (!context) {
|
||||
throw new Error('useSuppliersSearch must be used within AuthProvider');
|
||||
throw new Error('useSuppliersSearch must be used within AuthContext');
|
||||
}
|
||||
return context;
|
||||
};
|
||||
@@ -1,12 +1,13 @@
|
||||
import { useCallback, useState } from 'react';
|
||||
|
||||
import { StagesEnum } from "@/pages/suppliers-search";
|
||||
import { RequestInputForm } from "@/pages/suppliers-search/ui/RequestInputForm";
|
||||
import { SuppliersTable } from "@/pages/suppliers-search/ui/SuppliersTable";
|
||||
import { TechnicalSpecification } from "@/pages/suppliers-search/ui/TechnicalSpecification";
|
||||
import { useSuppliersSearch } from "@/shared/model";
|
||||
import { SuppliersTable } from "@/entities/supplier";
|
||||
import { RequestInputForm } from "@/features/suppliers-search/request-input";
|
||||
import { TechnicalSpecification } from "@/features/suppliers-search/technical-specification";
|
||||
import { useSuppliersSearch } from "@/pages/suppliers-search/model/SuppliersSearchContext";
|
||||
import { UIStepper } from "@/shared/ui";
|
||||
|
||||
import { StagesEnum } from "../model/types";
|
||||
|
||||
export const SuppliersSearchPage = () => {
|
||||
const [stage, setStage] = useState<StagesEnum>(StagesEnum.Input);
|
||||
const { suppliers, updateSuppliersSearchContext } = useSuppliersSearch();
|
||||
|
||||
@@ -1,3 +1,2 @@
|
||||
export { ViewingRequestsPage } from './ui/ViewingRequests'
|
||||
export { ViewingRequestById } from './ui/ViewingRequestById'
|
||||
export { ViewingRequestItem } from './ui/ViewingRequestItem'
|
||||
@@ -3,7 +3,6 @@ import { useEffect, useState } from "react";
|
||||
import { useParams } from "react-router";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
|
||||
import { IMailingInfo, ISupplier, mailingApi } from "@/shared/api";
|
||||
import {
|
||||
ButtonSize,
|
||||
@@ -103,7 +102,6 @@ export const ViewingRequestById = () => {
|
||||
value={message}
|
||||
placeholder="Текст сообщения"
|
||||
onChange={(val) => setMessage(val)}
|
||||
minCount={10}
|
||||
minHeight={500}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
import { ViewingRequestItem } from "@/entities/viewing-requests";
|
||||
import { IMailingItem, mailingApi } from "@/shared/api";
|
||||
import { ROUTES } from "@/shared/model/routes";
|
||||
import { ROUTES } from "@/shared/model";
|
||||
import {
|
||||
NotificationAppearance,
|
||||
UILoader,
|
||||
UINotification,
|
||||
} from "@/shared/ui";
|
||||
|
||||
import { ViewingRequestItem } from "./ViewingRequestItem";
|
||||
|
||||
export const ViewingRequestsPage = () => {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState('');
|
||||
|
||||
@@ -10,11 +10,11 @@ export const useDevices = (): UseDevicesType => {
|
||||
const [ isDesktop, setIsDesktop ] = useState<boolean>(window.innerWidth > 880)
|
||||
|
||||
const updateIsMobile = () => {
|
||||
setIsMobile(window.innerWidth < 880)
|
||||
setIsMobile(window.innerWidth < 1024)
|
||||
};
|
||||
|
||||
const updateIsDesktop = () => {
|
||||
setIsDesktop(window.innerWidth >= 880)
|
||||
setIsDesktop(window.innerWidth >= 1024)
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -1,23 +1,13 @@
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
export const ROUTES = {
|
||||
LOGIN: '/login',
|
||||
REGISTER: '/register',
|
||||
RESET_PASSWORD: '/reset-password',
|
||||
HOME: '/',
|
||||
SUPPLIERS_SEARCH: '/suppliers-search',
|
||||
MAILINGS: '/mailings',
|
||||
VIEWING_REQUESTS: '/viewing-requests',
|
||||
};
|
||||
|
||||
export type RouteName = keyof typeof ROUTES;
|
||||
import { ROUTES, RouteNameEnum } from "@/shared/model";
|
||||
|
||||
export const useNavigation = () => {
|
||||
const navigate = useNavigate();
|
||||
|
||||
const goTo = (route: RouteName | string) => {
|
||||
const goTo = (route: RouteNameEnum | string) => {
|
||||
if (route in ROUTES) {
|
||||
navigate(ROUTES[route as RouteName]);
|
||||
navigate(ROUTES[route as RouteNameEnum]);
|
||||
} else {
|
||||
navigate(route);
|
||||
}
|
||||
@@ -27,9 +17,9 @@ export const useNavigation = () => {
|
||||
navigate(-1);
|
||||
};
|
||||
|
||||
const replace = (route: RouteName | string) => {
|
||||
const replace = (route: RouteNameEnum | string) => {
|
||||
if (route in ROUTES) {
|
||||
navigate(ROUTES[route as RouteName], { replace: true });
|
||||
navigate(ROUTES[route as RouteNameEnum], { replace: true });
|
||||
} else {
|
||||
navigate(route, { replace: true });
|
||||
}
|
||||
@@ -38,7 +28,6 @@ export const useNavigation = () => {
|
||||
return {
|
||||
goTo,
|
||||
goBack,
|
||||
replace,
|
||||
ROUTES,
|
||||
replace
|
||||
};
|
||||
};
|
||||
@@ -1,9 +0,0 @@
|
||||
// import { createClient } from '@supabase/supabase-js';
|
||||
//
|
||||
// import { VITE_SUPABASE_ANON_KEY, VITE_SUPABASE_URL } from "@/shared/api";
|
||||
//
|
||||
// if (!VITE_SUPABASE_URL || !VITE_SUPABASE_ANON_KEY) {
|
||||
// throw new Error('Missing Supabase environment variables');
|
||||
// }
|
||||
|
||||
// export const supabase = createClient(VITE_SUPABASE_URL, VITE_SUPABASE_ANON_KEY);
|
||||
@@ -1,4 +1 @@
|
||||
export { useSuppliersSearch, SuppliersSearchProvider } from './context/SuppliersSearchContext';
|
||||
export { useAuth, AuthProvider } from './context/AuthContext';
|
||||
|
||||
export { ROUTES, type RouteType, type RouteName, getRoute, generatePath } from './routes'
|
||||
export { ROUTES, RouteNameEnum, type RouteType, type RouteName, getRoute, generatePath } from './routes/routes'
|
||||
|
||||
@@ -1,97 +0,0 @@
|
||||
import React from "react";
|
||||
|
||||
export type RouteType = {
|
||||
name: string;
|
||||
path: string;
|
||||
component: React.ComponentType<unknown>;
|
||||
exact?: boolean;
|
||||
isProtected?: boolean;
|
||||
isAuthOnly?: boolean;
|
||||
layout?: React.ComponentType<unknown>;
|
||||
children?: RouteType[];
|
||||
};
|
||||
|
||||
export const ROUTES = {
|
||||
HOME: '/',
|
||||
LOGIN: '/login',
|
||||
REGISTER: '/register',
|
||||
RESET_PASSWORD: '/reset-password',
|
||||
SUPPLIERS_SEARCH: '/suppliers-search',
|
||||
MAILINGS: '/mailings',
|
||||
SUPPLIERS: '/suppliers',
|
||||
VIEWING_REQUESTS: '/viewing-requests',
|
||||
} as const;
|
||||
|
||||
export type RouteName = keyof typeof ROUTES;
|
||||
|
||||
// export const routesConfig: RouteType[] = [
|
||||
// // Публичные роуты (не требуют аутентификации)
|
||||
// {
|
||||
// name: 'LOGIN',
|
||||
// path: ROUTES.LOGIN,
|
||||
// component: LoginPage,
|
||||
// exact: true,
|
||||
// isProtected: false,
|
||||
// },
|
||||
// {
|
||||
// name: 'REGISTER',
|
||||
// path: ROUTES.REGISTER,
|
||||
// component: RegisterPage,
|
||||
// exact: true,
|
||||
// isProtected: false,
|
||||
// },
|
||||
// {
|
||||
// name: 'RESET_PASSWORD',
|
||||
// path: ROUTES.RESET_PASSWORD,
|
||||
// component: ResetPasswordPage,
|
||||
// exact: true,
|
||||
// isProtected: false,
|
||||
// },
|
||||
//
|
||||
// // Защищенные роуты (требуют аутентификации)
|
||||
// {
|
||||
// name: 'HOME',
|
||||
// path: ROUTES.HOME,
|
||||
// component: HomePage,
|
||||
// exact: true,
|
||||
// isProtected: true,
|
||||
// },
|
||||
// {
|
||||
// name: 'SUPPLIERS_SEARCH',
|
||||
// path: ROUTES.SUPPLIERS_SEARCH,
|
||||
// component: SuppliersSearchPage,
|
||||
// exact: true,
|
||||
// isProtected: true,
|
||||
// },
|
||||
// {
|
||||
// name: 'MAILINGS',
|
||||
// path: ROUTES.MAILINGS,
|
||||
// component: MailingsPage,
|
||||
// exact: true,
|
||||
// isProtected: true,
|
||||
// },
|
||||
// {
|
||||
// name: 'SUPPLIERS',
|
||||
// path: ROUTES.SUPPLIERS,
|
||||
// component: SuppliersPage,
|
||||
// exact: true,
|
||||
// isProtected: true,
|
||||
// },
|
||||
// ];
|
||||
|
||||
// Вспомогательные функции для работы с роутами
|
||||
export const getRoute = (name: RouteName): string => ROUTES[name];
|
||||
|
||||
// export const getRouteConfig = (name: RouteName): RouteType | undefined =>
|
||||
// routesConfig.find(route => route.name === name);
|
||||
|
||||
// Функция для генерации пути с параметрами
|
||||
export const generatePath = (name: RouteName, params?: Record<string, string | number>): string => {
|
||||
let path = getRoute(name);
|
||||
if (params) {
|
||||
Object.entries(params).forEach(([key, value]) => {
|
||||
path = path.replace(`:${key}`, String(value));
|
||||
});
|
||||
}
|
||||
return path;
|
||||
};
|
||||
52
src/shared/model/routes/routes.ts
Normal file
52
src/shared/model/routes/routes.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import React from "react";
|
||||
|
||||
export type RouteType = {
|
||||
name: string;
|
||||
path: string;
|
||||
component: React.ComponentType<unknown>;
|
||||
exact?: boolean;
|
||||
isProtected?: boolean;
|
||||
isAuthOnly?: boolean;
|
||||
layout?: React.ComponentType<unknown>;
|
||||
children?: RouteType[];
|
||||
};
|
||||
|
||||
export const ROUTES = {
|
||||
HOME: '/',
|
||||
LOGIN: '/login',
|
||||
REGISTER: '/register',
|
||||
RESET_PASSWORD: '/reset-password',
|
||||
SUPPLIERS_SEARCH: '/suppliers-search',
|
||||
MAILINGS: '/mailings',
|
||||
SUPPLIERS: '/suppliers',
|
||||
VIEWING_REQUESTS: '/viewing-requests',
|
||||
};
|
||||
|
||||
export const enum RouteNameEnum {
|
||||
HOME = 'HOME',
|
||||
LOGIN = 'LOGIN',
|
||||
REGISTER = 'REGISTER',
|
||||
RESET_PASSWORD = 'RESET_PASSWORD',
|
||||
SUPPLIERS_SEARCH = 'SUPPLIERS_SEARCH',
|
||||
MAILINGS = 'MAILINGS',
|
||||
SUPPLIERS = 'SUPPLIERS',
|
||||
}
|
||||
|
||||
export type RouteName = keyof typeof ROUTES;
|
||||
|
||||
// Вспомогательные функции для работы с роутами
|
||||
export const getRoute = (name: RouteNameEnum): string => ROUTES[name];
|
||||
|
||||
// export const getRouteConfig = (name: RouteNameEnum): RouteType | undefined =>
|
||||
// routesConfig.find(route => route.name === name);
|
||||
|
||||
// Функция для генерации пути с параметрами
|
||||
export const generatePath = (name: RouteNameEnum, params?: Record<string, string | number>): string => {
|
||||
let path = getRoute(name);
|
||||
if (params) {
|
||||
Object.entries(params).forEach(([key, value]) => {
|
||||
path = path.replace(`:${key}`, String(value));
|
||||
});
|
||||
}
|
||||
return path;
|
||||
};
|
||||
@@ -1,8 +1,9 @@
|
||||
import { LogOut } from 'lucide-react';
|
||||
|
||||
import { useAuth } from "@/app/providers/AuthContext";
|
||||
import { authApi } from "@/shared/api";
|
||||
import { useNavigation } from "@/shared/lib/hooks/useNavigation";
|
||||
import { ROUTES, useAuth } from "@/shared/model";
|
||||
import { ROUTES } from "@/shared/model";
|
||||
import { ButtonVariant, UIButton } from "@/shared/ui";
|
||||
|
||||
export const SidebarFooter = () => {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { FC } from 'react';
|
||||
|
||||
import { useNavigation } from "@/shared/lib/hooks/useNavigation";
|
||||
import { RouteName, ROUTES } from "@/shared/model/routes";
|
||||
import { RouteName, ROUTES } from "@/shared/model";
|
||||
import { ButtonSize, ButtonVariant, UIButton } from "@/shared/ui";
|
||||
import { menuItems } from "@/widgets/sidebar/model/constants";
|
||||
|
||||
|
||||
Reference in New Issue
Block a user