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

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 { 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();

View File

@@ -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">
{/* Кнопка в хедере только для мобильных */}
<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>
{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 />

View File

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

View File

@@ -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(() => {

View File

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

View File

@@ -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,

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 { 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,

View File

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

View File

@@ -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,

View File

@@ -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 = () => {

View File

@@ -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 = () => {

View File

@@ -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,

View File

@@ -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}`)}

View File

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

View File

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

View File

@@ -1,3 +1,2 @@
export { ViewingRequestsPage } from './ui/ViewingRequests'
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 { 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>

View File

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

View File

@@ -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(() => {

View File

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

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 { 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'

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 { 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 = () => {

View File

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