commit 99faa784346df3d71c3128e6543cf953adbeb1e6 Author: apheyhys Date: Wed Feb 11 20:49:50 2026 +0300 init commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..132b0fb --- /dev/null +++ b/.gitignore @@ -0,0 +1,39 @@ +# Python +__pycache__/ +*.py[cod] +*$py.class +*.so +.Python +env/ +venv/ +.env +.venv +poetry.lock +alembic/versions/ +unloading.db + +# Backend +backend/backend_transfer_tdms_tcs/__pycache__/ +backend/backend_transfer_tdms_tcs/*.db +backend/backend_transfer_tdms_tcs/alembic/versions/ + +# Frontend +frontend/node_modules/ +frontend/.next/ +frontend/out/ +frontend/build +frontend/.env.local +frontend/.env.*.local +frontend/next-env.d.ts +frontend/package-lock.json + +# IDE +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# OS +.DS_Store +Thumbs.db \ No newline at end of file diff --git a/backend/backend_transfer_tdms_tcs/README.md b/backend/backend_transfer_tdms_tcs/README.md new file mode 100644 index 0000000..e69de29 diff --git a/backend/backend_transfer_tdms_tcs/alembic.ini b/backend/backend_transfer_tdms_tcs/alembic.ini new file mode 100644 index 0000000..ea68105 --- /dev/null +++ b/backend/backend_transfer_tdms_tcs/alembic.ini @@ -0,0 +1,46 @@ +[alembic] +script_location = alembic +prepend_sys_path = . +version_path_separator = os + +sqlalchemy.url = sqlite:///./unloading.db + +[post_write_hooks] +hooks = black +black.type = console_scripts +black.entrypoint = black +black.options = -l 88 + +[loggers] +keys = root,sqlalchemy,alembic + +[handlers] +keys = console + +[formatters] +keys = generic + +[logger_root] +level = WARN +handlers = console +qualname = + +[logger_sqlalchemy] +level = WARN +handlers = +qualname = sqlalchemy.engine + +[logger_alembic] +level = INFO +handlers = +qualname = alembic + +[handler_console] +class = StreamHandler +args = (sys.stderr,) +level = NOTSET +formatter = generic + +[formatter_generic] +format = %(levelname)-5.5s [%(name)s] %(message)s +datefmt = %H:%M:%S \ No newline at end of file diff --git a/backend/backend_transfer_tdms_tcs/app/__init__.py b/backend/backend_transfer_tdms_tcs/app/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/backend_transfer_tdms_tcs/app/admin.py b/backend/backend_transfer_tdms_tcs/app/admin.py new file mode 100644 index 0000000..6e0de75 --- /dev/null +++ b/backend/backend_transfer_tdms_tcs/app/admin.py @@ -0,0 +1,83 @@ +from sqladmin import Admin, ModelView +from sqladmin.authentication import AuthenticationBackend +from fastapi import Request +from wtforms import TextAreaField +from wtforms.widgets import TextArea + +from .database import engine +from .models import Unloading, Product, UnloadCheck, TestLoad, TestCheck, ProdTransfer + +class JSONTextAreaWidget(TextArea): + def __call__(self, field, **kwargs): + kwargs["class"] = "json-textarea" + return super().__call__(field, **kwargs) + +class UnloadingAdmin(ModelView, model=Unloading): + column_list = [Unloading.guid, Unloading.name, Unloading.created_at, Unloading.computer_name] + column_searchable_list = [Unloading.name, Unloading.guid, Unloading.computer_name] + column_sortable_list = [Unloading.created_at, Unloading.unloading_time_seconds] + form_overrides = {"warnings": TextAreaField} + form_widget_args = {"warnings": {"widget": JSONTextAreaWidget(), "rows": 5}} + + can_create = True + can_edit = True + can_delete = True + can_view_details = True + +class ProductAdmin(ModelView, model=Product): + column_list = [Product.guid, Product.name] + column_searchable_list = [Product.name, Product.guid] + + can_create = False # Продукты создаются через триггер + can_edit = True + can_delete = False + can_view_details = True + +class UnloadCheckAdmin(ModelView, model=UnloadCheck): + column_list = [UnloadCheck.id, UnloadCheck.product_guid, UnloadCheck.fio, UnloadCheck.date, UnloadCheck.is_completed] + column_searchable_list = [UnloadCheck.fio, UnloadCheck.product_guid] + +class TestLoadAdmin(ModelView, model=TestLoad): + column_list = [TestLoad.id, TestLoad.product_guid, TestLoad.fio, TestLoad.date, TestLoad.is_completed] + +class TestCheckAdmin(ModelView, model=TestCheck): + column_list = [TestCheck.id, TestCheck.product_guid, TestCheck.fio, TestCheck.date, TestCheck.is_completed] + +class ProdTransferAdmin(ModelView, model=ProdTransfer): + column_list = [ProdTransfer.id, ProdTransfer.product_guid, ProdTransfer.fio, ProdTransfer.date, ProdTransfer.is_completed] + +# Простая аутентификация (можно заменить на более сложную) +class AdminAuth(AuthenticationBackend): + async def login(self, request: Request) -> bool: + form = await request.form() + username = form.get("username") + password = form.get("password") + + # Простая проверка (в production используйте безопасную аутентификацию) + if username == "admin" and password == "admin": + request.session.update({"token": "admin-token"}) + return True + return False + + async def logout(self, request: Request) -> bool: + request.session.clear() + return True + + async def authenticate(self, request: Request) -> bool: + token = request.session.get("token") + if token == "admin-token": + return True + return False + +def setup_admin(app): + authentication_backend = AdminAuth(secret_key="secret-key") + admin = Admin(app=app, engine=engine, authentication_backend=authentication_backend) + + admin.add_view(UnloadingAdmin) + admin.add_view(ProductAdmin) + admin.add_view(UnloadCheckAdmin) + admin.add_view(TestLoadAdmin) + admin.add_view(TestCheckAdmin) + admin.add_view(ProdTransferAdmin) + + return admin \ No newline at end of file diff --git a/backend/backend_transfer_tdms_tcs/app/crud.py b/backend/backend_transfer_tdms_tcs/app/crud.py new file mode 100644 index 0000000..bfa363e --- /dev/null +++ b/backend/backend_transfer_tdms_tcs/app/crud.py @@ -0,0 +1,75 @@ +from sqlalchemy.orm import Session +from . import models, schemas +from typing import Optional, List +import uuid + +def create_unloading(db: Session, unloading: schemas.UnloadingCreate): + db_unloading = models.Unloading( + guid=unloading.guid or str(uuid.uuid4()).upper(), + name=unloading.name, + unloading_time_seconds=unloading.unloading_time_seconds, + warnings=unloading.warnings, + row_count=unloading.row_count, + file_count=unloading.file_count, + computer_name=unloading.computer_name + ) + db.add(db_unloading) + db.commit() + db.refresh(db_unloading) + return db_unloading + +def get_unloadings(db: Session, skip: int = 0, limit: int = 100): + return db.query(models.Unloading).offset(skip).limit(limit).all() + +def get_unloading(db: Session, guid: str): + return db.query(models.Unloading).filter(models.Unloading.guid == guid).first() + +def get_products(db: Session, skip: int = 0, limit: int = 100): + return db.query(models.Product).offset(skip).limit(limit).all() + +def get_product(db: Session, guid: str): + return db.query(models.Product).filter(models.Product.guid == guid).first() + +def add_unload_check(db: Session, product_guid: str, stage: schemas.UnloadCheckCreate): + db_stage = models.UnloadCheck( + product_guid=product_guid, + fio=stage.fio, + is_completed=stage.is_completed + ) + db.add(db_stage) + db.commit() + db.refresh(db_stage) + return db_stage + +def add_test_load(db: Session, product_guid: str, stage: schemas.TestLoadCreate): + db_stage = models.TestLoad( + product_guid=product_guid, + fio=stage.fio, + is_completed=stage.is_completed + ) + db.add(db_stage) + db.commit() + db.refresh(db_stage) + return db_stage + +def add_test_check(db: Session, product_guid: str, stage: schemas.TestCheckCreate): + db_stage = models.TestCheck( + product_guid=product_guid, + fio=stage.fio, + is_completed=stage.is_completed + ) + db.add(db_stage) + db.commit() + db.refresh(db_stage) + return db_stage + +def add_prod_transfer(db: Session, product_guid: str, stage: schemas.ProdTransferCreate): + db_stage = models.ProdTransfer( + product_guid=product_guid, + fio=stage.fio, + is_completed=stage.is_completed + ) + db.add(db_stage) + db.commit() + db.refresh(db_stage) + return db_stage \ No newline at end of file diff --git a/backend/backend_transfer_tdms_tcs/app/database.py b/backend/backend_transfer_tdms_tcs/app/database.py new file mode 100644 index 0000000..48ed28e --- /dev/null +++ b/backend/backend_transfer_tdms_tcs/app/database.py @@ -0,0 +1,23 @@ +from sqlalchemy import create_engine, event, DDL +from sqlalchemy.ext.declarative import declarative_base +from sqlalchemy.orm import sessionmaker +from sqlalchemy.pool import StaticPool + +SQLALCHEMY_DATABASE_URL = "sqlite:///./unloading.db" + +engine = create_engine( + SQLALCHEMY_DATABASE_URL, + connect_args={"check_same_thread": False}, + poolclass=StaticPool +) + +SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) + +Base = declarative_base() + +def get_db(): + db = SessionLocal() + try: + yield db + finally: + db.close() \ No newline at end of file diff --git a/backend/backend_transfer_tdms_tcs/app/main.py b/backend/backend_transfer_tdms_tcs/app/main.py new file mode 100644 index 0000000..6554a61 --- /dev/null +++ b/backend/backend_transfer_tdms_tcs/app/main.py @@ -0,0 +1,72 @@ +from contextlib import asynccontextmanager # ДОБАВЬТЕ ЭТОТ ИМПОРТ! +from fastapi import FastAPI, Depends +from sqlalchemy.orm import Session +from sqlalchemy import text + +from .database import engine, Base, get_db +from .models import * +from .routers import unloadings, products +from .admin import setup_admin + +@asynccontextmanager +async def lifespan(app: FastAPI): + # Создание таблиц при старте + Base.metadata.create_all(bind=engine) + + # Создание триггера + # Создание триггера + from sqlalchemy import text + with engine.begin() as conn: + conn.execute(text(""" + CREATE TRIGGER IF NOT EXISTS insert_product_from_unloading + AFTER INSERT ON unloading + FOR EACH ROW + WHEN NOT EXISTS (SELECT 1 FROM product WHERE guid = NEW.guid) + BEGIN + INSERT INTO product (guid, name) + VALUES (NEW.guid, NEW.name); + + -- Обновляем product_guid после создания продукта + UPDATE unloading + SET product_guid = NEW.guid + WHERE guid = NEW.guid; + END; + """)) + + yield + + # Очистка при завершении + pass + +app = FastAPI( + title="Unloading System API", + description="Система управления выгрузками и продуктами", + version="1.0.0", + lifespan=lifespan +) + +# Настройка админки +admin = setup_admin(app) + +# Подключение роутеров +app.include_router(unloadings.router) +app.include_router(products.router) + +@app.get("/") +async def root(): + return { + "message": "Unloading System API", + "docs": "/docs", + "admin": "/admin" + } + +@app.get("/test-db") +def test_db(db: Session = Depends(get_db)): + from sqlalchemy import text + result = db.execute(text("SELECT name FROM sqlite_master WHERE type='table'")) + tables = [row[0] for row in result] + return {"tables": tables} + +if __name__ == "__main__": + import uvicorn + uvicorn.run(app, host="0.0.0.0", port=8000) \ No newline at end of file diff --git a/backend/backend_transfer_tdms_tcs/app/models.py b/backend/backend_transfer_tdms_tcs/app/models.py new file mode 100644 index 0000000..ff6011a --- /dev/null +++ b/backend/backend_transfer_tdms_tcs/app/models.py @@ -0,0 +1,65 @@ +from sqlalchemy import Column, String, Integer, DateTime, JSON, ForeignKey, Boolean +from sqlalchemy.sql import func +from sqlalchemy.orm import relationship, declared_attr +import uuid +from .database import Base + +class Unloading(Base): + __tablename__ = "unloading" + + guid = Column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()).upper()) + name = Column(String(500), nullable=False) + unloading_time_seconds = Column(Integer, nullable=False) + warnings = Column(JSON, default={}) + row_count = Column(Integer, nullable=False) + created_at = Column(DateTime(timezone=True), server_default=func.now()) + file_count = Column(Integer, nullable=False) + computer_name = Column(String(100), nullable=True) + + # ДОБАВЬТЕ ЭТОТ ВНЕШНИЙ КЛЮЧ + product_guid = Column(String(36), ForeignKey("product.guid"), unique=True) + + # Исправленная связь + product = relationship("Product", back_populates="unloading", uselist=False) + +class Product(Base): + __tablename__ = "product" + + guid = Column(String(36), primary_key=True, index=True) + name = Column(String(500), nullable=False) + + # Связь остается без изменений + unloading = relationship("Unloading", back_populates="product", uselist=False) + + unload_checks = relationship("UnloadCheck", back_populates="product", cascade="all, delete-orphan") + test_loads = relationship("TestLoad", back_populates="product", cascade="all, delete-orphan") + test_checks = relationship("TestCheck", back_populates="product", cascade="all, delete-orphan") + prod_transfers = relationship("ProdTransfer", back_populates="product", cascade="all, delete-orphan") + +class BaseStage(Base): + __abstract__ = True + + id = Column(Integer, primary_key=True, index=True) + fio = Column(String(200), nullable=False) + date = Column(DateTime(timezone=True), server_default=func.now()) + is_completed = Column(Boolean, default=False) + + @declared_attr + def product_guid(cls): + return Column(String(36), ForeignKey("product.guid")) + +class UnloadCheck(BaseStage): + __tablename__ = "unload_check" + product = relationship("Product", back_populates="unload_checks") + +class TestLoad(BaseStage): + __tablename__ = "test_load" + product = relationship("Product", back_populates="test_loads") + +class TestCheck(BaseStage): + __tablename__ = "test_check" + product = relationship("Product", back_populates="test_checks") + +class ProdTransfer(BaseStage): + __tablename__ = "prod_transfer" + product = relationship("Product", back_populates="prod_transfers") \ No newline at end of file diff --git a/backend/backend_transfer_tdms_tcs/app/routers/__init__.py b/backend/backend_transfer_tdms_tcs/app/routers/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/backend_transfer_tdms_tcs/app/routers/products.py b/backend/backend_transfer_tdms_tcs/app/routers/products.py new file mode 100644 index 0000000..e11aa82 --- /dev/null +++ b/backend/backend_transfer_tdms_tcs/app/routers/products.py @@ -0,0 +1,34 @@ +from fastapi import APIRouter, Depends, HTTPException +from sqlalchemy.orm import Session +from typing import List +from .. import crud, schemas +from ..database import get_db + +router = APIRouter(prefix="/products", tags=["products"]) + +@router.get("/", response_model=List[schemas.Product]) +def read_products(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)): + return crud.get_products(db, skip=skip, limit=limit) + +@router.get("/{guid}", response_model=schemas.Product) +def read_product(guid: str, db: Session = Depends(get_db)): + db_product = crud.get_product(db, guid=guid) + if db_product is None: + raise HTTPException(status_code=404, detail="Product not found") + return db_product + +@router.post("/{guid}/unload-check/", response_model=schemas.Stage) +def create_unload_check(guid: str, stage: schemas.UnloadCheckCreate, db: Session = Depends(get_db)): + return crud.add_unload_check(db, product_guid=guid, stage=stage) + +@router.post("/{guid}/test-load/", response_model=schemas.Stage) +def create_test_load(guid: str, stage: schemas.TestLoadCreate, db: Session = Depends(get_db)): + return crud.add_test_load(db, product_guid=guid, stage=stage) + +@router.post("/{guid}/test-check/", response_model=schemas.Stage) +def create_test_check(guid: str, stage: schemas.TestCheckCreate, db: Session = Depends(get_db)): + return crud.add_test_check(db, product_guid=guid, stage=stage) + +@router.post("/{guid}/prod-transfer/", response_model=schemas.Stage) +def create_prod_transfer(guid: str, stage: schemas.ProdTransferCreate, db: Session = Depends(get_db)): + return crud.add_prod_transfer(db, product_guid=guid, stage=stage) \ No newline at end of file diff --git a/backend/backend_transfer_tdms_tcs/app/routers/unloadings.py b/backend/backend_transfer_tdms_tcs/app/routers/unloadings.py new file mode 100644 index 0000000..761885d --- /dev/null +++ b/backend/backend_transfer_tdms_tcs/app/routers/unloadings.py @@ -0,0 +1,23 @@ +from fastapi import APIRouter, Depends, HTTPException +from sqlalchemy.orm import Session +from typing import List +from .. import crud, schemas +from ..database import get_db + +router = APIRouter(prefix="/unloadings", tags=["unloadings"]) + +@router.post("/", response_model=schemas.Unloading) +def create_unloading(unloading: schemas.UnloadingCreate, db: Session = Depends(get_db)): + return crud.create_unloading(db=db, unloading=unloading) + +@router.get("/", response_model=List[schemas.Unloading]) +def read_unloadings(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)): + unloadings = crud.get_unloadings(db, skip=skip, limit=limit) + return unloadings + +@router.get("/{guid}", response_model=schemas.Unloading) +def read_unloading(guid: str, db: Session = Depends(get_db)): + db_unloading = crud.get_unloading(db, guid=guid) + if db_unloading is None: + raise HTTPException(status_code=404, detail="Unloading not found") + return db_unloading \ No newline at end of file diff --git a/backend/backend_transfer_tdms_tcs/app/schemas.py b/backend/backend_transfer_tdms_tcs/app/schemas.py new file mode 100644 index 0000000..073183a --- /dev/null +++ b/backend/backend_transfer_tdms_tcs/app/schemas.py @@ -0,0 +1,59 @@ +from pydantic import BaseModel +from datetime import datetime +from typing import Optional, Dict, List +import uuid + +class UnloadingBase(BaseModel): + name: str + unloading_time_seconds: int + warnings: Optional[Dict] = {} + row_count: int + file_count: int + computer_name: Optional[str] = None + +class UnloadingCreate(UnloadingBase): + guid: Optional[str] = None + +class Unloading(UnloadingBase): + guid: str + created_at: datetime + + class Config: + from_attributes = True + +class StageBase(BaseModel): + fio: str + is_completed: bool = False + +class UnloadCheckCreate(StageBase): + pass + +class TestLoadCreate(StageBase): + pass + +class TestCheckCreate(StageBase): + pass + +class ProdTransferCreate(StageBase): + pass + +class Stage(StageBase): + id: int + date: datetime + product_guid: str + + class Config: + from_attributes = True + +class ProductBase(BaseModel): + name: str + +class Product(ProductBase): + guid: str + unload_checks: List[Stage] = [] + test_loads: List[Stage] = [] + test_checks: List[Stage] = [] + prod_transfers: List[Stage] = [] + + class Config: + from_attributes = True \ No newline at end of file diff --git a/backend/backend_transfer_tdms_tcs/pyproject.toml b/backend/backend_transfer_tdms_tcs/pyproject.toml new file mode 100644 index 0000000..0db5b5c --- /dev/null +++ b/backend/backend_transfer_tdms_tcs/pyproject.toml @@ -0,0 +1,26 @@ +[tool.poetry] +name = "fastapi-unloading-app" +version = "0.1.0" +package-mode = false +description = "FastAPI application for unloading management" +authors = ["Your Name "] + +[tool.poetry.dependencies] +python = "^3.9" +fastapi = "^0.104.0" +sqlalchemy = "1.4.42" +databases = "0.8.0" +aiosqlite = "^0.19.0" +sqladmin = "^0.15.0" +jinja2 = "^3.1.0" +python-multipart = "^0.0.6" +uvicorn = {extras = ["standard"], version = "^0.24.0"} +itsdangerous = "^2.2.0" + +[tool.poetry.group.dev.dependencies] +alembic = "^1.12.0" +black = "^23.11.0" + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" \ No newline at end of file diff --git a/backend/backend_transfer_tdms_tcs/tests/__init__.py b/backend/backend_transfer_tdms_tcs/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/frontend/.gitignore b/frontend/.gitignore new file mode 100644 index 0000000..3c3629e --- /dev/null +++ b/frontend/.gitignore @@ -0,0 +1 @@ +node_modules diff --git a/frontend/app/layout.tsx b/frontend/app/layout.tsx new file mode 100644 index 0000000..1b407cb --- /dev/null +++ b/frontend/app/layout.tsx @@ -0,0 +1,21 @@ +// src/app/layout.tsx +import type { Metadata } from 'next' +import { Inter } from 'next/font/google' +import { Provider } from '@/components/ui/provider' + +const inter = Inter({ subsets: ['latin'] }) + +export const metadata: Metadata = { + title: 'My Chakra App', + description: 'Приложение на Next.js с Chakra UI', +} + +export default function RootLayout({ children }: { children: React.ReactNode }) { + return ( + + + {children} + + + ) +} \ No newline at end of file diff --git a/frontend/app/page.tsx b/frontend/app/page.tsx new file mode 100644 index 0000000..d9fdffa --- /dev/null +++ b/frontend/app/page.tsx @@ -0,0 +1,28 @@ +// src/app/page.tsx +import { Button, Container, Heading, Text, VStack } from '@chakra-ui/react' +import LineBox from '@/components/LineBox' + +export default function Home() { + return ( + + + + Добро пожаловать в Chakra UI + Next.js + + {/* + Приложение настроено по официальной документации + */} + {/* */} + + + + + ) +} \ No newline at end of file diff --git a/frontend/package.json b/frontend/package.json new file mode 100644 index 0000000..300fe97 --- /dev/null +++ b/frontend/package.json @@ -0,0 +1,24 @@ +{ + "dependencies": { + "@chakra-ui/react": "^3.32.0", + "@emotion/react": "^11.14.0", + "@emotion/styled": "^11.14.1", + "framer-motion": "^12.31.0", + "next": "^16.1.6", + "next-themes": "^0.4.6", + "react": "^19.2.4", + "react-dom": "^19.2.4", + "react-icons": "^5.5.0" + }, + "scripts": { + "dev": "next dev --webpack", + "build": "next build --webpack", + "start": "next start", + "lint": "next lint" + }, + "devDependencies": { + "@types/node": "25.2.0", + "@types/react": "19.2.11", + "typescript": "5.9.3" + } +} diff --git a/frontend/src/components/LineBox.tsx b/frontend/src/components/LineBox.tsx new file mode 100644 index 0000000..49335a9 --- /dev/null +++ b/frontend/src/components/LineBox.tsx @@ -0,0 +1,54 @@ +// src/components/CustomBox.tsx +import { Box, BoxProps, Heading, Text, Button, HStack } from '@chakra-ui/react' + +interface CustomBoxProps extends BoxProps { + title?: string + description?: string + buttonText?: string + onButtonClick?: () => void +} + +export default function CustomBox({ + title = "Заголовок по умолчанию", + description = "Описание по умолчанию", + buttonText = "Кнопка", + onButtonClick, + children, + ...rest +}: CustomBoxProps) { + return ( + + + {description && ( + + {description} + + )} + test + + {/* Дочерние элементы (если переданы) */} + {/* {children} + + // {buttonText && ( + // + // )} */} + + + + ) +} \ No newline at end of file diff --git a/frontend/src/components/ui/color-mode.jsx b/frontend/src/components/ui/color-mode.jsx new file mode 100644 index 0000000..a3029df --- /dev/null +++ b/frontend/src/components/ui/color-mode.jsx @@ -0,0 +1,90 @@ +'use client' + +import { ClientOnly, IconButton, Skeleton, Span } from '@chakra-ui/react' +import { ThemeProvider, useTheme } from 'next-themes' + +import * as React from 'react' +import { LuMoon, LuSun } from 'react-icons/lu' + +export function ColorModeProvider(props) { + return ( + + ) +} + +export function useColorMode() { + const { resolvedTheme, setTheme, forcedTheme } = useTheme() + const colorMode = forcedTheme || resolvedTheme + const toggleColorMode = () => { + setTheme(resolvedTheme === 'dark' ? 'light' : 'dark') + } + return { + colorMode: colorMode, + setColorMode: setTheme, + toggleColorMode, + } +} + +export function useColorModeValue(light, dark) { + const { colorMode } = useColorMode() + return colorMode === 'dark' ? dark : light +} + +export function ColorModeIcon() { + const { colorMode } = useColorMode() + return colorMode === 'dark' ? : +} + +export const ColorModeButton = React.forwardRef( + function ColorModeButton(props, ref) { + const { toggleColorMode } = useColorMode() + return ( + }> + + + + + ) + }, +) + +export const LightMode = React.forwardRef(function LightMode(props, ref) { + return ( + + ) +}) + +export const DarkMode = React.forwardRef(function DarkMode(props, ref) { + return ( + + ) +}) diff --git a/frontend/src/components/ui/provider.jsx b/frontend/src/components/ui/provider.jsx new file mode 100644 index 0000000..80e3e01 --- /dev/null +++ b/frontend/src/components/ui/provider.jsx @@ -0,0 +1,12 @@ +'use client' + +import { ChakraProvider, defaultSystem } from '@chakra-ui/react' +import { ColorModeProvider } from './color-mode' + +export function Provider(props) { + return ( + + + + ) +} diff --git a/frontend/src/components/ui/toaster.jsx b/frontend/src/components/ui/toaster.jsx new file mode 100644 index 0000000..6af2b70 --- /dev/null +++ b/frontend/src/components/ui/toaster.jsx @@ -0,0 +1,43 @@ +'use client' + +import { + Toaster as ChakraToaster, + Portal, + Spinner, + Stack, + Toast, + createToaster, +} from '@chakra-ui/react' + +export const toaster = createToaster({ + placement: 'bottom-end', + pauseOnPageIdle: true, +}) + +export const Toaster = () => { + return ( + + + {(toast) => ( + + {toast.type === 'loading' ? ( + + ) : ( + + )} + + {toast.title && {toast.title}} + {toast.description && ( + {toast.description} + )} + + {toast.action && ( + {toast.action.label} + )} + {toast.closable && } + + )} + + + ) +} diff --git a/frontend/src/components/ui/tooltip.jsx b/frontend/src/components/ui/tooltip.jsx new file mode 100644 index 0000000..ab225cc --- /dev/null +++ b/frontend/src/components/ui/tooltip.jsx @@ -0,0 +1,35 @@ +import { Tooltip as ChakraTooltip, Portal } from '@chakra-ui/react' +import * as React from 'react' + +export const Tooltip = React.forwardRef(function Tooltip(props, ref) { + const { + showArrow, + children, + disabled, + portalled = true, + content, + contentProps, + portalRef, + ...rest + } = props + + if (disabled) return children + + return ( + + {children} + + + + {showArrow && ( + + + + )} + {content} + + + + + ) +}) diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json new file mode 100644 index 0000000..daf5a0f --- /dev/null +++ b/frontend/tsconfig.json @@ -0,0 +1,42 @@ +{ + "compilerOptions": { + "target": "ESNext", // ← ЗАМЕНИТЬ es5 на ESNext + "lib": [ + "dom", + "dom.iterable", + "esnext" + ], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "noEmit": true, + "esModuleInterop": true, + "module": "ESNext", // ← ЗАМЕНИТЬ esnext на ESNext (капитализация) + "moduleResolution": "Bundler", // ← ЗАМЕНИТЬ node на Bundler + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "react-jsx", + "incremental": true, + "plugins": [ + { + "name": "next" + } + ], + "paths": { + "@/*": [ + "./src/*" + ] // ← ЕСЛИ используете src/ структуру + // или "@/": ["./*"] если без src/ + } + }, + "include": [ + "next-env.d.ts", + "**/*.ts", + "**/*.tsx", + ".next/types/**/*.ts", + ".next/dev/types/**/*.ts" + ], + "exclude": [ + "node_modules" + ] +}