рефакторинг структуры

This commit is contained in:
pavel
2025-12-20 16:40:13 +03:00
parent 637d24eaf6
commit 1206f65944
30 changed files with 103 additions and 159 deletions

View File

@@ -5,10 +5,12 @@ import { LoginPage, RegisterPage, ResetPasswordPage } from "@/pages/auth";
import { HomePage } from "@/pages/home"; import { HomePage } from "@/pages/home";
import { MailingById, MailingsPage } from "@/pages/mailings"; import { MailingById, MailingsPage } from "@/pages/mailings";
import { SuppliersSearchPage } from "@/pages/suppliers-search"; import { SuppliersSearchPage } from "@/pages/suppliers-search";
import { SuppliersSearchProvider } from "@/pages/suppliers-search/model/SuppliersSearchContext";
import { ViewingRequestById, ViewingRequestsPage } from "@/pages/viewing-requests"; import { ViewingRequestById, ViewingRequestsPage } from "@/pages/viewing-requests";
import { AuthProvider, SuppliersSearchProvider, useAuth } from "@/shared/model";
import { UILoader } from "@/shared/ui"; import { UILoader } from "@/shared/ui";
import { AuthProvider, useAuth } from "./providers/AuthContext";
const AppContent = () => { const AppContent = () => {
const { user, loading } = useAuth(); const { user, loading } = useAuth();

View File

@@ -2,11 +2,12 @@ import { Menu } from 'lucide-react';
import { useState } from 'react'; import { useState } from 'react';
import { Outlet } from 'react-router-dom'; import { Outlet } from 'react-router-dom';
import { useDevices } from "@/shared/lib";
import { Sidebar } from '@/widgets/sidebar' import { Sidebar } from '@/widgets/sidebar'
export const AppLayout = () => { export const AppLayout = () => {
const [isSidebarOpen, setIsSidebarOpen] = useState(true); const [isSidebarOpen, setIsSidebarOpen] = useState(false);
const { isMobile } = useDevices()
// Функция переключения состояния сайдбара // Функция переключения состояния сайдбара
const toggleSidebar = () => { const toggleSidebar = () => {
setIsSidebarOpen(!isSidebarOpen); setIsSidebarOpen(!isSidebarOpen);
@@ -21,11 +22,13 @@ export const AppLayout = () => {
<div className="min-h-screen bg-gray-50 flex"> <div className="min-h-screen bg-gray-50 flex">
<Sidebar isOpen={isSidebarOpen} onClose={closeSidebar} /> <Sidebar isOpen={isSidebarOpen} onClose={closeSidebar} />
<div className="flex-1 flex flex-col min-h-screen"> <div className="flex-1 flex flex-col">
{/* Кнопка в хедере только для мобильных */} {/* Кнопка в хедере только для мобильных */}
<header className="lg:hidden bg-white border-b border-gray-200 p-4"> {isMobile && (
<Menu className="w-6 h-6 cursor-pointer hover:bg-gray-200" onClick={() => toggleSidebar()}/> <header className="lg:hidden bg-white border-b border-gray-200 p-4">
</header> <Menu className="w-6 h-6 cursor-pointer hover:bg-gray-200" onClick={() => toggleSidebar()}/>
</header>
)}
<main className="flex-1 p-6 lg:p-8"> <main className="flex-1 p-6 lg:p-8">
<Outlet /> <Outlet />

View File

@@ -0,0 +1 @@
export { MailingItem } from './ui/MailingItem'

View File

@@ -9,7 +9,7 @@ interface IProps {
onSelectItem: (id: string | number) => void onSelectItem: (id: string | number) => void
} }
export const MailingsItem = (props: IProps) => { export const MailingItem = (props: IProps) => {
const { item, onSelectItem } = props; const { item, onSelectItem } = props;
const statusIcon = useMemo(() => { const statusIcon = useMemo(() => {

View File

@@ -0,0 +1 @@
export { SuppliersTable } from './ui/SuppliersTable'

View File

@@ -1,7 +1,7 @@
import { ArrowBigLeft } from "lucide-react"; import { ArrowBigLeft } from "lucide-react";
import { useSuppliersSearch } from "@/pages/suppliers-search/model/SuppliersSearchContext";
import { ISupplier } from "@/shared/api"; import { ISupplier } from "@/shared/api";
import { useSuppliersSearch } from "@/shared/model";
import { import {
ButtonSize, ButtonSize,
ButtonVariant, ButtonVariant,

View File

@@ -0,0 +1 @@
export { ViewingRequestItem } from './ui/ViewingRequestItem'

View File

@@ -0,0 +1 @@
export { RequestInputForm } from './ui/RequestInputForm'

View File

@@ -1,8 +1,9 @@
import { Send } from 'lucide-react'; import { Send } from 'lucide-react';
import { useState } from "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 { suppliersSearchApi } from '@/shared/api';
import { useAuth, useSuppliersSearch } from "@/shared/model";
import { import {
ButtonVariant, ButtonVariant,
NotificationAppearance, NotificationAppearance,

View File

@@ -0,0 +1 @@
export { TechnicalSpecification } from './ui/TechnicalSpecification'

View File

@@ -1,8 +1,9 @@
import { ArrowBigLeft, Search } from 'lucide-react'; import { ArrowBigLeft, Search } from 'lucide-react';
import { useState } from '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 { suppliersSearchApi } from '@/shared/api';
import { useAuth, useSuppliersSearch } from "@/shared/model";
import { import {
ButtonVariant, ButtonVariant,
NotificationAppearance, NotificationAppearance,

View File

@@ -1,8 +1,9 @@
import { useState } from 'react'; import { useState } from 'react';
import { Link, useNavigate } from "react-router"; import { Link, useNavigate } from "react-router";
import { useAuth } from "@/app/providers/AuthContext";
import { authApi } from "@/shared/api/endpoints/auth/auth-api"; 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"; import { ButtonType, InputType, UIButton, UIInput } from "@/shared/ui";
export const LoginPage = () => { export const LoginPage = () => {

View File

@@ -2,8 +2,9 @@ import { useState } from 'react';
import { Link } from "react-router"; import { Link } from "react-router";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import { useAuth } from "@/app/providers/AuthContext";
import { authApi } from '@/shared/api'; 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'; import { ButtonType, InputType, UIButton, UIInput, UIPhoneNumber } from '@/shared/ui';
export const RegisterPage = () => { export const RegisterPage = () => {

View File

@@ -3,7 +3,7 @@ import { useParams } from "react-router";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import { IMailingInfo, ISupplier, mailingApi } from "@/shared/api"; import { IMailingInfo, ISupplier, mailingApi } from "@/shared/api";
import { ROUTES } from "@/shared/model/routes"; import { ROUTES } from "@/shared/model";
import { import {
ButtonVariant, Column, ButtonVariant, Column,
NotificationAppearance, NotificationAppearance,

View File

@@ -1,7 +1,7 @@
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { useNavigate } from "react-router-dom"; 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 { IMailingItem, mailingApi } from "@/shared/api";
import { ROUTES } from "@/shared/model"; import { ROUTES } from "@/shared/model";
import { import {
@@ -62,7 +62,7 @@ export const MailingsPage = () => {
<div className="flex flex-col gap-4"> <div className="flex flex-col gap-4">
{mailingList.length > 0 && ( {mailingList.length > 0 && (
mailingList.map((item) => ( mailingList.map((item) => (
<MailingsItem <MailingItem
key={item.request_id} key={item.request_id}
item={item} item={item}
onSelectItem={() => navigate(`${ROUTES.MAILINGS}/${item.request_id}`)} onSelectItem={() => navigate(`${ROUTES.MAILINGS}/${item.request_id}`)}

View File

@@ -20,7 +20,7 @@ const SuppliersSearchContext = createContext<ISuppliersSearchContext>({
export const useSuppliersSearch = () => { export const useSuppliersSearch = () => {
const context = useContext(SuppliersSearchContext); const context = useContext(SuppliersSearchContext);
if (!context) { if (!context) {
throw new Error('useSuppliersSearch must be used within AuthProvider'); throw new Error('useSuppliersSearch must be used within AuthContext');
} }
return context; return context;
}; };

View File

@@ -1,12 +1,13 @@
import { useCallback, useState } from 'react'; import { useCallback, useState } from 'react';
import { StagesEnum } from "@/pages/suppliers-search"; import { SuppliersTable } from "@/entities/supplier";
import { RequestInputForm } from "@/pages/suppliers-search/ui/RequestInputForm"; import { RequestInputForm } from "@/features/suppliers-search/request-input";
import { SuppliersTable } from "@/pages/suppliers-search/ui/SuppliersTable"; import { TechnicalSpecification } from "@/features/suppliers-search/technical-specification";
import { TechnicalSpecification } from "@/pages/suppliers-search/ui/TechnicalSpecification"; import { useSuppliersSearch } from "@/pages/suppliers-search/model/SuppliersSearchContext";
import { useSuppliersSearch } from "@/shared/model";
import { UIStepper } from "@/shared/ui"; import { UIStepper } from "@/shared/ui";
import { StagesEnum } from "../model/types";
export const SuppliersSearchPage = () => { export const SuppliersSearchPage = () => {
const [stage, setStage] = useState<StagesEnum>(StagesEnum.Input); const [stage, setStage] = useState<StagesEnum>(StagesEnum.Input);
const { suppliers, updateSuppliersSearchContext } = useSuppliersSearch(); const { suppliers, updateSuppliersSearchContext } = useSuppliersSearch();

View File

@@ -1,3 +1,2 @@
export { ViewingRequestsPage } from './ui/ViewingRequests' export { ViewingRequestsPage } from './ui/ViewingRequests'
export { ViewingRequestById } from './ui/ViewingRequestById' export { ViewingRequestById } from './ui/ViewingRequestById'
export { ViewingRequestItem } from './ui/ViewingRequestItem'

View File

@@ -3,7 +3,6 @@ import { useEffect, useState } from "react";
import { useParams } from "react-router"; import { useParams } from "react-router";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import { IMailingInfo, ISupplier, mailingApi } from "@/shared/api"; import { IMailingInfo, ISupplier, mailingApi } from "@/shared/api";
import { import {
ButtonSize, ButtonSize,
@@ -103,7 +102,6 @@ export const ViewingRequestById = () => {
value={message} value={message}
placeholder="Текст сообщения" placeholder="Текст сообщения"
onChange={(val) => setMessage(val)} onChange={(val) => setMessage(val)}
minCount={10}
minHeight={500} minHeight={500}
/> />
</div> </div>

View File

@@ -1,16 +1,15 @@
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import { ViewingRequestItem } from "@/entities/viewing-requests";
import { IMailingItem, mailingApi } from "@/shared/api"; import { IMailingItem, mailingApi } from "@/shared/api";
import { ROUTES } from "@/shared/model/routes"; import { ROUTES } from "@/shared/model";
import { import {
NotificationAppearance, NotificationAppearance,
UILoader, UILoader,
UINotification, UINotification,
} from "@/shared/ui"; } from "@/shared/ui";
import { ViewingRequestItem } from "./ViewingRequestItem";
export const ViewingRequestsPage = () => { export const ViewingRequestsPage = () => {
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [error, setError] = useState(''); const [error, setError] = useState('');

View File

@@ -10,11 +10,11 @@ export const useDevices = (): UseDevicesType => {
const [ isDesktop, setIsDesktop ] = useState<boolean>(window.innerWidth > 880) const [ isDesktop, setIsDesktop ] = useState<boolean>(window.innerWidth > 880)
const updateIsMobile = () => { const updateIsMobile = () => {
setIsMobile(window.innerWidth < 880) setIsMobile(window.innerWidth < 1024)
}; };
const updateIsDesktop = () => { const updateIsDesktop = () => {
setIsDesktop(window.innerWidth >= 880) setIsDesktop(window.innerWidth >= 1024)
}; };
useEffect(() => { useEffect(() => {

View File

@@ -1,23 +1,13 @@
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
export const ROUTES = { import { ROUTES, RouteNameEnum } from "@/shared/model";
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;
export const useNavigation = () => { export const useNavigation = () => {
const navigate = useNavigate(); const navigate = useNavigate();
const goTo = (route: RouteName | string) => { const goTo = (route: RouteNameEnum | string) => {
if (route in ROUTES) { if (route in ROUTES) {
navigate(ROUTES[route as RouteName]); navigate(ROUTES[route as RouteNameEnum]);
} else { } else {
navigate(route); navigate(route);
} }
@@ -27,9 +17,9 @@ export const useNavigation = () => {
navigate(-1); navigate(-1);
}; };
const replace = (route: RouteName | string) => { const replace = (route: RouteNameEnum | string) => {
if (route in ROUTES) { if (route in ROUTES) {
navigate(ROUTES[route as RouteName], { replace: true }); navigate(ROUTES[route as RouteNameEnum], { replace: true });
} else { } else {
navigate(route, { replace: true }); navigate(route, { replace: true });
} }
@@ -38,7 +28,6 @@ export const useNavigation = () => {
return { return {
goTo, goTo,
goBack, goBack,
replace, replace
ROUTES,
}; };
}; };

View File

@@ -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);

View File

@@ -1,4 +1 @@
export { useSuppliersSearch, SuppliersSearchProvider } from './context/SuppliersSearchContext'; export { ROUTES, RouteNameEnum, type RouteType, type RouteName, getRoute, generatePath } from './routes/routes'
export { useAuth, AuthProvider } from './context/AuthContext';
export { ROUTES, type RouteType, type RouteName, getRoute, generatePath } from './routes'

View File

@@ -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;
};

View 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;
};

View File

@@ -1,8 +1,9 @@
import { LogOut } from 'lucide-react'; import { LogOut } from 'lucide-react';
import { useAuth } from "@/app/providers/AuthContext";
import { authApi } from "@/shared/api"; import { authApi } from "@/shared/api";
import { useNavigation } from "@/shared/lib/hooks/useNavigation"; import { useNavigation } from "@/shared/lib/hooks/useNavigation";
import { ROUTES, useAuth } from "@/shared/model"; import { ROUTES } from "@/shared/model";
import { ButtonVariant, UIButton } from "@/shared/ui"; import { ButtonVariant, UIButton } from "@/shared/ui";
export const SidebarFooter = () => { export const SidebarFooter = () => {

View File

@@ -1,7 +1,7 @@
import { FC } from 'react'; import { FC } from 'react';
import { useNavigation } from "@/shared/lib/hooks/useNavigation"; 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 { ButtonSize, ButtonVariant, UIButton } from "@/shared/ui";
import { menuItems } from "@/widgets/sidebar/model/constants"; import { menuItems } from "@/widgets/sidebar/model/constants";