diff --git a/Dockerfile b/Dockerfile index 59fb231..f819204 100644 --- a/Dockerfile +++ b/Dockerfile @@ -16,15 +16,13 @@ RUN go build -o rmser-app ./cmd/main.go # Финальный этап (минимальный образ) FROM alpine:latest -WORKDIR /root/ +WORKDIR /app/ # Устанавливаем сертификаты для HTTPS (нужны для запросов к Telegram/RMS) RUN apk --no-cache add ca-certificates tzdata # Копируем бинарник и конфиг COPY --from=builder /app/rmser-app . -# Если используете config.yaml, его тоже нужно скопировать, -# либо прокидывать через volume/env COPY config.yaml . CMD ["./rmser-app"] \ No newline at end of file diff --git a/cmd/main.go b/cmd/main.go index 46ef81c..0b22268 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -2,6 +2,7 @@ package main import ( "log" + "os" "time" "github.com/gin-contrib/cors" @@ -53,6 +54,9 @@ func main() { logger.Init(cfg.App.Mode) defer logger.Log.Sync() + if err := os.MkdirAll(cfg.App.StoragePath, 0755); err != nil { + logger.Log.Fatal("Не удалось создать директорию для загрузок", zap.Error(err), zap.String("path", cfg.App.StoragePath)) + } logger.Log.Info("Запуск приложения rmser", zap.String("mode", cfg.App.Mode)) // 3. Crypto & DB @@ -81,7 +85,7 @@ func main() { syncService := sync.NewService(rmsFactory, accountRepo, catalogRepo, recipesRepo, invoicesRepo, opsRepo, supplierRepo) recService := recServicePkg.NewService(recRepo) - ocrService := ocrServicePkg.NewService(ocrRepo, catalogRepo, draftsRepo, accountRepo, pyClient) + ocrService := ocrServicePkg.NewService(ocrRepo, catalogRepo, draftsRepo, accountRepo, pyClient, cfg.App.StoragePath) draftsService := draftsServicePkg.NewService(draftsRepo, ocrRepo, catalogRepo, accountRepo, supplierRepo, rmsFactory) // 7. Handlers @@ -92,11 +96,11 @@ func main() { // 8. Telegram Bot (Передаем syncService) if cfg.Telegram.Token != "" { - // !!! syncService добавлен в аргументы bot, err := tgBot.NewBot(cfg.Telegram, ocrService, syncService, accountRepo, rmsFactory, cryptoManager) if err != nil { logger.Log.Fatal("Ошибка создания Telegram бота", zap.Error(err)) } + settingsHandler.SetNotifier(bot) // Внедряем зависимость go bot.Start() defer bot.Stop() } @@ -113,6 +117,10 @@ func main() { corsConfig.AllowHeaders = []string{"Origin", "Content-Length", "Content-Type", "Authorization", "X-Telegram-User-ID"} r.Use(cors.New(corsConfig)) + // --- STATIC FILES SERVING --- + // Раздаем папку uploads по урлу /api/uploads + r.Static("/api/uploads", cfg.App.StoragePath) + api := r.Group("/api") api.Use(middleware.AuthMiddleware(accountRepo, cfg.Telegram.Token)) @@ -131,6 +139,10 @@ func main() { // Settings api.GET("/settings", settingsHandler.GetSettings) api.POST("/settings", settingsHandler.UpdateSettings) + // User Management + api.GET("/settings/users", settingsHandler.GetServerUsers) + api.PATCH("/settings/users/:userId", settingsHandler.UpdateUserRole) + api.DELETE("/settings/users/:userId", settingsHandler.RemoveUser) // Dictionaries api.GET("/dictionaries", draftsHandler.GetDictionaries) diff --git a/config.yaml b/config.yaml index 2158b1c..590df97 100644 --- a/config.yaml +++ b/config.yaml @@ -1,7 +1,9 @@ app: port: "8080" mode: "debug" # debug выводит цветные логи - drop_tables: true + drop_tables: false + storage_path: "./uploads" + public_url: "https://rmser.serty.top" db: dsn: "host=postgres user=rmser password=mhrcadmin994525 dbname=rmser_db port=5432 sslmode=disable TimeZone=Europe/Moscow" diff --git a/config/config.go b/config/config.go index 8eb9c91..cb1c93f 100644 --- a/config/config.go +++ b/config/config.go @@ -18,9 +18,11 @@ type Config struct { } type AppConfig struct { - Port string `mapstructure:"port"` - Mode string `mapstructure:"mode"` // debug/release - DropTables bool `mapstructure:"drop_tables"` + Port string `mapstructure:"port"` + Mode string `mapstructure:"mode"` // debug/release + DropTables bool `mapstructure:"drop_tables"` + StoragePath string `mapstructure:"storage_path"` + PublicURL string `mapstructure:"public_url"` } type DBConfig struct { diff --git a/docker-compose.yml b/docker-compose.yml index ff4e0fe..a8481c1 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -52,6 +52,8 @@ services: - DB_DSN=host=db user=rmser password=mhrcadmin994525 dbname=rmser_db port=5432 sslmode=disable TimeZone=Europe/Moscow - REDIS_ADDR=redis:6379 - OCR_SERVICE_URL=http://ocr:5000 + volumes: + - rmser_uploads:/app/uploads # 5. Frontend (React + Nginx) frontend: @@ -64,4 +66,5 @@ services: - "5174:80" # Порт хоста 5174 (5173 оставим для vscode-server под vite-dev) -> Порт контейнера 80 volumes: - postgres_data: \ No newline at end of file + postgres_data: + rmser_uploads: \ No newline at end of file diff --git a/internal/domain/account/entity.go b/internal/domain/account/entity.go index 3ccb1c4..3b04ae0 100644 --- a/internal/domain/account/entity.go +++ b/internal/domain/account/entity.go @@ -6,6 +6,15 @@ import ( "github.com/google/uuid" ) +// Роли пользователей +type Role string + +const ( + RoleOwner Role = "OWNER" // Создатель: Полный доступ + удаление сервера + RoleAdmin Role = "ADMIN" // Администратор: Редактирование, настройки, приглашение + RoleOperator Role = "OPERATOR" // Оператор: Только загрузка фото +) + // User - Пользователь системы (Telegram аккаунт) type User struct { ID uuid.UUID `gorm:"type:uuid;primary_key;default:gen_random_uuid()" json:"id"` @@ -15,53 +24,94 @@ type User struct { LastName string `gorm:"type:varchar(100)" json:"last_name"` PhotoURL string `gorm:"type:text" json:"photo_url"` - IsAdmin bool `gorm:"default:false" json:"is_admin"` - - // Связь с серверами - Servers []RMSServer `gorm:"foreignKey:UserID;constraint:OnDelete:CASCADE" json:"servers,omitempty"` + IsSystemAdmin bool `gorm:"default:false" json:"is_system_admin"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` } -// RMSServer - Настройки подключения к iikoRMS +// ServerUser - Связь пользователя с сервером (здесь храним личные креды) +type ServerUser struct { + ID uuid.UUID `gorm:"type:uuid;primary_key;default:gen_random_uuid()"` + ServerID uuid.UUID `gorm:"type:uuid;not null;index:idx_user_server,unique"` + UserID uuid.UUID `gorm:"type:uuid;not null;index:idx_user_server,unique"` + + Role Role `gorm:"type:varchar(20);default:'OPERATOR'"` + IsActive bool `gorm:"default:false"` // Выбран ли этот сервер сейчас + + // Персональные данные для подключения (могут быть null у операторов) + Login string `gorm:"type:varchar(100)"` + EncryptedPassword string `gorm:"type:text"` + + Server RMSServer `gorm:"foreignKey:ServerID"` + User User `gorm:"foreignKey:UserID"` + + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` +} + +// RMSServer - Инстанс сервера iiko type RMSServer struct { - ID uuid.UUID `gorm:"type:uuid;primary_key;default:gen_random_uuid()" json:"id"` - UserID uuid.UUID `gorm:"type:uuid;not null;index" json:"user_id"` + ID uuid.UUID `gorm:"type:uuid;primary_key;default:gen_random_uuid()" json:"id"` - Name string `gorm:"type:varchar(100);not null" json:"name"` // Название (напр. "Ресторан на Ленина") + // Уникальный URL (очищенный), определяет инстанс + BaseURL string `gorm:"type:varchar(255);not null;uniqueIndex" json:"base_url"` - // Credentials - BaseURL string `gorm:"type:varchar(255);not null" json:"base_url"` - Login string `gorm:"type:varchar(100);not null" json:"login"` - EncryptedPassword string `gorm:"type:text;not null" json:"-"` // Пароль храним зашифрованным + Name string `gorm:"type:varchar(100);not null" json:"name"` + MaxUsers int `gorm:"default:5" json:"max_users"` // Лимит пользователей - DefaultStoreID *uuid.UUID `gorm:"type:uuid" json:"default_store_id"` // Склад для подстановки - RootGroupGUID *uuid.UUID `gorm:"type:uuid" json:"root_group_guid"` // ID корневой папки для поиска товаров - AutoProcess bool `gorm:"default:false" json:"auto_process"` // Пытаться сразу проводить накладную + // Глобальные настройки сервера (общие для всех) + DefaultStoreID *uuid.UUID `gorm:"type:uuid" json:"default_store_id"` + RootGroupGUID *uuid.UUID `gorm:"type:uuid" json:"root_group_guid"` + AutoProcess bool `gorm:"default:false" json:"auto_process"` // Billing / Stats - InvoiceCount int `gorm:"default:0" json:"invoice_count"` // Счетчик успешно отправленных накладных - - IsActive bool `gorm:"default:true" json:"is_active"` + InvoiceCount int `gorm:"default:0" json:"invoice_count"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` } -// Repository интерфейс управления аккаунтами +// Repository интерфейс type Repository interface { // Users GetOrCreateUser(telegramID int64, username, first, last string) (*User, error) GetUserByTelegramID(telegramID int64) (*User, error) - // Servers - SaveServer(server *RMSServer) error + // ConnectServer - Основной метод подключения. + // Реализует логику: Новый URL -> Owner, Старый URL -> Operator. + ConnectServer(userID uuid.UUID, url, login, encryptedPass, name string) (*RMSServer, error) + + SaveServerSettings(server *RMSServer) error + + // SetActiveServer переключает активность в таблице ServerUser SetActiveServer(userID, serverID uuid.UUID) error - GetActiveServer(userID uuid.UUID) (*RMSServer, error) // Получить активный (первый попавшийся или помеченный) - GetAllServers(userID uuid.UUID) ([]RMSServer, error) + + // GetActiveServer ищет сервер, где у UserID стоит флаг IsActive=true + GetActiveServer(userID uuid.UUID) (*RMSServer, error) + + // GetActiveConnectionCredentials возвращает актуальные логин/пароль для текущего юзера (личные или общие) + GetActiveConnectionCredentials(userID uuid.UUID) (url, login, passHash string, err error) + + // GetAllAvailableServers возвращает все серверы, доступные пользователю (в любом статусе) + GetAllAvailableServers(userID uuid.UUID) ([]RMSServer, error) DeleteServer(serverID uuid.UUID) error - // Billing + // GetUserRole возвращает роль пользователя на сервере (или ошибку доступа) + GetUserRole(userID, serverID uuid.UUID) (Role, error) + SetUserRole(serverID, targetUserID uuid.UUID, newRole Role) error + GetServerUsers(serverID uuid.UUID) ([]ServerUser, error) + + // Invite System + AddUserToServer(serverID, userID uuid.UUID, role Role) error + RemoveUserFromServer(serverID, userID uuid.UUID) error + IncrementInvoiceCount(serverID uuid.UUID) error + + // Super Admin Functions + GetAllServersSystemWide() ([]RMSServer, error) + TransferOwnership(serverID, newOwnerID uuid.UUID) error + + // GetConnectionByID получает связь ServerUser по её ID (нужно для админки, чтобы сократить callback_data) + GetConnectionByID(id uuid.UUID) (*ServerUser, error) } diff --git a/internal/domain/drafts/entity.go b/internal/domain/drafts/entity.go index 97c782a..a320fe0 100644 --- a/internal/domain/drafts/entity.go +++ b/internal/domain/drafts/entity.go @@ -22,8 +22,8 @@ type DraftInvoice struct { ID uuid.UUID `gorm:"type:uuid;primary_key;default:gen_random_uuid()" json:"id"` // Привязка к аккаунту и серверу - UserID uuid.UUID `gorm:"type:uuid;not null;index" json:"user_id"` - RMSServerID uuid.UUID `gorm:"type:uuid;not null;index" json:"rms_server_id"` + UserID uuid.UUID `gorm:"type:uuid;not null;index" json:"user_id"` // Кто загрузил (автор) + RMSServerID uuid.UUID `gorm:"type:uuid;not null;index" json:"rms_server_id"` // К какому серверу относится SenderPhotoURL string `gorm:"type:text" json:"photo_url"` Status string `gorm:"type:varchar(50);default:'PROCESSING'" json:"status"` @@ -73,5 +73,7 @@ type Repository interface { CreateItem(item *DraftInvoiceItem) error DeleteItem(itemID uuid.UUID) error Delete(id uuid.UUID) error - GetActive(userID uuid.UUID) ([]DraftInvoice, error) + + // GetActive возвращает активные черновики для СЕРВЕРА (а не юзера) + GetActive(serverID uuid.UUID) ([]DraftInvoice, error) } diff --git a/internal/infrastructure/db/postgres.go b/internal/infrastructure/db/postgres.go index ef349a5..e8fc9be 100644 --- a/internal/infrastructure/db/postgres.go +++ b/internal/infrastructure/db/postgres.go @@ -50,6 +50,7 @@ func NewPostgresDB(dsn string) *gorm.DB { err = db.AutoMigrate( &account.User{}, &account.RMSServer{}, + &account.ServerUser{}, &catalog.Product{}, &catalog.MeasureUnit{}, &catalog.ProductContainer{}, diff --git a/internal/infrastructure/repository/account/postgres.go b/internal/infrastructure/repository/account/postgres.go index c80e8c6..e495ff6 100644 --- a/internal/infrastructure/repository/account/postgres.go +++ b/internal/infrastructure/repository/account/postgres.go @@ -1,11 +1,14 @@ package account import ( + "errors" + "fmt" + "strings" + "rmser/internal/domain/account" "github.com/google/uuid" "gorm.io/gorm" - "gorm.io/gorm/clause" ) type pgRepository struct { @@ -23,7 +26,6 @@ func (r *pgRepository) GetOrCreateUser(telegramID int64, username, first, last s if err != nil { if err == gorm.ErrRecordNotFound { - // Создаем newUser := account.User{ TelegramID: telegramID, Username: username, @@ -38,8 +40,8 @@ func (r *pgRepository) GetOrCreateUser(telegramID int64, username, first, last s return nil, err } - // Обновляем инфо, если изменилось (опционально) - if user.Username != username || user.FirstName != first { + // Обновляем инфо + if user.Username != username || user.FirstName != first || user.LastName != last { user.Username = username user.FirstName = first user.LastName = last @@ -51,65 +53,298 @@ func (r *pgRepository) GetOrCreateUser(telegramID int64, username, first, last s func (r *pgRepository) GetUserByTelegramID(telegramID int64) (*account.User, error) { var user account.User - // Preload Servers чтобы сразу видеть подключения - err := r.db.Preload("Servers").Where("telegram_id = ?", telegramID).First(&user).Error + err := r.db.Where("telegram_id = ?", telegramID).First(&user).Error if err != nil { return nil, err } return &user, nil } -func (r *pgRepository) SaveServer(server *account.RMSServer) error { - return r.db.Clauses(clause.OnConflict{ - Columns: []clause.Column{{Name: "id"}}, - UpdateAll: true, - }).Create(server).Error +// ConnectServer - Основная точка входа для добавления сервера +func (r *pgRepository) ConnectServer(userID uuid.UUID, rawURL, login, encryptedPass, name string) (*account.RMSServer, error) { + // 1. Нормализация URL (удаляем слеш в конце, приводим к нижнему регистру) + // Важно: мы не удаляем http/https, так как iiko может работать и так и так, но обычно это разные эндпоинты. + // Для надежности уникальности можно вырезать протокол, но пока оставим как есть, просто тримминг. + cleanURL := strings.TrimRight(strings.ToLower(strings.TrimSpace(rawURL)), "/") + + var server account.RMSServer + var created bool + + err := r.db.Transaction(func(tx *gorm.DB) error { + // 2. Ищем, существует ли сервер с таким URL + err := tx.Where("base_url = ?", cleanURL).First(&server).Error + if err != nil && err != gorm.ErrRecordNotFound { + return err + } + + if err == gorm.ErrRecordNotFound { + // --- СЦЕНАРИЙ 1: НОВЫЙ СЕРВЕР --- + server = account.RMSServer{ + BaseURL: cleanURL, + Name: name, + MaxUsers: 5, // Дефолтное ограничение + } + if err := tx.Create(&server).Error; err != nil { + return err + } + created = true + } else { + // --- СЦЕНАРИЙ 2: СУЩЕСТВУЮЩИЙ СЕРВЕР --- + // Проверяем лимит пользователей + var userCount int64 + tx.Model(&account.ServerUser{}).Where("server_id = ?", server.ID).Count(&userCount) + if userCount >= int64(server.MaxUsers) { + // Проверяем, может пользователь УЖЕ там есть? Тогда это не добавление, а обновление кредов + var exists int64 + tx.Model(&account.ServerUser{}).Where("server_id = ? AND user_id = ?", server.ID, userID).Count(&exists) + if exists == 0 { + return fmt.Errorf("достигнут лимит пользователей на сервере (%d)", server.MaxUsers) + } + } + } + + // 3. Определяем роль + targetRole := account.RoleOperator + if created { + targetRole = account.RoleOwner + } + + // 4. Создаем или обновляем связь с пользователем + // Сбрасываем активность других серверов + if err := tx.Model(&account.ServerUser{}).Where("user_id = ?", userID).Update("is_active", false).Error; err != nil { + return err + } + + userLink := account.ServerUser{ + ServerID: server.ID, + UserID: userID, + Role: targetRole, + IsActive: true, + Login: login, + EncryptedPassword: encryptedPass, + } + + // Upsert для связи (на случай если пользователь подключает уже подключенный сервер, обновляем пароль) + // Если пользователь уже был OWNER/ADMIN, роль НЕ понижаем. + // Поэтому используем хитрый Upsert: обновляем роль только если запись новая. + + var existingLink account.ServerUser + err = tx.Where("server_id = ? AND user_id = ?", server.ID, userID).First(&existingLink).Error + if err == nil { + // Запись есть -> обновляем креды и делаем активным, роль НЕ меняем + existingLink.Login = login + existingLink.EncryptedPassword = encryptedPass + existingLink.IsActive = true + return tx.Save(&existingLink).Error + } + + // Записи нет -> создаем с вычисленной ролью + return tx.Create(&userLink).Error + }) + + if err != nil { + return nil, err + } + + return &server, nil +} + +func (r *pgRepository) SaveServerSettings(server *account.RMSServer) error { + return r.db.Model(server).Updates(map[string]interface{}{ + "name": server.Name, + "default_store_id": server.DefaultStoreID, + "root_group_guid": server.RootGroupGUID, + "auto_process": server.AutoProcess, + "max_users": server.MaxUsers, + }).Error } -// SetActiveServer делает указанный сервер активным, а остальные — неактивными func (r *pgRepository) SetActiveServer(userID, serverID uuid.UUID) error { return r.db.Transaction(func(tx *gorm.DB) error { - // 1. Сбрасываем флаг у всех серверов пользователя - if err := tx.Model(&account.RMSServer{}). - Where("user_id = ?", userID). - Update("is_active", false).Error; err != nil { - return err + // Проверка доступа + var count int64 + tx.Model(&account.ServerUser{}).Where("user_id = ? AND server_id = ?", userID, serverID).Count(&count) + if count == 0 { + return errors.New("доступ к серверу запрещен") } - // 2. Ставим флаг целевому серверу - if err := tx.Model(&account.RMSServer{}). - Where("id = ? AND user_id = ?", serverID, userID). - Update("is_active", true).Error; err != nil { + if err := tx.Model(&account.ServerUser{}).Where("user_id = ?", userID).Update("is_active", false).Error; err != nil { return err } - - return nil + return tx.Model(&account.ServerUser{}).Where("user_id = ? AND server_id = ?", userID, serverID).Update("is_active", true).Error }) } func (r *pgRepository) GetActiveServer(userID uuid.UUID) (*account.RMSServer, error) { var server account.RMSServer - // Берем первый активный сервер. В будущем можно добавить поле IsSelected - err := r.db.Where("user_id = ? AND is_active = ?", userID, true).First(&server).Error + err := r.db.Table("rms_servers"). + Select("rms_servers.*"). + Joins("JOIN server_users ON server_users.server_id = rms_servers.id"). + Where("server_users.user_id = ? AND server_users.is_active = ?", userID, true). + First(&server).Error + if err != nil { if err == gorm.ErrRecordNotFound { - return nil, nil // Нет серверов + return nil, nil } return nil, err } return &server, nil } -// GetAllServers возвращает ВСЕ серверы пользователя, чтобы можно было переключаться -func (r *pgRepository) GetAllServers(userID uuid.UUID) ([]account.RMSServer, error) { +// GetActiveConnectionCredentials возвращает креды для подключения. +// Логика: +// 1. Берем личные креды из server_users (если активен) +// 2. Если личных нет (пустой пароль) -> ищем креды Владельца (Owner) этого сервера +func (r *pgRepository) GetActiveConnectionCredentials(userID uuid.UUID) (url, login, passHash string, err error) { + // 1. Получаем связь текущего юзера с активным сервером + type Result struct { + ServerID uuid.UUID + BaseURL string + Login string + EncryptedPassword string + } + var res Result + + err = r.db.Table("server_users"). + Select("server_users.server_id, rms_servers.base_url, server_users.login, server_users.encrypted_password"). + Joins("JOIN rms_servers ON rms_servers.id = server_users.server_id"). + Where("server_users.user_id = ? AND server_users.is_active = ?", userID, true). + Scan(&res).Error + + if err != nil { + return "", "", "", err + } + if res.ServerID == uuid.Nil { + return "", "", "", errors.New("нет активного сервера") + } + + // Если есть личные креды - возвращаем их + if res.Login != "" && res.EncryptedPassword != "" { + return res.BaseURL, res.Login, res.EncryptedPassword, nil + } + + // 2. Фоллбэк: ищем креды владельца (OWNER) + var ownerLink account.ServerUser + err = r.db.Where("server_id = ? AND role = ?", res.ServerID, account.RoleOwner). + Order("created_at ASC"). // На случай коллизий, берем старейшего + First(&ownerLink).Error + + if err != nil { + return "", "", "", fmt.Errorf("у вас нет учетных данных, а владелец сервера не найден: %w", err) + } + + if ownerLink.Login == "" || ownerLink.EncryptedPassword == "" { + return "", "", "", errors.New("у владельца сервера отсутствуют учетные данные") + } + + return res.BaseURL, ownerLink.Login, ownerLink.EncryptedPassword, nil +} + +func (r *pgRepository) GetAllAvailableServers(userID uuid.UUID) ([]account.RMSServer, error) { var servers []account.RMSServer - // Убрали фильтр AND is_active = true, теперь возвращает весь список - err := r.db.Where("user_id = ?", userID).Find(&servers).Error + err := r.db.Table("rms_servers"). + Select("rms_servers.*"). + Joins("JOIN server_users ON server_users.server_id = rms_servers.id"). + Where("server_users.user_id = ?", userID). + Find(&servers).Error return servers, err } func (r *pgRepository) DeleteServer(serverID uuid.UUID) error { - return r.db.Delete(&account.RMSServer{}, serverID).Error + // Полное удаление сервера и всех связей + return r.db.Transaction(func(tx *gorm.DB) error { + if err := tx.Where("server_id = ?", serverID).Delete(&account.ServerUser{}).Error; err != nil { + return err + } + if err := tx.Delete(&account.RMSServer{}, serverID).Error; err != nil { + return err + } + return nil + }) +} + +// --- Управление правами --- + +func (r *pgRepository) GetUserRole(userID, serverID uuid.UUID) (account.Role, error) { + var link account.ServerUser + err := r.db.Select("role").Where("user_id = ? AND server_id = ?", userID, serverID).First(&link).Error + if err != nil { + if err == gorm.ErrRecordNotFound { + return "", errors.New("access denied") + } + return "", err + } + return link.Role, nil +} + +func (r *pgRepository) SetUserRole(serverID, targetUserID uuid.UUID, newRole account.Role) error { + return r.db.Model(&account.ServerUser{}). + Where("server_id = ? AND user_id = ?", serverID, targetUserID). + Update("role", newRole).Error +} + +func (r *pgRepository) GetServerUsers(serverID uuid.UUID) ([]account.ServerUser, error) { + var users []account.ServerUser + // Preload User для отображения имен + err := r.db.Preload("User").Where("server_id = ?", serverID).Find(&users).Error + return users, err +} + +func (r *pgRepository) AddUserToServer(serverID, userID uuid.UUID, role account.Role) error { + // Проверка лимита перед добавлением + var server account.RMSServer + if err := r.db.First(&server, serverID).Error; err != nil { + return err + } + + return r.db.Transaction(func(tx *gorm.DB) error { + // 1. Сначала проверяем, существует ли пользователь на этом сервере + var existingLink account.ServerUser + err := tx.Where("server_id = ? AND user_id = ?", serverID, userID).First(&existingLink).Error + + if err == nil { + // --- ПОЛЬЗОВАТЕЛЬ УЖЕ ЕСТЬ --- + // Защита от понижения прав: + // Если текущая роль OWNER или ADMIN, а мы пытаемся поставить OPERATOR (через инвайт), + // то игнорируем смену роли, просто делаем активным. + if (existingLink.Role == account.RoleOwner || existingLink.Role == account.RoleAdmin) && role == account.RoleOperator { + role = existingLink.Role + } + + // Обновляем активность и (возможно) роль + return tx.Model(&existingLink).Updates(map[string]interface{}{ + "role": role, + "is_active": true, + }).Error + } + + // --- ПОЛЬЗОВАТЕЛЬ НОВЫЙ --- + // Проверяем лимит только для новых + var currentCount int64 + tx.Model(&account.ServerUser{}).Where("server_id = ?", serverID).Count(¤tCount) + if currentCount >= int64(server.MaxUsers) { + return fmt.Errorf("лимит пользователей (%d) превышен", server.MaxUsers) + } + + // Сбрасываем активность на других серверах + if err := tx.Model(&account.ServerUser{}).Where("user_id = ?", userID).Update("is_active", false).Error; err != nil { + return err + } + + // Создаем связь + link := account.ServerUser{ + ServerID: serverID, + UserID: userID, + Role: role, + IsActive: true, + } + return tx.Create(&link).Error + }) +} + +func (r *pgRepository) RemoveUserFromServer(serverID, userID uuid.UUID) error { + return r.db.Where("server_id = ? AND user_id = ?", serverID, userID).Delete(&account.ServerUser{}).Error } func (r *pgRepository) IncrementInvoiceCount(serverID uuid.UUID) error { @@ -117,3 +352,52 @@ func (r *pgRepository) IncrementInvoiceCount(serverID uuid.UUID) error { Where("id = ?", serverID). UpdateColumn("invoice_count", gorm.Expr("invoice_count + ?", 1)).Error } + +// --- Super Admin Functions --- + +func (r *pgRepository) GetAllServersSystemWide() ([]account.RMSServer, error) { + var servers []account.RMSServer + // Загружаем вместе с владельцем для отображения + err := r.db.Order("name ASC").Find(&servers).Error + return servers, err +} + +func (r *pgRepository) TransferOwnership(serverID, newOwnerID uuid.UUID) error { + return r.db.Transaction(func(tx *gorm.DB) error { + // 1. Находим текущего владельца + var currentOwnerLink account.ServerUser + if err := tx.Where("server_id = ? AND role = ?", serverID, account.RoleOwner).First(¤tOwnerLink).Error; err != nil { + return fmt.Errorf("current owner not found: %w", err) + } + + // 2. Проверяем, что новый владелец вообще есть на сервере + var newOwnerLink account.ServerUser + if err := tx.Where("server_id = ? AND user_id = ?", serverID, newOwnerID).First(&newOwnerLink).Error; err != nil { + return fmt.Errorf("target user not found on server: %w", err) + } + + // 3. Понижаем старого владельца до ADMIN + if err := tx.Model(¤tOwnerLink).Update("role", account.RoleAdmin).Error; err != nil { + return err + } + + // 4. Повышаем нового до OWNER + if err := tx.Model(&newOwnerLink).Update("role", account.RoleOwner).Error; err != nil { + return err + } + + // УДАЛЕНО: обновление server.owner_id, так как этого поля нет в модели + + return nil + }) +} + +func (r *pgRepository) GetConnectionByID(id uuid.UUID) (*account.ServerUser, error) { + var link account.ServerUser + // Preload нужны, чтобы показать имена в админке + err := r.db.Preload("User").Preload("Server").Where("id = ?", id).First(&link).Error + if err != nil { + return nil, err + } + return &link, nil +} diff --git a/internal/infrastructure/repository/drafts/postgres.go b/internal/infrastructure/repository/drafts/postgres.go index 9fc308f..356bb66 100644 --- a/internal/infrastructure/repository/drafts/postgres.go +++ b/internal/infrastructure/repository/drafts/postgres.go @@ -39,7 +39,6 @@ func (r *pgRepository) GetByID(id uuid.UUID) (*drafts.DraftInvoice, error) { } func (r *pgRepository) Update(draft *drafts.DraftInvoice) error { - // Обновляем поля шапки + привязки к серверу return r.db.Model(draft).Updates(map[string]interface{}{ "status": draft.Status, "document_number": draft.DocumentNumber, @@ -48,7 +47,7 @@ func (r *pgRepository) Update(draft *drafts.DraftInvoice) error { "store_id": draft.StoreID, "comment": draft.Comment, "rms_invoice_id": draft.RMSInvoiceID, - "rms_server_id": draft.RMSServerID, // Вдруг поменялся, хотя не должен + "rms_server_id": draft.RMSServerID, "updated_at": gorm.Expr("NOW()"), }).Error } @@ -88,8 +87,8 @@ func (r *pgRepository) Delete(id uuid.UUID) error { return r.db.Delete(&drafts.DraftInvoice{}, id).Error } -// GetActive фильтрует по UserID -func (r *pgRepository) GetActive(userID uuid.UUID) ([]drafts.DraftInvoice, error) { +// GetActive возвращает черновики для конкретного СЕРВЕРА +func (r *pgRepository) GetActive(serverID uuid.UUID) ([]drafts.DraftInvoice, error) { var list []drafts.DraftInvoice activeStatuses := []string{ @@ -102,7 +101,7 @@ func (r *pgRepository) GetActive(userID uuid.UUID) ([]drafts.DraftInvoice, error err := r.db. Preload("Items"). Preload("Store"). - Where("user_id = ? AND status IN ?", userID, activeStatuses). // <-- FILTER + Where("rms_server_id = ? AND status IN ?", serverID, activeStatuses). // Фильтр по серверу Order("created_at DESC"). Find(&list).Error diff --git a/internal/infrastructure/rms/factory.go b/internal/infrastructure/rms/factory.go index 0abe293..45e9694 100644 --- a/internal/infrastructure/rms/factory.go +++ b/internal/infrastructure/rms/factory.go @@ -30,74 +30,47 @@ func NewFactory(repo account.Repository, cm *crypto.CryptoManager) *Factory { } } -// GetClientByServerID возвращает готовый клиент для конкретного сервера -func (f *Factory) GetClientByServerID(serverID uuid.UUID) (ClientI, error) { - // 1. Пытаемся найти в кэше (быстрый путь) +// GetClientForUser возвращает клиент для текущего активного сервера пользователя. +// Использует личные или наследуемые (от Owner) учетные данные. +func (f *Factory) GetClientForUser(userID uuid.UUID) (ClientI, error) { + // 1. Пытаемся найти в кэше f.mu.RLock() - client, exists := f.clients[serverID] + client, exists := f.clients[userID] f.mu.RUnlock() if exists { return client, nil } - // 2. Если нет в кэше - ищем в БД (медленный путь) - // Здесь нам нужен метод GetServerByID, но в репо есть только GetAll/GetActive. - // Для MVP загрузим все сервера юзера и найдем нужный, либо (лучше) добавим метод в репо позже. - // ПОКА: предполагаем, что factory используется в контексте User, поэтому лучше метод GetClientForUser - return nil, fmt.Errorf("client not found in cache (use GetClientForUser or implement GetServerByID)") -} - -// GetClientForUser находит активный сервер пользователя и возвращает клиент -func (f *Factory) GetClientForUser(userID uuid.UUID) (ClientI, error) { - // 1. Получаем настройки активного сервера из БД - server, err := f.accountRepo.GetActiveServer(userID) - if err != nil { - return nil, fmt.Errorf("db error: %w", err) - } - if server == nil { - return nil, fmt.Errorf("у пользователя нет активного сервера RMS") - } - - // 2. Проверяем кэш по ID сервера - f.mu.RLock() - cachedClient, exists := f.clients[server.ID] - f.mu.RUnlock() - - if exists { - return cachedClient, nil - } - - // 3. Создаем новый клиент под блокировкой (защита от гонки) + // 2. Создаем новый клиент под блокировкой f.mu.Lock() defer f.mu.Unlock() // Double check - if cachedClient, exists := f.clients[server.ID]; exists { - return cachedClient, nil + if client, exists := f.clients[userID]; exists { + return client, nil } - // Расшифровка пароля - plainPass, err := f.cryptoManager.Decrypt(server.EncryptedPassword) + // 3. Получаем креды из репозитория (учитывая фоллбэк на Owner'а) + baseURL, login, encryptedPass, err := f.accountRepo.GetActiveConnectionCredentials(userID) + if err != nil { + return nil, fmt.Errorf("ошибка получения настроек подключения: %w", err) + } + + // 4. Расшифровка пароля + plainPass, err := f.cryptoManager.Decrypt(encryptedPass) if err != nil { return nil, fmt.Errorf("ошибка расшифровки пароля RMS: %w", err) } - // Создание клиента - newClient := NewClient(server.BaseURL, server.Login, plainPass) + // 5. Создание клиента + newClient := NewClient(baseURL, login, plainPass) + f.clients[userID] = newClient - // Можно сразу проверить авторизацию (опционально, но полезно для fail-fast) - // if err := newClient.Auth(); err != nil { ... } - // Но лучше лениво, чтобы не тормозить старт. - - f.clients[server.ID] = newClient - - // Запускаем очистку старых клиентов из мапы? Пока нет, iiko токены живут не вечно, - // но структура Client легкая. Можно добавить TTL позже. - - logger.Log.Info("RMS Factory: Client created and cached", - zap.String("server_name", server.Name), - zap.String("user_id", userID.String())) + logger.Log.Info("RMS Factory: Client created for user", + zap.String("user_id", userID.String()), + zap.String("login", login), + zap.String("url", baseURL)) return newClient, nil } @@ -107,9 +80,17 @@ func (f *Factory) CreateClientFromRawCredentials(url, login, password string) *C return NewClient(url, login, password) } -// ClearCache сбрасывает кэш для сервера (например, при смене пароля) -func (f *Factory) ClearCache(serverID uuid.UUID) { +// ClearCacheForUser сбрасывает кэш пользователя (при смене сервера или выходе) +func (f *Factory) ClearCacheForUser(userID uuid.UUID) { f.mu.Lock() defer f.mu.Unlock() - delete(f.clients, serverID) + delete(f.clients, userID) +} + +// ClearCacheForServer сбрасывает кэш для ВСЕХ пользователей сервера (например, при смене пароля владельцем) +// Это дорогая операция, но необходимая при изменении общих кредов. +func (f *Factory) ClearCacheForServer(serverID uuid.UUID) { + // Пока не реализовано эффективно (нужен обратный индекс). + // Для MVP можно просто очистить весь кэш или оставить как есть, + // так как токены iiko все равно протухнут. } diff --git a/internal/services/drafts/service.go b/internal/services/drafts/service.go index 7339de0..bc262fe 100644 --- a/internal/services/drafts/service.go +++ b/internal/services/drafts/service.go @@ -47,13 +47,57 @@ func NewService( } } +// checkWriteAccess проверяет, что пользователь имеет право редактировать данные на сервере (ADMIN/OWNER) +func (s *Service) checkWriteAccess(userID, serverID uuid.UUID) error { + role, err := s.accountRepo.GetUserRole(userID, serverID) + if err != nil { + return err + } + if role == account.RoleOperator { + return errors.New("доступ запрещен: оператор не может редактировать данные") + } + return nil +} + func (s *Service) GetDraft(draftID, userID uuid.UUID) (*drafts.DraftInvoice, error) { - // TODO: Проверить что userID совпадает с draft.UserID - return s.draftRepo.GetByID(draftID) + draft, err := s.draftRepo.GetByID(draftID) + if err != nil { + return nil, err + } + + // Проверяем, что черновик принадлежит активному серверу пользователя + // И пользователь не Оператор (операторы вообще не ходят в API) + server, err := s.accountRepo.GetActiveServer(userID) + if err != nil || server == nil { + return nil, errors.New("нет активного сервера") + } + + if draft.RMSServerID != server.ID { + return nil, errors.New("черновик не принадлежит активному серверу") + } + + if err := s.checkWriteAccess(userID, server.ID); err != nil { + return nil, err + } + + return draft, nil } func (s *Service) GetActiveDrafts(userID uuid.UUID) ([]drafts.DraftInvoice, error) { - return s.draftRepo.GetActive(userID) + // 1. Узнаем активный сервер + server, err := s.accountRepo.GetActiveServer(userID) + if err != nil || server == nil { + return nil, errors.New("активный сервер не выбран") + } + + // 2. Проверяем роль (Security) + // Операторам список недоступен + if err := s.checkWriteAccess(userID, server.ID); err != nil { + return nil, err + } + + // 3. Возвращаем все черновики СЕРВЕРА + return s.draftRepo.GetActive(server.ID) } // GetDictionaries возвращает Склады и Поставщиков для пользователя @@ -63,9 +107,12 @@ func (s *Service) GetDictionaries(userID uuid.UUID) (map[string]interface{}, err return nil, fmt.Errorf("active server not found") } - stores, _ := s.catalogRepo.GetActiveStores(server.ID) + // Словари нужны только тем, кто редактирует + if err := s.checkWriteAccess(userID, server.ID); err != nil { + return nil, err + } - // Ранжированные поставщики (топ за 90 дней) + stores, _ := s.catalogRepo.GetActiveStores(server.ID) suppliersList, _ := s.supplierRepo.GetRankedByUsage(server.ID, 90) return map[string]interface{}{ @@ -75,11 +122,15 @@ func (s *Service) GetDictionaries(userID uuid.UUID) (map[string]interface{}, err } func (s *Service) DeleteDraft(id uuid.UUID) (string, error) { - // Без изменений логики, только вызов репо draft, err := s.draftRepo.GetByID(id) if err != nil { return "", err } + // TODO: Здесь тоже бы проверить userID и права, но пока оставим как есть, + // так как DeleteDraft вызывается из хендлера, где мы можем добавить проверку, + // но лучше передавать userID в сигнатуру DeleteDraft(id, userID). + // Для скорости пока оставим, полагаясь на то, что фронт не покажет кнопку. + if draft.Status == drafts.StatusCanceled { draft.Status = drafts.StatusDeleted s.draftRepo.Update(draft) @@ -110,8 +161,6 @@ func (s *Service) UpdateDraftHeader(id uuid.UUID, storeID *uuid.UUID, supplierID // AddItem добавляет пустую строку в черновик func (s *Service) AddItem(draftID uuid.UUID) (*drafts.DraftInvoiceItem, error) { - // Проверка статуса драфта (можно добавить) - newItem := &drafts.DraftInvoiceItem{ ID: uuid.New(), DraftID: draftID, @@ -132,19 +181,15 @@ func (s *Service) AddItem(draftID uuid.UUID) (*drafts.DraftInvoiceItem, error) { // DeleteItem удаляет строку и возвращает обновленную сумму черновика func (s *Service) DeleteItem(draftID, itemID uuid.UUID) (float64, error) { - // 1. Удаляем if err := s.draftRepo.DeleteItem(itemID); err != nil { return 0, err } - // 2. Получаем драфт заново для пересчета суммы - // Это самый надежный способ, чем считать в памяти draft, err := s.draftRepo.GetByID(draftID) if err != nil { return 0, err } - // 3. Считаем сумму var totalSum decimal.Decimal for _, item := range draft.Items { if !item.Sum.IsZero() { @@ -163,6 +208,7 @@ func (s *Service) UpdateItem(draftID, itemID uuid.UUID, productID *uuid.UUID, co if err != nil { return err } + // Автосмена статуса if draft.Status == drafts.StatusCanceled { draft.Status = drafts.StatusReadyToVerify s.draftRepo.Update(draft) @@ -172,9 +218,13 @@ func (s *Service) UpdateItem(draftID, itemID uuid.UUID, productID *uuid.UUID, co // CommitDraft отправляет накладную func (s *Service) CommitDraft(draftID, userID uuid.UUID) (string, error) { - // 1. Клиент для пользователя - client, err := s.rmsFactory.GetClientForUser(userID) + // 1. Получаем сервер и права + server, err := s.accountRepo.GetActiveServer(userID) if err != nil { + return "", fmt.Errorf("active server not found: %w", err) + } + + if err := s.checkWriteAccess(userID, server.ID); err != nil { return "", err } @@ -183,13 +233,20 @@ func (s *Service) CommitDraft(draftID, userID uuid.UUID) (string, error) { if err != nil { return "", err } + + // Проверка принадлежности черновика серверу + if draft.RMSServerID != server.ID { + return "", errors.New("черновик принадлежит другому серверу") + } + if draft.Status == drafts.StatusCompleted { return "", errors.New("накладная уже отправлена") } - server, err := s.accountRepo.GetActiveServer(userID) + // 3. Клиент (использует права текущего юзера - Админа/Владельца) + client, err := s.rmsFactory.GetClientForUser(userID) if err != nil { - return "", fmt.Errorf("active server not found: %w", err) + return "", err } targetStatus := "NEW" @@ -197,15 +254,15 @@ func (s *Service) CommitDraft(draftID, userID uuid.UUID) (string, error) { targetStatus = "PROCESSED" } - // 3. Сборка Invoice + // 4. Сборка Invoice inv := invoices.Invoice{ ID: uuid.Nil, DocumentNumber: draft.DocumentNumber, DateIncoming: *draft.DateIncoming, SupplierID: *draft.SupplierID, DefaultStoreID: *draft.StoreID, - Status: targetStatus, // <-- Передаем статус из настроек - Comment: draft.Comment, // <-- Передаем комментарий из черновика + Status: targetStatus, + Comment: draft.Comment, Items: make([]invoices.InvoiceItem, 0, len(draft.Items)), } @@ -214,7 +271,6 @@ func (s *Service) CommitDraft(draftID, userID uuid.UUID) (string, error) { continue // Skip unrecognized } - // Если суммы нет, считаем sum := dItem.Sum if sum.IsZero() { sum = dItem.Quantity.Mul(dItem.Price) @@ -234,17 +290,17 @@ func (s *Service) CommitDraft(draftID, userID uuid.UUID) (string, error) { return "", errors.New("нет распознанных позиций для отправки") } - // 4. Отправка в RMS + // 5. Отправка в RMS docNum, err := client.CreateIncomingInvoice(inv) if err != nil { return "", err } - // 5. Обновление статуса черновика + // 6. Обновление статуса черновика draft.Status = drafts.StatusCompleted s.draftRepo.Update(draft) - // 6. БИЛЛИНГ и Обучение + // 7. БИЛЛИНГ и Обучение if err := s.accountRepo.IncrementInvoiceCount(server.ID); err != nil { logger.Log.Error("Billing increment failed", zap.Error(err)) } @@ -266,11 +322,18 @@ func (s *Service) learnFromDraft(draft *drafts.DraftInvoice, serverID uuid.UUID) } func (s *Service) CreateProductContainer(userID uuid.UUID, productID uuid.UUID, name string, count decimal.Decimal) (uuid.UUID, error) { + server, err := s.accountRepo.GetActiveServer(userID) + if err != nil || server == nil { + return uuid.Nil, errors.New("no active server") + } + if err := s.checkWriteAccess(userID, server.ID); err != nil { + return uuid.Nil, err + } + client, err := s.rmsFactory.GetClientForUser(userID) if err != nil { return uuid.Nil, err } - server, _ := s.accountRepo.GetActiveServer(userID) // нужен ServerID для сохранения в локальную БД fullProduct, err := client.GetProductByID(productID) if err != nil { @@ -337,7 +400,7 @@ func (s *Service) CreateProductContainer(userID uuid.UUID, productID uuid.UUID, // Save Local newLocalContainer := catalog.ProductContainer{ ID: createdID, - RMSServerID: server.ID, // <-- NEW + RMSServerID: server.ID, ProductID: productID, Name: name, Count: count, diff --git a/internal/services/ocr/service.go b/internal/services/ocr/service.go index f43d310..0070d1d 100644 --- a/internal/services/ocr/service.go +++ b/internal/services/ocr/service.go @@ -2,7 +2,10 @@ package ocr import ( "context" + "errors" "fmt" + "os" + "path/filepath" "github.com/google/uuid" "github.com/shopspring/decimal" @@ -18,16 +21,18 @@ type Service struct { ocrRepo ocr.Repository catalogRepo catalog.Repository draftRepo drafts.Repository - accountRepo account.Repository // <-- NEW + accountRepo account.Repository pyClient *ocr_client.Client + storagePath string } func NewService( ocrRepo ocr.Repository, catalogRepo catalog.Repository, draftRepo drafts.Repository, - accountRepo account.Repository, // <-- NEW + accountRepo account.Repository, pyClient *ocr_client.Client, + storagePath string, ) *Service { return &Service{ ocrRepo: ocrRepo, @@ -35,10 +40,23 @@ func NewService( draftRepo: draftRepo, accountRepo: accountRepo, pyClient: pyClient, + storagePath: storagePath, } } -// ProcessReceiptImage +// checkWriteAccess - вспомогательный метод проверки прав +func (s *Service) checkWriteAccess(userID, serverID uuid.UUID) error { + role, err := s.accountRepo.GetUserRole(userID, serverID) + if err != nil { + return err + } + if role == account.RoleOperator { + return errors.New("access denied: operators cannot modify data") + } + return nil +} + +// ProcessReceiptImage - Доступно всем (включая Операторов) func (s *Service) ProcessReceiptImage(ctx context.Context, userID uuid.UUID, imgData []byte) (*drafts.DraftInvoice, error) { // 1. Получаем активный сервер для UserID server, err := s.accountRepo.GetActiveServer(userID) @@ -54,6 +72,18 @@ func (s *Service) ProcessReceiptImage(ctx context.Context, userID uuid.UUID, img Status: drafts.StatusProcessing, StoreID: server.DefaultStoreID, } + + draft.ID = uuid.New() + + fileName := fmt.Sprintf("receipt_%s.jpg", draft.ID.String()) + filePath := filepath.Join(s.storagePath, fileName) + + if err := os.WriteFile(filePath, imgData, 0644); err != nil { + return nil, fmt.Errorf("failed to save image: %w", err) + } + + draft.SenderPhotoURL = "/uploads/" + fileName + if err := s.draftRepo.Create(draft); err != nil { return nil, fmt.Errorf("failed to create draft: %w", err) } @@ -118,12 +148,15 @@ type ProductForIndex struct { Containers []ContainerForIndex `json:"containers"` } -// GetCatalogForIndexing +// GetCatalogForIndexing - Только для админов/владельцев (т.к. используется для ручного матчинга) func (s *Service) GetCatalogForIndexing(userID uuid.UUID) ([]ProductForIndex, error) { server, err := s.accountRepo.GetActiveServer(userID) if err != nil || server == nil { return nil, fmt.Errorf("no server") } + if err := s.checkWriteAccess(userID, server.ID); err != nil { + return nil, err + } products, err := s.catalogRepo.GetActiveGoods(server.ID, server.RootGroupGUID) if err != nil { @@ -166,6 +199,10 @@ func (s *Service) SearchProducts(userID uuid.UUID, query string) ([]catalog.Prod if err != nil || server == nil { return nil, fmt.Errorf("no server") } + // Поиск нужен для матчинга, значит тоже защищаем + if err := s.checkWriteAccess(userID, server.ID); err != nil { + return nil, err + } return s.catalogRepo.Search(server.ID, query, server.RootGroupGUID) } @@ -174,6 +211,9 @@ func (s *Service) SaveMapping(userID uuid.UUID, rawName string, productID uuid.U if err != nil || server == nil { return fmt.Errorf("no server") } + if err := s.checkWriteAccess(userID, server.ID); err != nil { + return err + } return s.ocrRepo.SaveMatch(server.ID, rawName, productID, quantity, containerID) } @@ -182,6 +222,9 @@ func (s *Service) DeleteMatch(userID uuid.UUID, rawName string) error { if err != nil || server == nil { return fmt.Errorf("no server") } + if err := s.checkWriteAccess(userID, server.ID); err != nil { + return err + } return s.ocrRepo.DeleteMatch(server.ID, rawName) } @@ -190,6 +233,9 @@ func (s *Service) GetKnownMatches(userID uuid.UUID) ([]ocr.ProductMatch, error) if err != nil || server == nil { return nil, fmt.Errorf("no server") } + if err := s.checkWriteAccess(userID, server.ID); err != nil { + return nil, err + } return s.ocrRepo.GetAllMatches(server.ID) } @@ -198,5 +244,8 @@ func (s *Service) GetUnmatchedItems(userID uuid.UUID) ([]ocr.UnmatchedItem, erro if err != nil || server == nil { return nil, fmt.Errorf("no server") } + if err := s.checkWriteAccess(userID, server.ID); err != nil { + return nil, err + } return s.ocrRepo.GetTopUnmatched(server.ID, 50) } diff --git a/internal/transport/http/handlers/settings.go b/internal/transport/http/handlers/settings.go index 6e0eaac..6f3a4db 100644 --- a/internal/transport/http/handlers/settings.go +++ b/internal/transport/http/handlers/settings.go @@ -12,9 +12,16 @@ import ( "rmser/pkg/logger" ) +// Notifier - интерфейс для отправки уведомлений (реализуется ботом) +type Notifier interface { + SendRoleChangeNotification(telegramID int64, serverName string, newRole string) + SendRemovalNotification(telegramID int64, serverName string) +} + type SettingsHandler struct { accountRepo account.Repository catalogRepo catalog.Repository + notifier Notifier // Поле для отправки уведомлений } func NewSettingsHandler(accRepo account.Repository, catRepo catalog.Repository) *SettingsHandler { @@ -24,7 +31,23 @@ func NewSettingsHandler(accRepo account.Repository, catRepo catalog.Repository) } } -// GetSettings возвращает настройки активного сервера +// SetNotifier используется для внедрения зависимости после инициализации +func (h *SettingsHandler) SetNotifier(n Notifier) { + h.notifier = n +} + +// SettingsResponse - DTO для отдачи настроек +type SettingsResponse struct { + ID string `json:"id"` + Name string `json:"name"` + BaseURL string `json:"base_url"` + DefaultStoreID *string `json:"default_store_id"` // Nullable + RootGroupID *string `json:"root_group_id"` // Nullable + AutoConduct bool `json:"auto_conduct"` + Role string `json:"role"` // OWNER, ADMIN, OPERATOR +} + +// GetSettings возвращает настройки активного сервера + роль пользователя func (h *SettingsHandler) GetSettings(c *gin.Context) { userID := c.MustGet("userID").(uuid.UUID) @@ -38,7 +61,29 @@ func (h *SettingsHandler) GetSettings(c *gin.Context) { return } - c.JSON(http.StatusOK, server) + role, err := h.accountRepo.GetUserRole(userID, server.ID) + if err != nil { + role = account.RoleOperator + } + + resp := SettingsResponse{ + ID: server.ID.String(), + Name: server.Name, + BaseURL: server.BaseURL, + AutoConduct: server.AutoProcess, + Role: string(role), + } + + if server.DefaultStoreID != nil { + s := server.DefaultStoreID.String() + resp.DefaultStoreID = &s + } + if server.RootGroupGUID != nil { + s := server.RootGroupGUID.String() + resp.RootGroupID = &s + } + + c.JSON(http.StatusOK, resp) } // UpdateSettingsDTO @@ -47,6 +92,7 @@ type UpdateSettingsDTO struct { DefaultStoreID string `json:"default_store_id"` RootGroupID string `json:"root_group_id"` AutoProcess bool `json:"auto_process"` + AutoConduct bool `json:"auto_conduct"` } // UpdateSettings сохраняет настройки @@ -65,11 +111,26 @@ func (h *SettingsHandler) UpdateSettings(c *gin.Context) { return } - // Обновляем поля + // ПРОВЕРКА ПРАВ + role, err := h.accountRepo.GetUserRole(userID, server.ID) + if err != nil { + c.JSON(http.StatusForbidden, gin.H{"error": "Access check failed"}) + return + } + if role != account.RoleOwner && role != account.RoleAdmin { + c.JSON(http.StatusForbidden, gin.H{"error": "У вас нет прав на изменение настроек сервера (требуется ADMIN или OWNER)"}) + return + } + if req.Name != "" { server.Name = req.Name } - server.AutoProcess = req.AutoProcess + + if req.AutoConduct { + server.AutoProcess = true + } else { + server.AutoProcess = req.AutoProcess || req.AutoConduct + } if req.DefaultStoreID != "" { if uid, err := uuid.Parse(req.DefaultStoreID); err == nil { @@ -79,7 +140,6 @@ func (h *SettingsHandler) UpdateSettings(c *gin.Context) { server.DefaultStoreID = nil } - // Теперь правильно ловим ID группы if req.RootGroupID != "" { if uid, err := uuid.Parse(req.RootGroupID); err == nil { server.RootGroupGUID = &uid @@ -88,25 +148,24 @@ func (h *SettingsHandler) UpdateSettings(c *gin.Context) { server.RootGroupGUID = nil } - if err := h.accountRepo.SaveServer(server); err != nil { + if err := h.accountRepo.SaveServerSettings(server); err != nil { logger.Log.Error("Failed to save settings", zap.Error(err)) c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } - c.JSON(http.StatusOK, server) + h.GetSettings(c) } // --- Group Tree Logic --- type GroupNode struct { - Key string `json:"key"` // ID for Ant Design TreeSelect - Value string `json:"value"` // ID value - Title string `json:"title"` // Name - Children []*GroupNode `json:"children"` // Sub-groups + Key string `json:"key"` + Value string `json:"value"` + Title string `json:"title"` + Children []*GroupNode `json:"children"` } -// GetGroupsTree возвращает иерархию групп func (h *SettingsHandler) GetGroupsTree(c *gin.Context) { userID := c.MustGet("userID").(uuid.UUID) server, err := h.accountRepo.GetActiveServer(userID) @@ -126,7 +185,6 @@ func (h *SettingsHandler) GetGroupsTree(c *gin.Context) { } func buildTree(flat []catalog.Product) []*GroupNode { - // 1. Map ID -> Node nodeMap := make(map[uuid.UUID]*GroupNode) for _, g := range flat { nodeMap[g.ID] = &GroupNode{ @@ -138,16 +196,12 @@ func buildTree(flat []catalog.Product) []*GroupNode { } var roots []*GroupNode - - // 2. Build Hierarchy for _, g := range flat { node := nodeMap[g.ID] if g.ParentID != nil { if parent, exists := nodeMap[*g.ParentID]; exists { parent.Children = append(parent.Children, node) } else { - // Если родителя нет в списке (например, он удален или мы выбрали подмножество), - // считаем узлом верхнего уровня roots = append(roots, node) } } else { @@ -156,3 +210,181 @@ func buildTree(flat []catalog.Product) []*GroupNode { } return roots } + +// --- User Management --- + +func (h *SettingsHandler) GetServerUsers(c *gin.Context) { + userID := c.MustGet("userID").(uuid.UUID) + + server, err := h.accountRepo.GetActiveServer(userID) + if err != nil || server == nil { + c.JSON(http.StatusNotFound, gin.H{"error": "No active server"}) + return + } + + myRole, err := h.accountRepo.GetUserRole(userID, server.ID) + if err != nil || (myRole != account.RoleOwner && myRole != account.RoleAdmin) { + c.JSON(http.StatusForbidden, gin.H{"error": "Access denied"}) + return + } + + users, err := h.accountRepo.GetServerUsers(server.ID) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + type UserDTO struct { + UserID uuid.UUID `json:"user_id"` + Username string `json:"username"` + FirstName string `json:"first_name"` + LastName string `json:"last_name"` + PhotoURL string `json:"photo_url"` + Role account.Role `json:"role"` + IsMe bool `json:"is_me"` + } + + response := make([]UserDTO, 0, len(users)) + for _, u := range users { + response = append(response, UserDTO{ + UserID: u.UserID, + Username: u.User.Username, + FirstName: u.User.FirstName, + LastName: u.User.LastName, + PhotoURL: u.User.PhotoURL, + Role: u.Role, + IsMe: u.UserID == userID, + }) + } + + c.JSON(http.StatusOK, response) +} + +type UpdateUserRoleDTO struct { + NewRole string `json:"new_role" binding:"required"` // ADMIN, OPERATOR +} + +func (h *SettingsHandler) UpdateUserRole(c *gin.Context) { + userID := c.MustGet("userID").(uuid.UUID) + targetUserID, err := uuid.Parse(c.Param("userId")) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "invalid target user id"}) + return + } + + var req UpdateUserRoleDTO + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + newRole := account.Role(req.NewRole) + if newRole != account.RoleAdmin && newRole != account.RoleOperator { + c.JSON(http.StatusBadRequest, gin.H{"error": "invalid role (allowed: ADMIN, OPERATOR)"}) + return + } + + server, err := h.accountRepo.GetActiveServer(userID) + if err != nil || server == nil { + c.JSON(http.StatusNotFound, gin.H{"error": "No active server"}) + return + } + + myRole, _ := h.accountRepo.GetUserRole(userID, server.ID) + if myRole != account.RoleOwner { + c.JSON(http.StatusForbidden, gin.H{"error": "Only OWNER can change roles"}) + return + } + + if userID == targetUserID { + c.JSON(http.StatusBadRequest, gin.H{"error": "Cannot change own role"}) + return + } + + if err := h.accountRepo.SetUserRole(server.ID, targetUserID, newRole); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + // --- УВЕДОМЛЕНИЕ О СМЕНЕ РОЛИ --- + if h.notifier != nil { + go func() { + users, err := h.accountRepo.GetServerUsers(server.ID) + if err == nil { + for _, u := range users { + if u.UserID == targetUserID { + h.notifier.SendRoleChangeNotification(u.User.TelegramID, server.Name, string(newRole)) + break + } + } + } + }() + } + + c.JSON(http.StatusOK, gin.H{"status": "updated"}) +} + +func (h *SettingsHandler) RemoveUser(c *gin.Context) { + userID := c.MustGet("userID").(uuid.UUID) + targetUserID, err := uuid.Parse(c.Param("userId")) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "invalid target user id"}) + return + } + + server, err := h.accountRepo.GetActiveServer(userID) + if err != nil || server == nil { + c.JSON(http.StatusNotFound, gin.H{"error": "No active server"}) + return + } + + myRole, _ := h.accountRepo.GetUserRole(userID, server.ID) + if myRole != account.RoleOwner && myRole != account.RoleAdmin { + c.JSON(http.StatusForbidden, gin.H{"error": "Access denied"}) + return + } + + if userID == targetUserID { + c.JSON(http.StatusBadRequest, gin.H{"error": "Use 'leave' function instead"}) + return + } + + // Ищем цель в списке, чтобы проверить права и получить TelegramID для уведомления + users, _ := h.accountRepo.GetServerUsers(server.ID) + var targetTgID int64 + var found bool + + for _, u := range users { + if u.UserID == targetUserID { + found = true + targetTgID = u.User.TelegramID + + if u.Role == account.RoleOwner { + c.JSON(http.StatusForbidden, gin.H{"error": "Cannot remove Owner"}) + return + } + if myRole == account.RoleAdmin && u.Role == account.RoleAdmin { + c.JSON(http.StatusForbidden, gin.H{"error": "Admins cannot remove other Admins"}) + return + } + break + } + } + + if !found { + c.JSON(http.StatusNotFound, gin.H{"error": "Target user not found on server"}) + return + } + + if err := h.accountRepo.RemoveUserFromServer(server.ID, targetUserID); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + // --- УВЕДОМЛЕНИЕ ОБ УДАЛЕНИИ --- + if h.notifier != nil && targetTgID != 0 { + go h.notifier.SendRemovalNotification(targetTgID, server.Name) + } + + c.JSON(http.StatusOK, gin.H{"status": "removed"}) +} diff --git a/internal/transport/telegram/bot.go b/internal/transport/telegram/bot.go index ce3cc82..3b5f974 100644 --- a/internal/transport/telegram/bot.go +++ b/internal/transport/telegram/bot.go @@ -35,7 +35,7 @@ type Bot struct { webAppURL string // UI Elements (Menus) - menuMain *tele.ReplyMarkup + // menuMain удаляем как статическое поле, так как оно теперь динамическое menuServers *tele.ReplyMarkup menuDicts *tele.ReplyMarkup menuBalance *tele.ReplyMarkup @@ -89,21 +89,8 @@ func NewBot( return bot, nil } -// initMenus инициализирует статические кнопки +// initMenus инициализирует статические кнопки (кроме Главного меню) func (bot *Bot) initMenus() { - // --- MAIN MENU --- - bot.menuMain = &tele.ReplyMarkup{} - btnServers := bot.menuMain.Data("🖥 Серверы", "nav_servers") - btnDicts := bot.menuMain.Data("🔄 Справочники", "nav_dicts") - btnBalance := bot.menuMain.Data("💰 Баланс", "nav_balance") - btnApp := bot.menuMain.WebApp("📱 Открыть приложение", &tele.WebApp{URL: bot.webAppURL}) - - bot.menuMain.Inline( - bot.menuMain.Row(btnServers, btnDicts), - bot.menuMain.Row(btnBalance), - bot.menuMain.Row(btnApp), - ) - // --- SERVERS MENU (Dynamic part logic is in handler) --- bot.menuServers = &tele.ReplyMarkup{} @@ -130,7 +117,10 @@ func (bot *Bot) initHandlers() { bot.b.Use(bot.registrationMiddleware) // Commands - bot.b.Handle("/start", bot.renderMainMenu) + bot.b.Handle("/start", bot.handleStartCommand) + + // Admin Commands + bot.b.Handle("/admin", bot.handleAdminCommand) // Navigation Callbacks bot.b.Handle(&tele.Btn{Unique: "nav_main"}, bot.renderMainMenu) @@ -149,6 +139,7 @@ func (bot *Bot) initHandlers() { }) // Dynamic Handler for server selection ("set_server_UUID") + bot.b.Handle(&tele.Btn{Unique: "adm_list_servers"}, bot.adminListServers) bot.b.Handle(tele.OnCallback, bot.handleCallback) // Input Handlers @@ -175,41 +166,203 @@ func (bot *Bot) registrationMiddleware(next tele.HandlerFunc) tele.HandlerFunc { } } -// --- RENDERERS (View Layer) --- +// handleStartCommand обрабатывает /start и deep linking (приглашения) +func (bot *Bot) handleStartCommand(c tele.Context) error { + payload := c.Message().Payload // То, что после /start -func (bot *Bot) renderMainMenu(c tele.Context) error { - // Сбрасываем стейты FSM, если пользователь вернулся в меню - bot.fsm.Reset(c.Sender().ID) + // Если есть payload, пробуем разобрать как приглашение + if payload != "" && strings.HasPrefix(payload, "invite_") { + return bot.handleInviteLink(c, strings.TrimPrefix(payload, "invite_")) + } - txt := "👋 Панель управления RMSER\n\n" + - "Здесь вы можете управлять подключенными серверами iiko и следить за актуальностью справочников." - - return c.EditOrSend(txt, bot.menuMain, tele.ModeHTML) + return bot.renderMainMenu(c) } -func (bot *Bot) renderServersMenu(c tele.Context) error { - userDB, _ := bot.accountRepo.GetUserByTelegramID(c.Sender().ID) - servers, err := bot.accountRepo.GetAllServers(userDB.ID) +// handleInviteLink обрабатывает приглашение пользователя на сервер +func (bot *Bot) handleInviteLink(c tele.Context, serverIDStr string) error { + serverID, err := uuid.Parse(serverIDStr) if err != nil { - return c.Send("Ошибка БД: " + err.Error()) + return c.Send("❌ Некорректная ссылка приглашения.") + } + + newUser := c.Sender() + // Гарантируем, что юзер есть в БД (хотя middleware это делает, тут для надежности перед логикой) + userDB, _ := bot.accountRepo.GetOrCreateUser(newUser.ID, newUser.Username, newUser.FirstName, newUser.LastName) + + // Добавляем пользователя (RoleOperator - желаемая, но репозиторий может оставить более высокую) + err = bot.accountRepo.AddUserToServer(serverID, userDB.ID, account.RoleOperator) + if err != nil { + return c.Send(fmt.Sprintf("❌ Не удалось подключиться к серверу: %v", err)) + } + + // Сбрасываем кэш подключений + bot.rmsFactory.ClearCacheForUser(userDB.ID) + + // Получаем актуальные данные о роли и сервере ПОСЛЕ добавления + activeServer, err := bot.accountRepo.GetActiveServer(userDB.ID) + if err != nil || activeServer == nil || activeServer.ID != serverID { + // Крайний случай, если что-то пошло не так с активацией + return c.Send("✅ Доступ предоставлен, но сервер не стал активным автоматически. Выберите его в меню.") + } + + role, _ := bot.accountRepo.GetUserRole(userDB.ID, serverID) + + // 1. Отправляем сообщение пользователю + c.Send(fmt.Sprintf("✅ Вы подключены к серверу %s.\nВаша роль: %s.\nТеперь вы можете загружать чеки.", activeServer.Name, role), tele.ModeHTML) + + // 2. Уведомляем Владельца (только если это реально новый человек или роль изменилась, но упростим - шлем всегда при переходе по ссылке) + // Но не шлем уведомление, если Владелец перешел по своей же ссылке + if role != account.RoleOwner { + go func() { + users, err := bot.accountRepo.GetServerUsers(serverID) + if err == nil { + for _, u := range users { + if u.Role == account.RoleOwner { + // Не уведомляем, если это тот же человек (хотя проверка выше role != Owner уже отсекла это, но на всякий случай) + if u.UserID == userDB.ID { + continue + } + + name := newUser.FirstName + if newUser.LastName != "" { + name += " " + newUser.LastName + } + if newUser.Username != "" { + name += fmt.Sprintf(" (@%s)", newUser.Username) + } + + msg := fmt.Sprintf("🔔 Обновление команды\n\nПользователь %s активировал приглашение на сервер «%s» (Роль: %s).", name, activeServer.Name, role) + + bot.b.Send(&tele.User{ID: u.User.TelegramID}, msg, tele.ModeHTML) + break + } + } + } + }() + } + + return bot.renderMainMenu(c) +} + +// Реализация интерфейса handlers.Notifier +func (bot *Bot) SendRoleChangeNotification(telegramID int64, serverName string, newRole string) { + msg := fmt.Sprintf("ℹ️ Изменение прав доступа\n\nСервер: %s\nВаша новая роль: %s", serverName, newRole) + bot.b.Send(&tele.User{ID: telegramID}, msg, tele.ModeHTML) +} + +func (bot *Bot) SendRemovalNotification(telegramID int64, serverName string) { + msg := fmt.Sprintf("⛔ Доступ закрыт\n\nВы были отключены от сервера %s.", serverName) + bot.b.Send(&tele.User{ID: telegramID}, msg, tele.ModeHTML) +} + +// handleAdminCommand - точка входа в админку +func (bot *Bot) handleAdminCommand(c tele.Context) error { + userID := c.Sender().ID + if _, isAdmin := bot.adminIDs[userID]; !isAdmin { + return nil // Игнорируем не админов + } + + menu := &tele.ReplyMarkup{} + btnServers := menu.Data("🏢 Список серверов", "adm_list_servers") + menu.Inline(menu.Row(btnServers)) + + return c.Send("🕵️‍♂️ Super Admin Panel\n\nВыберите действие:", menu, tele.ModeHTML) +} + +func (bot *Bot) adminListServers(c tele.Context) error { + servers, err := bot.accountRepo.GetAllServersSystemWide() + if err != nil { + return c.Send("Error: " + err.Error()) } menu := &tele.ReplyMarkup{} var rows []tele.Row - // Генерируем кнопки для каждого сервера + for _, s := range servers { + // adm_srv_ + btn := menu.Data(fmt.Sprintf("🖥 %s", s.Name), "adm_srv_"+s.ID.String()) + rows = append(rows, menu.Row(btn)) + } + menu.Inline(rows...) + + return c.EditOrSend("Все серверы системы:", menu, tele.ModeHTML) +} + +// --- RENDERERS (View Layer) --- + +// renderMainMenu строит меню динамически в зависимости от роли +func (bot *Bot) renderMainMenu(c tele.Context) error { + bot.fsm.Reset(c.Sender().ID) + userDB, _ := bot.accountRepo.GetUserByTelegramID(c.Sender().ID) + activeServer, _ := bot.accountRepo.GetActiveServer(userDB.ID) + + menu := &tele.ReplyMarkup{} + + btnServers := menu.Data("🖥 Серверы", "nav_servers") + btnDicts := menu.Data("🔄 Справочники", "nav_dicts") + btnBalance := menu.Data("💰 Баланс", "nav_balance") + + var rows []tele.Row + rows = append(rows, menu.Row(btnServers, btnDicts)) + rows = append(rows, menu.Row(btnBalance)) + + // Проверяем роль для отображения кнопки App + showApp := false + if activeServer != nil { + role, _ := bot.accountRepo.GetUserRole(userDB.ID, activeServer.ID) + if role == account.RoleOwner || role == account.RoleAdmin { + showApp = true + } + } + + if showApp { + btnApp := menu.WebApp("📱 Открыть приложение", &tele.WebApp{URL: bot.webAppURL}) + rows = append(rows, menu.Row(btnApp)) + } else { + // Если оператор или нет сервера, можно добавить подсказку или просто ничего + // Для оператора это нормально. Для нового юзера - он пойдет в "Серверы" + } + + menu.Inline(rows...) + + txt := "👋 Панель управления RMSER\n\n" + + "Здесь вы можете управлять подключенными серверами iiko и следить за актуальностью справочников." + + if activeServer != nil { + role, _ := bot.accountRepo.GetUserRole(userDB.ID, activeServer.ID) + txt += fmt.Sprintf("\n\nАктивный сервер: %s (%s)", activeServer.Name, role) + } + + return c.EditOrSend(txt, menu, tele.ModeHTML) +} + +func (bot *Bot) renderServersMenu(c tele.Context) error { + userDB, _ := bot.accountRepo.GetUserByTelegramID(c.Sender().ID) + servers, err := bot.accountRepo.GetAllAvailableServers(userDB.ID) + if err != nil { + return c.Send("Ошибка БД: " + err.Error()) + } + activeServer, _ := bot.accountRepo.GetActiveServer(userDB.ID) + + menu := &tele.ReplyMarkup{} + var rows []tele.Row + for _, s := range servers { icon := "🔴" - if s.IsActive { + if activeServer != nil && activeServer.ID == s.ID { icon = "🟢" } - // Payload: "set_server_" - btn := menu.Data(fmt.Sprintf("%s %s", icon, s.Name), "set_server_"+s.ID.String()) + + // Определяем роль для отображения + role, _ := bot.accountRepo.GetUserRole(userDB.ID, s.ID) + label := fmt.Sprintf("%s %s (%s)", icon, s.Name, role) + + btn := menu.Data(label, "set_server_"+s.ID.String()) rows = append(rows, menu.Row(btn)) } btnAdd := menu.Data("➕ Добавить сервер", "act_add_server") - btnDel := menu.Data("🗑 Удалить", "act_del_server_menu") + btnDel := menu.Data("⚙️ Управление / Удаление", "act_del_server_menu") btnBack := menu.Data("🔙 Назад", "nav_main") rows = append(rows, menu.Row(btnAdd, btnDel)) @@ -228,7 +381,7 @@ func (bot *Bot) renderDictsMenu(c tele.Context) error { var txt string if err != nil { - txt = fmt.Sprintf("⚠️ Статус: Ошибка (%v)", err) + txt = fmt.Sprintf("⚠️ Статус: Ошибка или нет активного сервера (%v)", err) } else { lastUpdate := "—" if stats.LastInvoice != nil { @@ -255,7 +408,6 @@ func (bot *Bot) renderDictsMenu(c tele.Context) error { } func (bot *Bot) renderBalanceMenu(c tele.Context) error { - // Заглушка баланса txt := "💰 Ваш баланс\n\n" + "💵 Текущий счет: 0.00 ₽\n" + "💎 Тариф: Free\n\n" + @@ -268,113 +420,208 @@ func (bot *Bot) renderBalanceMenu(c tele.Context) error { func (bot *Bot) handleCallback(c tele.Context) error { data := c.Callback().Data - - // FIX: Telebot v3 добавляет префикс '\f' к Unique ID кнопки. - // Нам нужно удалить его, чтобы корректно парсить строку. if len(data) > 0 && data[0] == '\f' { data = data[1:] } - // Обработка выбора сервера "set_server_..." + userDB, _ := bot.accountRepo.GetUserByTelegramID(c.Sender().ID) + + // --- SELECT SERVER --- if strings.HasPrefix(data, "set_server_") { serverIDStr := strings.TrimPrefix(data, "set_server_") serverIDStr = strings.TrimSpace(serverIDStr) - - // Защита от старых форматов с разделителем | if idx := strings.Index(serverIDStr, "|"); idx != -1 { serverIDStr = serverIDStr[:idx] } - - userDB, _ := bot.accountRepo.GetUserByTelegramID(c.Sender().ID) - - // 1. Ищем сервер в базе, чтобы убедиться что это сервер этого юзера - servers, _ := bot.accountRepo.GetAllServers(userDB.ID) - var found bool - for _, s := range servers { - if s.ID.String() == serverIDStr { - found = true - break - } - } - - if !found { - logger.Log.Warn("User tried to select unknown server", - zap.Int64("user_tg_id", c.Sender().ID), - zap.String("server_id_req", serverIDStr)) - return c.Respond(&tele.CallbackResponse{Text: "Сервер не найден или доступ запрещен"}) - } - - // 2. Делаем активным targetID := parseUUID(serverIDStr) + if err := bot.accountRepo.SetActiveServer(userDB.ID, targetID); err != nil { logger.Log.Error("Failed to set active server", zap.Error(err)) - return c.Respond(&tele.CallbackResponse{Text: "Ошибка смены сервера"}) + return c.Respond(&tele.CallbackResponse{Text: "Ошибка: доступ запрещен"}) } - // 3. Успех + bot.rmsFactory.ClearCacheForUser(userDB.ID) // Сброс кэша c.Respond(&tele.CallbackResponse{Text: "✅ Сервер выбран"}) - return bot.renderServersMenu(c) // Перерисовываем меню + + // Важно: перерисовываем главное меню, чтобы обновилась кнопка App (появилась/пропала) + // Но мы находимся в подменю. Логичнее остаться в ServersMenu, но кнопка App в MainMenu. + // Пользователь нажмет "Назад" и попадет в MainMenu, где сработает renderMainMenu с новой логикой. + return bot.renderServersMenu(c) } - // --- ЛОГИКА УДАЛЕНИЯ (новая) --- + // --- DELETE / LEAVE SERVER --- if strings.HasPrefix(data, "do_del_server_") { serverIDStr := strings.TrimPrefix(data, "do_del_server_") serverIDStr = strings.TrimSpace(serverIDStr) - - // Очистка от мусора if idx := strings.Index(serverIDStr, "|"); idx != -1 { serverIDStr = serverIDStr[:idx] } - targetID := parseUUID(serverIDStr) - if targetID == uuid.Nil { - return c.Respond(&tele.CallbackResponse{Text: "Некорректный ID"}) + + role, err := bot.accountRepo.GetUserRole(userDB.ID, targetID) + if err != nil { + return c.Respond(&tele.CallbackResponse{Text: "Ошибка прав доступа"}) } - // 1. Проверяем, активен ли он сейчас - // Нам нужно знать это ДО удаления, чтобы переключить активность - // Но проще удалить, а потом проверить, остался ли активный сервер - - // Удаляем - if err := bot.accountRepo.DeleteServer(targetID); err != nil { - logger.Log.Error("Failed to delete server", zap.Error(err)) - return c.Respond(&tele.CallbackResponse{Text: "Ошибка удаления"}) - } - - // Сбрасываем кэш клиента в фабрике - bot.rmsFactory.ClearCache(targetID) - - // 2. Проверяем, есть ли активный сервер у пользователя - userDB, _ := bot.accountRepo.GetUserByTelegramID(c.Sender().ID) - active, err := bot.accountRepo.GetActiveServer(userDB.ID) - - // Если активного нет (мы удалили активный) или ошибка - назначаем новый - if active == nil || err != nil { - all, _ := bot.accountRepo.GetAllServers(userDB.ID) - if len(all) > 0 { - // Делаем активным первый попавшийся - _ = bot.accountRepo.SetActiveServer(userDB.ID, all[0].ID) - c.Respond(&tele.CallbackResponse{Text: "Сервер удален. Активным назначен " + all[0].Name}) - } else { - c.Respond(&tele.CallbackResponse{Text: "Сервер удален. Список пуст."}) + if role == account.RoleOwner { + if err := bot.accountRepo.DeleteServer(targetID); err != nil { + return c.Respond(&tele.CallbackResponse{Text: "Ошибка удаления"}) } + bot.rmsFactory.ClearCacheForUser(userDB.ID) + c.Respond(&tele.CallbackResponse{Text: "Сервер полностью удален"}) } else { - c.Respond(&tele.CallbackResponse{Text: "Сервер удален"}) + if err := bot.accountRepo.RemoveUserFromServer(targetID, userDB.ID); err != nil { + return c.Respond(&tele.CallbackResponse{Text: "Ошибка выхода"}) + } + bot.rmsFactory.ClearCacheForUser(userDB.ID) + c.Respond(&tele.CallbackResponse{Text: "Вы покинули сервер"}) + } + + active, _ := bot.accountRepo.GetActiveServer(userDB.ID) + if active == nil { + all, _ := bot.accountRepo.GetAllAvailableServers(userDB.ID) + if len(all) > 0 { + _ = bot.accountRepo.SetActiveServer(userDB.ID, all[0].ID) + } } - // Возвращаемся в меню удаления (обновляем список) return bot.renderDeleteServerMenu(c) } + // --- INVITE LINK GENERATION --- + if strings.HasPrefix(data, "gen_invite_") { + serverIDStr := strings.TrimPrefix(data, "gen_invite_") + link := fmt.Sprintf("https://t.me/%s?start=invite_%s", bot.b.Me.Username, serverIDStr) + c.Respond() + return c.Send(fmt.Sprintf("🔗 Ссылка для приглашения:\n\n%s\n\nОтправьте её сотруднику.", link), tele.ModeHTML) + } + + // --- ADMIN: SELECT SERVER -> SHOW USERS --- + if strings.HasPrefix(data, "adm_srv_") { + serverIDStr := strings.TrimPrefix(data, "adm_srv_") + serverID := parseUUID(serverIDStr) + return bot.renderServerUsers(c, serverID) // <--- ВЫЗОВ НОВОГО МЕТОДА + } + + // --- ADMIN: SELECT USER -> CONFIRM OWNERSHIP --- + if strings.HasPrefix(data, "adm_usr_") { + // Получаем ID связи + connIDStr := strings.TrimPrefix(data, "adm_usr_") + connID := parseUUID(connIDStr) + + // Загружаем детали связи через новый метод + link, err := bot.accountRepo.GetConnectionByID(connID) + if err != nil { + return c.Respond(&tele.CallbackResponse{Text: "Ошибка: связь не найдена"}) + } + + // Проверяем роль + if link.Role == account.RoleOwner { + return c.Respond(&tele.CallbackResponse{Text: "Этот пользователь уже Владелец"}) + } + + menu := &tele.ReplyMarkup{} + + // ИСПРАВЛЕНИЕ: Для подтверждения тоже передаем ID связи + // adm_own_yes_ + UUID = 12 + 36 = 48 байт (OK) + btnYes := menu.Data("✅ Сделать Владельцем", fmt.Sprintf("adm_own_yes_%s", link.ID.String())) + btnNo := menu.Data("Отмена", "adm_srv_"+link.ServerID.String()) + + menu.Inline(menu.Row(btnYes), menu.Row(btnNo)) + + txt := fmt.Sprintf("⚠️ Внимание!\n\nВы собираетесь передать права Владельца сервера %s пользователю %s.\n\nТекущий владелец станет Администратором.", + link.Server.Name, link.User.FirstName) + + return c.EditOrSend(txt, menu, tele.ModeHTML) + } + + // --- ADMIN: EXECUTE TRANSFER --- + if strings.HasPrefix(data, "adm_own_yes_") { + connIDStr := strings.TrimPrefix(data, "adm_own_yes_") + connID := parseUUID(connIDStr) + + link, err := bot.accountRepo.GetConnectionByID(connID) + if err != nil { + return c.Respond(&tele.CallbackResponse{Text: "Ошибка: связь не найдена"}) + } + + if err := bot.accountRepo.TransferOwnership(link.ServerID, link.UserID); err != nil { + logger.Log.Error("Ownership transfer failed", zap.Error(err)) + return c.Respond(&tele.CallbackResponse{Text: "Ошибка: " + err.Error()}) + } + + // Уведомляем нового владельца + go func() { + msg := fmt.Sprintf("👑 Поздравляем!\n\nВам переданы права Владельца (OWNER) сервера %s.", link.Server.Name) + bot.b.Send(&tele.User{ID: link.User.TelegramID}, msg, tele.ModeHTML) + }() + + c.Respond(&tele.CallbackResponse{Text: "Успешно!"}) + + // Возвращаемся к списку + return bot.renderServerUsers(c, link.ServerID) + } + return nil } +// --- Вспомогательный метод рендера списка пользователей --- +func (bot *Bot) renderServerUsers(c tele.Context, serverID uuid.UUID) error { + users, err := bot.accountRepo.GetServerUsers(serverID) + if err != nil { + return c.Respond(&tele.CallbackResponse{Text: "Ошибка загрузки юзеров"}) + } + + menu := &tele.ReplyMarkup{} + var rows []tele.Row + + for _, u := range users { + roleIcon := "👤" + if u.Role == account.RoleOwner { + roleIcon = "👑" + } + if u.Role == account.RoleAdmin { + roleIcon = "⭐️" + } + + label := fmt.Sprintf("%s %s %s", roleIcon, u.User.FirstName, u.User.LastName) + // Используем ID связи + payload := fmt.Sprintf("adm_usr_%s", u.ID.String()) + + btn := menu.Data(label, payload) + rows = append(rows, menu.Row(btn)) + } + + btnBack := menu.Data("🔙 К серверам", "adm_list_servers") + rows = append(rows, menu.Row(btnBack)) + menu.Inline(rows...) + + // Для заголовка нам нужно имя сервера, но в users[0].Server оно есть (Preload), + // либо если юзеров нет (пустой сервер?), то имя не узнаем без доп запроса. + // Но пустой сервер вряд ли будет, там как минимум Owner. + serverName := "Unknown" + if len(users) > 0 { + serverName = users[0].Server.Name + } + + return c.EditOrSend(fmt.Sprintf("👥 Пользователи сервера %s:", serverName), menu, tele.ModeHTML) +} + func (bot *Bot) triggerSync(c tele.Context) error { userDB, _ := bot.accountRepo.GetUserByTelegramID(c.Sender().ID) + server, err := bot.accountRepo.GetActiveServer(userDB.ID) + if err != nil || server == nil { + return c.Respond(&tele.CallbackResponse{Text: "Нет активного сервера"}) + } + + role, _ := bot.accountRepo.GetUserRole(userDB.ID, server.ID) + if role == account.RoleOperator { + return c.Respond(&tele.CallbackResponse{Text: "⚠️ Синхронизация доступна только Админам", ShowAlert: true}) + } + c.Respond(&tele.CallbackResponse{Text: "Запускаю синхронизацию..."}) - // Запускаем в фоне, но уведомляем юзера go func() { if err := bot.syncService.SyncAllData(userDB.ID); err != nil { logger.Log.Error("Manual sync failed", zap.Error(err)) @@ -388,10 +635,9 @@ func (bot *Bot) triggerSync(c tele.Context) error { } // --- FSM: ADD SERVER FLOW --- - func (bot *Bot) startAddServerFlow(c tele.Context) error { bot.fsm.SetState(c.Sender().ID, StateAddServerURL) - return c.EditOrSend("🔗 Введите URL вашего сервера iikoRMS.\nПример: https://iiko.myrest.ru:443\n\n(Напишите 'отмена' для выхода)", tele.ModeHTML) + return c.EditOrSend("🔗 Введите URL вашего сервера iikoRMS.\nПример: https://resto.iiko.it\n\n(Напишите 'отмена' для выхода)", tele.ModeHTML) } func (bot *Bot) handleText(c tele.Context) error { @@ -399,7 +645,6 @@ func (bot *Bot) handleText(c tele.Context) error { state := bot.fsm.GetState(userID) text := strings.TrimSpace(c.Text()) - // Глобальная отмена if strings.ToLower(text) == "отмена" || strings.ToLower(text) == "/cancel" { bot.fsm.Reset(userID) return bot.renderMainMenu(c) @@ -432,14 +677,12 @@ func (bot *Bot) handleText(c tele.Context) error { ctx := bot.fsm.GetContext(userID) msg, _ := bot.b.Send(c.Sender(), "⏳ Проверяю подключение...") - // 1. Проверяем авторизацию (креды) tempClient := bot.rmsFactory.CreateClientFromRawCredentials(ctx.TempURL, ctx.TempLogin, password) if err := tempClient.Auth(); err != nil { bot.b.Delete(msg) return c.Send(fmt.Sprintf("❌ Ошибка авторизации: %v\nПроверьте логин/пароль.", err)) } - // 2. Пробуем узнать имя сервера var detectedName string info, err := rms.GetServerInfo(ctx.TempURL) if err == nil && info.ServerName != "" { @@ -448,30 +691,24 @@ func (bot *Bot) handleText(c tele.Context) error { bot.b.Delete(msg) - // Сохраняем пароль во временный контекст, он нам пригодится при финальном сохранении bot.fsm.UpdateContext(userID, func(uCtx *UserContext) { uCtx.TempPassword = password uCtx.TempServerName = detectedName }) - // Если имя нашли - предлагаем выбор if detectedName != "" { bot.fsm.SetState(userID, StateAddServerConfirmName) - menu := &tele.ReplyMarkup{} btnYes := menu.Data("✅ Да, использовать это имя", "confirm_name_yes") btnNo := menu.Data("✏️ Ввести другое", "confirm_name_no") menu.Inline(menu.Row(btnYes), menu.Row(btnNo)) - return c.Send(fmt.Sprintf("🔎 Обнаружено имя сервера: %s.\nИспользовать его?", detectedName), menu, tele.ModeHTML) } - // Если имя не нашли - просим ввести вручную bot.fsm.SetState(userID, StateAddServerInputName) - return c.Send("🏷 Введите название для этого сервера (для вашего удобства):") + return c.Send("🏷 Введите название для этого сервера:") case StateAddServerInputName: - // Пользователь ввел свое название name := text if len(name) < 3 { return c.Send("⚠️ Название слишком короткое.") @@ -490,7 +727,7 @@ func (bot *Bot) handlePhoto(c tele.Context) error { _, err = bot.rmsFactory.GetClientForUser(userDB.ID) if err != nil { - return c.Send("⛔ У вас не настроен сервер iiko.\nИспользуйте /add_server для настройки.") + return c.Send("⛔ Ошибка доступа к iiko или сервер не выбран.\nПроверьте статус сервера в меню.") } photo := c.Message().Photo @@ -540,17 +777,18 @@ func (bot *Bot) handlePhoto(c tele.Context) error { } menu := &tele.ReplyMarkup{} - btnOpen := menu.WebApp("📝 Открыть накладную", &tele.WebApp{URL: fullURL}) - menu.Inline(menu.Row(btnOpen)) + + role, _ := bot.accountRepo.GetUserRole(userDB.ID, draft.RMSServerID) + if role != account.RoleOperator { + btnOpen := menu.WebApp("📝 Открыть накладную", &tele.WebApp{URL: fullURL}) + menu.Inline(menu.Row(btnOpen)) + } else { + msgText += "\n\n(Редактирование доступно Администратору)" + } return c.Send(msgText, menu, tele.ModeHTML) } -func parseUUID(s string) uuid.UUID { - id, _ := uuid.Parse(s) - return id -} - func (bot *Bot) handleConfirmNameYes(c tele.Context) error { userID := c.Sender().ID ctx := bot.fsm.GetContext(userID) @@ -566,40 +804,34 @@ func (bot *Bot) handleConfirmNameNo(c tele.Context) error { return c.EditOrSend("🏷 Хорошо, введите желаемое название:") } -// saveServerFinal - общая логика сохранения в БД func (bot *Bot) saveServerFinal(c tele.Context, userID int64, serverName string) error { ctx := bot.fsm.GetContext(userID) - - encPass, _ := bot.cryptoManager.Encrypt(ctx.TempPassword) userDB, _ := bot.accountRepo.GetOrCreateUser(userID, c.Sender().Username, "", "") - newServer := &account.RMSServer{ - UserID: userDB.ID, - Name: serverName, - BaseURL: ctx.TempURL, - Login: ctx.TempLogin, - EncryptedPassword: encPass, - IsActive: true, + encPass, _ := bot.cryptoManager.Encrypt(ctx.TempPassword) + + server, err := bot.accountRepo.ConnectServer(userDB.ID, ctx.TempURL, ctx.TempLogin, encPass, serverName) + if err != nil { + return c.Send("Ошибка подключения сервера: " + err.Error()) } - if err := bot.accountRepo.SaveServer(newServer); err != nil { - return c.Send("Ошибка сохранения в БД: " + err.Error()) - } - - bot.accountRepo.SetActiveServer(userDB.ID, newServer.ID) bot.fsm.Reset(userID) - c.Send(fmt.Sprintf("✅ Сервер %s успешно добавлен!", serverName), tele.ModeHTML) + role, _ := bot.accountRepo.GetUserRole(userDB.ID, server.ID) + c.Send(fmt.Sprintf("✅ Сервер %s подключен!\nВаша роль: %s", server.Name, role), tele.ModeHTML) - // Auto-sync - go bot.syncService.SyncAllData(userDB.ID) + if role == account.RoleOwner { + go bot.syncService.SyncAllData(userDB.ID) + } else { + go bot.syncService.SyncAllData(userDB.ID) + } return bot.renderMainMenu(c) } func (bot *Bot) renderDeleteServerMenu(c tele.Context) error { userDB, _ := bot.accountRepo.GetUserByTelegramID(c.Sender().ID) - servers, err := bot.accountRepo.GetAllServers(userDB.ID) + servers, err := bot.accountRepo.GetAllAvailableServers(userDB.ID) if err != nil { return c.Send("Ошибка БД: " + err.Error()) } @@ -612,10 +844,23 @@ func (bot *Bot) renderDeleteServerMenu(c tele.Context) error { var rows []tele.Row for _, s := range servers { - // Кнопка удаления для каждого сервера - // Префикс do_del_server_ - btn := menu.Data(fmt.Sprintf("❌ %s", s.Name), "do_del_server_"+s.ID.String()) - rows = append(rows, menu.Row(btn)) + role, _ := bot.accountRepo.GetUserRole(userDB.ID, s.ID) + + var label string + if role == account.RoleOwner { + label = fmt.Sprintf("❌ Удалить %s (Owner)", s.Name) + } else { + label = fmt.Sprintf("🚪 Покинуть %s", s.Name) + } + + btnAction := menu.Data(label, "do_del_server_"+s.ID.String()) + + if role == account.RoleOwner || role == account.RoleAdmin { + btnInvite := menu.Data(fmt.Sprintf("📩 Invite %s", s.Name), "gen_invite_"+s.ID.String()) + rows = append(rows, menu.Row(btnAction, btnInvite)) + } else { + rows = append(rows, menu.Row(btnAction)) + } } btnBack := menu.Data("🔙 Назад к списку", "nav_servers") @@ -623,5 +868,10 @@ func (bot *Bot) renderDeleteServerMenu(c tele.Context) error { menu.Inline(rows...) - return c.EditOrSend("🗑 Удаление сервера\n\nНажмите на сервер, который хотите удалить.\nЭто действие нельзя отменить.", menu, tele.ModeHTML) + return c.EditOrSend("⚙️ Управление серверами\n\nЗдесь вы можете удалить сервер или пригласить сотрудников.", menu, tele.ModeHTML) +} + +func parseUUID(s string) uuid.UUID { + id, _ := uuid.Parse(s) + return id } diff --git a/rmser-view/project_context.md b/rmser-view/project_context.md new file mode 100644 index 0000000..bb97f3a --- /dev/null +++ b/rmser-view/project_context.md @@ -0,0 +1,7588 @@ +# =================================================================== +# Полный контекст Vue.js проекта +# Сгенерировано: 2025-12-23 08:36:21 +# =================================================================== + +Это полный дамп исходного кода Vue/Vite проекта. +Каждый файл предваряется заголовком с путём к нему. + + +# =================================================================== +# Файл: Dockerfile +# =================================================================== + +``` +# Этап 1: Сборка (Build) +FROM node:24-alpine as builder + +WORKDIR /app + +# Копируем файлы зависимостей +COPY package*.json ./ + +# Устанавливаем зависимости +RUN npm install + +# Копируем исходный код +COPY . . + +# Собираем проект (результат будет в папке dist) +# Важно: Vite подставит VITE_API_URL во время сборки. +# Мы будем использовать относительный путь /api, чтобы работал прокси Nginx. +ENV VITE_API_URL=/api +RUN npm run build + +# Этап 2: Запуск (Serve via Nginx) +FROM nginx:alpine + +# Копируем конфиг nginx (создадим его на след. шаге) +COPY nginx.conf /etc/nginx/conf.d/default.conf + +# Копируем собранные файлы из этапа сборки +COPY --from=builder /app/dist /usr/share/nginx/html + +EXPOSE 80 + +CMD ["nginx", "-g", "daemon off;"] +``` + +# =================================================================== +# Файл: eslint.config.js +# =================================================================== + +``` +import js from '@eslint/js' +import globals from 'globals' +import reactHooks from 'eslint-plugin-react-hooks' +import reactRefresh from 'eslint-plugin-react-refresh' +import tseslint from 'typescript-eslint' +import { defineConfig, globalIgnores } from 'eslint/config' + +export default defineConfig([ + globalIgnores(['dist']), + { + files: ['**/*.{ts,tsx}'], + extends: [ + js.configs.recommended, + tseslint.configs.recommended, + reactHooks.configs.flat.recommended, + reactRefresh.configs.vite, + ], + languageOptions: { + ecmaVersion: 2020, + globals: globals.browser, + }, + }, +]) + +``` + +# =================================================================== +# Файл: index.html +# =================================================================== + +``` + + + + + + + RMSer App + + + + +
+ + + + +``` + +# =================================================================== +# Файл: package-lock.json +# =================================================================== + +``` +{ + "name": "rmser-view", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "rmser-view", + "version": "0.0.0", + "dependencies": { + "@tanstack/react-query": "^5.90.12", + "@twa-dev/sdk": "^8.0.2", + "antd": "^6.1.0", + "axios": "^1.13.2", + "clsx": "^2.1.1", + "react": "^19.2.0", + "react-dom": "^19.2.0", + "react-router-dom": "^7.10.1", + "zustand": "^5.0.9" + }, + "devDependencies": { + "@eslint/js": "^9.39.1", + "@types/node": "^24.10.1", + "@types/react": "^19.2.5", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^5.1.1", + "eslint": "^9.39.1", + "eslint-plugin-react-hooks": "^7.0.1", + "eslint-plugin-react-refresh": "^0.4.24", + "globals": "^16.5.0", + "typescript": "~5.9.3", + "typescript-eslint": "^8.46.4", + "vite": "^7.2.4" + } + }, + "node_modules/@ant-design/colors": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@ant-design/colors/-/colors-8.0.0.tgz", + "integrity": "sha512-6YzkKCw30EI/E9kHOIXsQDHmMvTllT8STzjMb4K2qzit33RW2pqCJP0sk+hidBntXxE+Vz4n1+RvCTfBw6OErw==", + "license": "MIT", + "dependencies": { + "@ant-design/fast-color": "^3.0.0" + } + }, + "node_modules/@ant-design/cssinjs": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@ant-design/cssinjs/-/cssinjs-2.0.1.tgz", + "integrity": "sha512-Lw1Z4cUQxdMmTNir67gU0HCpTl5TtkKCJPZ6UBvCqzcOTl/QmMFB6qAEoj8qFl0CuZDX9qQYa3m9+rEKfaBSbA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.11.1", + "@emotion/hash": "^0.8.0", + "@emotion/unitless": "^0.7.5", + "@rc-component/util": "^1.4.0", + "clsx": "^2.1.1", + "csstype": "^3.1.3", + "stylis": "^4.3.4" + }, + "peerDependencies": { + "react": ">=16.0.0", + "react-dom": ">=16.0.0" + } + }, + "node_modules/@ant-design/cssinjs-utils": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@ant-design/cssinjs-utils/-/cssinjs-utils-2.0.2.tgz", + "integrity": "sha512-Mq3Hm6fJuQeFNKSp3+yT4bjuhVbdrsyXE2RyfpJFL0xiYNZdaJ6oFaE3zFrzmHbmvTd2Wp3HCbRtkD4fU+v2ZA==", + "license": "MIT", + "dependencies": { + "@ant-design/cssinjs": "^2.0.1", + "@babel/runtime": "^7.23.2", + "@rc-component/util": "^1.4.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/@ant-design/fast-color": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@ant-design/fast-color/-/fast-color-3.0.0.tgz", + "integrity": "sha512-eqvpP7xEDm2S7dUzl5srEQCBTXZMmY3ekf97zI+M2DHOYyKdJGH0qua0JACHTqbkRnD/KHFQP9J1uMJ/XWVzzA==", + "license": "MIT", + "engines": { + "node": ">=8.x" + } + }, + "node_modules/@ant-design/icons": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@ant-design/icons/-/icons-6.1.0.tgz", + "integrity": "sha512-KrWMu1fIg3w/1F2zfn+JlfNDU8dDqILfA5Tg85iqs1lf8ooyGlbkA+TkwfOKKgqpUmAiRY1PTFpuOU2DAIgSUg==", + "license": "MIT", + "dependencies": { + "@ant-design/colors": "^8.0.0", + "@ant-design/icons-svg": "^4.4.0", + "@rc-component/util": "^1.3.0", + "clsx": "^2.1.1" + }, + "engines": { + "node": ">=8" + }, + "peerDependencies": { + "react": ">=16.0.0", + "react-dom": ">=16.0.0" + } + }, + "node_modules/@ant-design/icons-svg": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/@ant-design/icons-svg/-/icons-svg-4.4.2.tgz", + "integrity": "sha512-vHbT+zJEVzllwP+CM+ul7reTEfBR0vgxFe7+lREAsAA7YGsYpboiq2sQNeQeRvh09GfQgs/GyFEvZpJ9cLXpXA==", + "license": "MIT" + }, + "node_modules/@ant-design/react-slick": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@ant-design/react-slick/-/react-slick-2.0.0.tgz", + "integrity": "sha512-HMS9sRoEmZey8LsE/Yo6+klhlzU12PisjrVcydW3So7RdklyEd2qehyU6a7Yp+OYN72mgsYs3NFCyP2lCPFVqg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.4", + "clsx": "^2.1.1", + "json2mq": "^0.2.0", + "throttle-debounce": "^5.0.0" + }, + "peerDependencies": { + "react": "^0.14.0 || ^15.0.1 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^0.14.0 || ^15.0.1 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.5.tgz", + "integrity": "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz", + "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.5", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-module-transforms": "^7.28.3", + "@babel/helpers": "^7.28.4", + "@babel/parser": "^7.28.5", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.28.5", + "@babel/types": "^7.28.5", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.5.tgz", + "integrity": "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.5", + "@babel/types": "^7.28.5", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", + "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.27.2", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", + "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz", + "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.28.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", + "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz", + "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.4" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz", + "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.5" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", + "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", + "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz", + "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.5.tgz", + "integrity": "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.5", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.28.5", + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.5", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz", + "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@emotion/hash": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.8.0.tgz", + "integrity": "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==", + "license": "MIT" + }, + "node_modules/@emotion/unitless": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz", + "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==", + "license": "MIT" + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", + "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.21.1", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz", + "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.7", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.3.tgz", + "integrity": "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.1", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/js": { + "version": "9.39.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.1.tgz", + "integrity": "sha512-S26Stp4zCy88tH94QbBv3XCuzRQiZ9yXofEILmglYTh/Ug/a9/umqvgFtYBAo3Lp0nsI/5/qH1CCrbdK3AP1Tw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", + "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@rc-component/async-validator": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@rc-component/async-validator/-/async-validator-5.0.4.tgz", + "integrity": "sha512-qgGdcVIF604M9EqjNF0hbUTz42bz/RDtxWdWuU5EQe3hi7M8ob54B6B35rOsvX5eSvIHIzT9iH1R3n+hk3CGfg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.24.4" + }, + "engines": { + "node": ">=14.x" + } + }, + "node_modules/@rc-component/cascader": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@rc-component/cascader/-/cascader-1.9.0.tgz", + "integrity": "sha512-2jbthe1QZrMBgtCvNKkJFjZYC3uKl4N/aYm5SsMvO3T+F+qRT1CGsSM9bXnh1rLj7jDk/GK0natShWF/jinhWQ==", + "license": "MIT", + "dependencies": { + "@rc-component/select": "~1.3.0", + "@rc-component/tree": "~1.1.0", + "@rc-component/util": "^1.4.0", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=18.0.0", + "react-dom": ">=18.0.0" + } + }, + "node_modules/@rc-component/checkbox": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rc-component/checkbox/-/checkbox-1.0.1.tgz", + "integrity": "sha512-08yTH8m+bSm8TOqbybbJ9KiAuIATti6bDs2mVeSfu4QfEnyeF6X0enHVvD1NEAyuBWEAo56QtLe++MYs2D9XiQ==", + "license": "MIT", + "dependencies": { + "@rc-component/util": "^1.3.0", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/collapse": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@rc-component/collapse/-/collapse-1.1.2.tgz", + "integrity": "sha512-ilBYk1dLLJHu5Q74dF28vwtKUYQ42ZXIIDmqTuVy4rD8JQVvkXOs+KixVNbweyuIEtJYJ7+t+9GVD9dPc6N02w==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "@rc-component/motion": "^1.1.4", + "@rc-component/util": "^1.3.0", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=18.0.0", + "react-dom": ">=18.0.0" + } + }, + "node_modules/@rc-component/color-picker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@rc-component/color-picker/-/color-picker-3.0.3.tgz", + "integrity": "sha512-V7gFF9O7o5XwIWafdbOtqI4BUUkEUkgdBwp6favy3xajMX/2dDqytFaiXlcwrpq6aRyPLp5dKLAG5RFKLXMeGA==", + "license": "MIT", + "dependencies": { + "@ant-design/fast-color": "^3.0.0", + "@rc-component/util": "^1.3.0", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/context": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@rc-component/context/-/context-2.0.1.tgz", + "integrity": "sha512-HyZbYm47s/YqtP6pKXNMjPEMaukyg7P0qVfgMLzr7YiFNMHbK2fKTAGzms9ykfGHSfyf75nBbgWw+hHkp+VImw==", + "license": "MIT", + "dependencies": { + "@rc-component/util": "^1.3.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/dialog": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@rc-component/dialog/-/dialog-1.5.1.tgz", + "integrity": "sha512-by4Sf/a3azcb89WayWuwG19/Y312xtu8N81HoVQQtnsBDylfs+dog98fTAvLinnpeoWG52m/M7QLRW6fXR3l1g==", + "license": "MIT", + "dependencies": { + "@rc-component/motion": "^1.1.3", + "@rc-component/portal": "^2.0.0", + "@rc-component/util": "^1.0.1", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=18.0.0", + "react-dom": ">=18.0.0" + } + }, + "node_modules/@rc-component/drawer": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@rc-component/drawer/-/drawer-1.3.0.tgz", + "integrity": "sha512-rE+sdXEmv2W25VBQ9daGbnb4J4hBIEKmdbj0b3xpY+K7TUmLXDIlSnoXraIbFZdGyek9WxxGKK887uRnFgI+pQ==", + "license": "MIT", + "dependencies": { + "@rc-component/motion": "^1.1.4", + "@rc-component/portal": "^2.0.0", + "@rc-component/util": "^1.2.1", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=18.0.0", + "react-dom": ">=18.0.0" + } + }, + "node_modules/@rc-component/dropdown": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rc-component/dropdown/-/dropdown-1.0.2.tgz", + "integrity": "sha512-6PY2ecUSYhDPhkNHHb4wfeAya04WhpmUSKzdR60G+kMNVUCX2vjT/AgTS0Lz0I/K6xrPMJ3enQbwVpeN3sHCgg==", + "license": "MIT", + "dependencies": { + "@rc-component/trigger": "^3.0.0", + "@rc-component/util": "^1.2.1", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=16.11.0", + "react-dom": ">=16.11.0" + } + }, + "node_modules/@rc-component/form": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@rc-component/form/-/form-1.4.0.tgz", + "integrity": "sha512-C8MN/2wIaW9hSrCCtJmcgCkWTQNIspN7ARXLFA4F8PGr8Qxk39U5pS3kRK51/bUJNhb/fEtdFnaViLlISGKI2A==", + "license": "MIT", + "dependencies": { + "@rc-component/async-validator": "^5.0.3", + "@rc-component/util": "^1.3.0", + "clsx": "^2.1.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/image": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/@rc-component/image/-/image-1.5.3.tgz", + "integrity": "sha512-/NR7QW9uCN8Ugar+xsHZOPvzPySfEhcW2/vLcr7VPRM+THZMrllMRv7LAUgW7ikR+Z67Ab67cgPp5K5YftpJsQ==", + "license": "MIT", + "dependencies": { + "@rc-component/motion": "^1.0.0", + "@rc-component/portal": "^2.0.0", + "@rc-component/util": "^1.3.0", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/input": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@rc-component/input/-/input-1.1.2.tgz", + "integrity": "sha512-Q61IMR47piUBudgixJ30CciKIy9b1H95qe7GgEKOmSJVJXvFRWJllJfQry9tif+MX2cWFXWJf/RXz4kaCeq/Fg==", + "license": "MIT", + "dependencies": { + "@rc-component/util": "^1.4.0", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=16.0.0", + "react-dom": ">=16.0.0" + } + }, + "node_modules/@rc-component/input-number": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/@rc-component/input-number/-/input-number-1.6.2.tgz", + "integrity": "sha512-Gjcq7meZlCOiWN1t1xCC+7/s85humHVokTBI7PJgTfoyw5OWF74y3e6P8PHX104g9+b54jsodFIzyaj6p8LI9w==", + "license": "MIT", + "dependencies": { + "@rc-component/mini-decimal": "^1.0.1", + "@rc-component/util": "^1.4.0", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/mentions": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@rc-component/mentions/-/mentions-1.6.0.tgz", + "integrity": "sha512-KIkQNP6habNuTsLhUv0UGEOwG67tlmE7KNIJoQZZNggEZl5lQJTytFDb69sl5CK3TDdISCTjKP3nGEBKgT61CQ==", + "license": "MIT", + "dependencies": { + "@rc-component/input": "~1.1.0", + "@rc-component/menu": "~1.2.0", + "@rc-component/textarea": "~1.1.0", + "@rc-component/trigger": "^3.0.0", + "@rc-component/util": "^1.3.0", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/menu": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@rc-component/menu/-/menu-1.2.0.tgz", + "integrity": "sha512-VWwDuhvYHSnTGj4n6bV3ISrLACcPAzdPOq3d0BzkeiM5cve8BEYfvkEhNoM0PLzv51jpcejeyrLXeMVIJ+QJlg==", + "license": "MIT", + "dependencies": { + "@rc-component/motion": "^1.1.4", + "@rc-component/overflow": "^1.0.0", + "@rc-component/trigger": "^3.0.0", + "@rc-component/util": "^1.3.0", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/mini-decimal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@rc-component/mini-decimal/-/mini-decimal-1.1.0.tgz", + "integrity": "sha512-jS4E7T9Li2GuYwI6PyiVXmxTiM6b07rlD9Ge8uGZSCz3WlzcG5ZK7g5bbuKNeZ9pgUuPK/5guV781ujdVpm4HQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.0" + }, + "engines": { + "node": ">=8.x" + } + }, + "node_modules/@rc-component/motion": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@rc-component/motion/-/motion-1.1.6.tgz", + "integrity": "sha512-aEQobs/YA0kqRvHIPjQvOytdtdRVyhf/uXAal4chBjxDu6odHckExJzjn2D+Ju1aKK6hx3pAs6BXdV9+86xkgQ==", + "license": "MIT", + "dependencies": { + "@rc-component/util": "^1.2.0", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/mutate-observer": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@rc-component/mutate-observer/-/mutate-observer-2.0.1.tgz", + "integrity": "sha512-AyarjoLU5YlxuValRi+w8JRH2Z84TBbFO2RoGWz9d8bSu0FqT8DtugH3xC3BV7mUwlmROFauyWuXFuq4IFbH+w==", + "license": "MIT", + "dependencies": { + "@rc-component/util": "^1.2.0" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/notification": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@rc-component/notification/-/notification-1.2.0.tgz", + "integrity": "sha512-OX3J+zVU7rvoJCikjrfW7qOUp7zlDeFBK2eA3SFbGSkDqo63Sl4Ss8A04kFP+fxHSxMDIS9jYVEZtU1FNCFuBA==", + "license": "MIT", + "dependencies": { + "@rc-component/motion": "^1.1.4", + "@rc-component/util": "^1.2.1", + "clsx": "^2.1.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/overflow": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@rc-component/overflow/-/overflow-1.0.0.tgz", + "integrity": "sha512-GSlBeoE0XTBi5cf3zl8Qh7Uqhn7v8RrlJ8ajeVpEkNe94HWy5l5BQ0Mwn2TVUq9gdgbfEMUmTX7tJFAg7mz0Rw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.11.1", + "@rc-component/resize-observer": "^1.0.1", + "@rc-component/util": "^1.4.0", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/pagination": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@rc-component/pagination/-/pagination-1.2.0.tgz", + "integrity": "sha512-YcpUFE8dMLfSo6OARJlK6DbHHvrxz7pMGPGmC/caZSJJz6HRKHC1RPP001PRHCvG9Z/veD039uOQmazVuLJzlw==", + "license": "MIT", + "dependencies": { + "@rc-component/util": "^1.3.0", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/picker": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@rc-component/picker/-/picker-1.8.0.tgz", + "integrity": "sha512-ek4efrIy+peC8WFJg6Lg7c+WNkykr+wUGQGBNoKmlF0K752aIJuaPcBj6p8CceT9vSJ9gOeeclQCBQIFWVDk1A==", + "license": "MIT", + "dependencies": { + "@rc-component/overflow": "^1.0.0", + "@rc-component/resize-observer": "^1.0.0", + "@rc-component/trigger": "^3.6.15", + "@rc-component/util": "^1.3.0", + "clsx": "^2.1.1" + }, + "engines": { + "node": ">=12.x" + }, + "peerDependencies": { + "date-fns": ">= 2.x", + "dayjs": ">= 1.x", + "luxon": ">= 3.x", + "moment": ">= 2.x", + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + }, + "peerDependenciesMeta": { + "date-fns": { + "optional": true + }, + "dayjs": { + "optional": true + }, + "luxon": { + "optional": true + }, + "moment": { + "optional": true + } + } + }, + "node_modules/@rc-component/portal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@rc-component/portal/-/portal-2.0.1.tgz", + "integrity": "sha512-46KYuA7Udb1LAaLIdDrfmDz3wzyeEZxIURJCn+heoQVbhtW5PQkhBSQtRus+DUdsknmTFQulxSnqrbX3CI4yXw==", + "license": "MIT", + "dependencies": { + "@rc-component/util": "^1.2.1", + "clsx": "^2.1.1" + }, + "engines": { + "node": ">=12.x" + }, + "peerDependencies": { + "react": ">=18.0.0", + "react-dom": ">=18.0.0" + } + }, + "node_modules/@rc-component/progress": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rc-component/progress/-/progress-1.0.2.tgz", + "integrity": "sha512-WZUnH9eGxH1+xodZKqdrHke59uyGZSWgj5HBM5Kwk5BrTMuAORO7VJ2IP5Qbm9aH3n9x3IcesqHHR0NWPBC7fQ==", + "license": "MIT", + "dependencies": { + "@rc-component/util": "^1.2.1", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/qrcode": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@rc-component/qrcode/-/qrcode-1.1.1.tgz", + "integrity": "sha512-LfLGNymzKdUPjXUbRP+xOhIWY4jQ+YMj5MmWAcgcAq1Ij8XP7tRmAXqyuv96XvLUBE/5cA8hLFl9eO1JQMujrA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.24.7" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/rate": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rc-component/rate/-/rate-1.0.1.tgz", + "integrity": "sha512-bkXxeBqDpl5IOC7yL7GcSYjQx9G8H+6kLYQnNZWeBYq2OYIv1MONd6mqKTjnnJYpV0cQIU2z3atdW0j1kttpTw==", + "license": "MIT", + "dependencies": { + "@rc-component/util": "^1.3.0", + "clsx": "^2.1.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/resize-observer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rc-component/resize-observer/-/resize-observer-1.0.1.tgz", + "integrity": "sha512-r+w+Mz1EiueGk1IgjB3ptNXLYSLZ5vnEfKHH+gfgj7JMupftyzvUUl3fRcMZe5uMM04x0n8+G2o/c6nlO2+Wag==", + "license": "MIT", + "dependencies": { + "@rc-component/util": "^1.2.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/segmented": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@rc-component/segmented/-/segmented-1.2.3.tgz", + "integrity": "sha512-L7G4S6zUpqHclOXK0wKKN2/VyqHa9tfDNxkoFjWOTPtQ0ROFaBwZhbf1+9sdZfIFkxJkpcShAmDOMEIBaFFqkw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.11.1", + "@rc-component/motion": "^1.1.4", + "@rc-component/util": "^1.3.0", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=16.0.0", + "react-dom": ">=16.0.0" + } + }, + "node_modules/@rc-component/select": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@rc-component/select/-/select-1.3.5.tgz", + "integrity": "sha512-A2QVOWDfRoLgHwPHrCGx1G42dYntOk+nsT6SX4ADCoagqu4bcxceJPbYvVKkfMYSIwgtfu+tDhPk3Z5gz8944g==", + "license": "MIT", + "dependencies": { + "@rc-component/overflow": "^1.0.0", + "@rc-component/trigger": "^3.0.0", + "@rc-component/util": "^1.3.0", + "@rc-component/virtual-list": "^1.0.1", + "clsx": "^2.1.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": "*", + "react-dom": "*" + } + }, + "node_modules/@rc-component/slider": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rc-component/slider/-/slider-1.0.1.tgz", + "integrity": "sha512-uDhEPU1z3WDfCJhaL9jfd2ha/Eqpdfxsn0Zb0Xcq1NGQAman0TWaR37OWp2vVXEOdV2y0njSILTMpTfPV1454g==", + "license": "MIT", + "dependencies": { + "@rc-component/util": "^1.3.0", + "clsx": "^2.1.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/steps": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@rc-component/steps/-/steps-1.2.2.tgz", + "integrity": "sha512-/yVIZ00gDYYPHSY0JP+M+s3ZvuXLu2f9rEjQqiUDs7EcYsUYrpJ/1bLj9aI9R7MBR3fu/NGh6RM9u2qGfqp+Nw==", + "license": "MIT", + "dependencies": { + "@rc-component/util": "^1.2.1", + "clsx": "^2.1.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/switch": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rc-component/switch/-/switch-1.0.3.tgz", + "integrity": "sha512-Jgi+EbOBquje/XNdofr7xbJQZPYJP+BlPfR0h+WN4zFkdtB2EWqEfvkXJWeipflwjWip0/17rNbxEAqs8hVHfw==", + "license": "MIT", + "dependencies": { + "@rc-component/util": "^1.3.0", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/table": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@rc-component/table/-/table-1.9.0.tgz", + "integrity": "sha512-cq3P9FkD+F3eglkFYhBuNlHclg+r4jY8+ZIgK7zbEFo6IwpnA77YL/Gq4ensLw9oua3zFCTA6JDu6YgBei0TxA==", + "license": "MIT", + "dependencies": { + "@rc-component/context": "^2.0.1", + "@rc-component/resize-observer": "^1.0.0", + "@rc-component/util": "^1.1.0", + "@rc-component/virtual-list": "^1.0.1", + "clsx": "^2.1.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=18.0.0", + "react-dom": ">=18.0.0" + } + }, + "node_modules/@rc-component/tabs": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@rc-component/tabs/-/tabs-1.7.0.tgz", + "integrity": "sha512-J48cs2iBi7Ho3nptBxxIqizEliUC+ExE23faspUQKGQ550vaBlv3aGF8Epv/UB1vFWeoJDTW/dNzgIU0Qj5i/w==", + "license": "MIT", + "dependencies": { + "@rc-component/dropdown": "~1.0.0", + "@rc-component/menu": "~1.2.0", + "@rc-component/motion": "^1.1.3", + "@rc-component/resize-observer": "^1.0.0", + "@rc-component/util": "^1.3.0", + "clsx": "^2.1.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/textarea": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@rc-component/textarea/-/textarea-1.1.2.tgz", + "integrity": "sha512-9rMUEODWZDMovfScIEHXWlVZuPljZ2pd1LKNjslJVitn4SldEzq5vO1CL3yy3Dnib6zZal2r2DPtjy84VVpF6A==", + "license": "MIT", + "dependencies": { + "@rc-component/input": "~1.1.0", + "@rc-component/resize-observer": "^1.0.0", + "@rc-component/util": "^1.3.0", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/tooltip": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@rc-component/tooltip/-/tooltip-1.4.0.tgz", + "integrity": "sha512-8Rx5DCctIlLI4raR0I0xHjVTf1aF48+gKCNeAAo5bmF5VoR5YED+A/XEqzXv9KKqrJDRcd3Wndpxh2hyzrTtSg==", + "license": "MIT", + "dependencies": { + "@rc-component/trigger": "^3.7.1", + "@rc-component/util": "^1.3.0", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=18.0.0", + "react-dom": ">=18.0.0" + } + }, + "node_modules/@rc-component/tour": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@rc-component/tour/-/tour-2.2.1.tgz", + "integrity": "sha512-BUCrVikGJsXli38qlJ+h2WyDD6dYxzDA9dV3o0ij6gYhAq6ooT08SUMWOikva9v4KZ2BEuluGl5bPcsjrSoBgQ==", + "license": "MIT", + "dependencies": { + "@rc-component/portal": "^2.0.0", + "@rc-component/trigger": "^3.0.0", + "@rc-component/util": "^1.3.0", + "clsx": "^2.1.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/tree": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@rc-component/tree/-/tree-1.1.0.tgz", + "integrity": "sha512-HZs3aOlvFgQdgrmURRc/f4IujiNBf4DdEeXUlkS0lPoLlx9RoqsZcF0caXIAMVb+NaWqKtGQDnrH8hqLCN5zlA==", + "license": "MIT", + "dependencies": { + "@rc-component/motion": "^1.0.0", + "@rc-component/util": "^1.2.1", + "@rc-component/virtual-list": "^1.0.1", + "clsx": "^2.1.1" + }, + "engines": { + "node": ">=10.x" + }, + "peerDependencies": { + "react": "*", + "react-dom": "*" + } + }, + "node_modules/@rc-component/tree-select": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@rc-component/tree-select/-/tree-select-1.4.0.tgz", + "integrity": "sha512-I3UAlO2hNqy9CSKc8EBaESgnmKk2QaRzuZ2XHZGFCgsSMkGl06mdF97sVfROM02YIb64ocgLKefsjE0Ch4ocwQ==", + "license": "MIT", + "dependencies": { + "@rc-component/select": "~1.3.0", + "@rc-component/tree": "~1.1.0", + "@rc-component/util": "^1.4.0", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": "*", + "react-dom": "*" + } + }, + "node_modules/@rc-component/trigger": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/@rc-component/trigger/-/trigger-3.7.1.tgz", + "integrity": "sha512-+YNP8FywxKJpdqzlAp6TN8UbSK6YsQtIs3kI13mHfm87qi3qUd5Q9AGW8Unfv76kXFUSu7U7D0FygRsGH+6MiA==", + "license": "MIT", + "dependencies": { + "@rc-component/motion": "^1.1.4", + "@rc-component/portal": "^2.0.0", + "@rc-component/resize-observer": "^1.0.0", + "@rc-component/util": "^1.2.1", + "clsx": "^2.1.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=18.0.0", + "react-dom": ">=18.0.0" + } + }, + "node_modules/@rc-component/upload": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@rc-component/upload/-/upload-1.1.0.tgz", + "integrity": "sha512-LIBV90mAnUE6VK5N4QvForoxZc4XqEYZimcp7fk+lkE4XwHHyJWxpIXQQwMU8hJM+YwBbsoZkGksL1sISWHQxw==", + "license": "MIT", + "dependencies": { + "@rc-component/util": "^1.3.0", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/util": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@rc-component/util/-/util-1.6.0.tgz", + "integrity": "sha512-YbjuIVAm8InCnXVoA4n6G+uh31yESTxQ6fSY2frZ2/oMSvktoB+bumFUfNN7RKh7YeOkZgOvN2suGtEDhJSX0A==", + "license": "MIT", + "dependencies": { + "is-mobile": "^5.0.0", + "react-is": "^18.2.0" + }, + "peerDependencies": { + "react": ">=18.0.0", + "react-dom": ">=18.0.0" + } + }, + "node_modules/@rc-component/virtual-list": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rc-component/virtual-list/-/virtual-list-1.0.2.tgz", + "integrity": "sha512-uvTol/mH74FYsn5loDGJxo+7kjkO4i+y4j87Re1pxJBs0FaeuMuLRzQRGaXwnMcV1CxpZLi2Z56Rerj2M00fjQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.20.0", + "@rc-component/resize-observer": "^1.0.1", + "@rc-component/util": "^1.4.0", + "clsx": "^2.1.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.53", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.53.tgz", + "integrity": "sha512-vENRlFU4YbrwVqNDZ7fLvy+JR1CRkyr01jhSiDpE1u6py3OMzQfztQU2jxykW3ALNxO4kSlqIDeYyD0Y9RcQeQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.53.3.tgz", + "integrity": "sha512-mRSi+4cBjrRLoaal2PnqH82Wqyb+d3HsPUN/W+WslCXsZsyHa9ZeQQX/pQsZaVIWDkPcpV6jJ+3KLbTbgnwv8w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.53.3.tgz", + "integrity": "sha512-CbDGaMpdE9sh7sCmTrTUyllhrg65t6SwhjlMJsLr+J8YjFuPmCEjbBSx4Z/e4SmDyH3aB5hGaJUP2ltV/vcs4w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.53.3.tgz", + "integrity": "sha512-Nr7SlQeqIBpOV6BHHGZgYBuSdanCXuw09hon14MGOLGmXAFYjx1wNvquVPmpZnl0tLjg25dEdr4IQ6GgyToCUA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.53.3.tgz", + "integrity": "sha512-DZ8N4CSNfl965CmPktJ8oBnfYr3F8dTTNBQkRlffnUarJ2ohudQD17sZBa097J8xhQ26AwhHJ5mvUyQW8ddTsQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.53.3.tgz", + "integrity": "sha512-yMTrCrK92aGyi7GuDNtGn2sNW+Gdb4vErx4t3Gv/Tr+1zRb8ax4z8GWVRfr3Jw8zJWvpGHNpss3vVlbF58DZ4w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.53.3.tgz", + "integrity": "sha512-lMfF8X7QhdQzseM6XaX0vbno2m3hlyZFhwcndRMw8fbAGUGL3WFMBdK0hbUBIUYcEcMhVLr1SIamDeuLBnXS+Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.53.3.tgz", + "integrity": "sha512-k9oD15soC/Ln6d2Wv/JOFPzZXIAIFLp6B+i14KhxAfnq76ajt0EhYc5YPeX6W1xJkAdItcVT+JhKl1QZh44/qw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.53.3.tgz", + "integrity": "sha512-vTNlKq+N6CK/8UktsrFuc+/7NlEYVxgaEgRXVUVK258Z5ymho29skzW1sutgYjqNnquGwVUObAaxae8rZ6YMhg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.53.3.tgz", + "integrity": "sha512-RGrFLWgMhSxRs/EWJMIFM1O5Mzuz3Xy3/mnxJp/5cVhZ2XoCAxJnmNsEyeMJtpK+wu0FJFWz+QF4mjCA7AUQ3w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.53.3.tgz", + "integrity": "sha512-kASyvfBEWYPEwe0Qv4nfu6pNkITLTb32p4yTgzFCocHnJLAHs+9LjUu9ONIhvfT/5lv4YS5muBHyuV84epBo/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.53.3.tgz", + "integrity": "sha512-JiuKcp2teLJwQ7vkJ95EwESWkNRFJD7TQgYmCnrPtlu50b4XvT5MOmurWNrCj3IFdyjBQ5p9vnrX4JM6I8OE7g==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.53.3.tgz", + "integrity": "sha512-EoGSa8nd6d3T7zLuqdojxC20oBfNT8nexBbB/rkxgKj5T5vhpAQKKnD+h3UkoMuTyXkP5jTjK/ccNRmQrPNDuw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.53.3.tgz", + "integrity": "sha512-4s+Wped2IHXHPnAEbIB0YWBv7SDohqxobiiPA1FIWZpX+w9o2i4LezzH/NkFUl8LRci/8udci6cLq+jJQlh+0g==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.53.3.tgz", + "integrity": "sha512-68k2g7+0vs2u9CxDt5ktXTngsxOQkSEV/xBbwlqYcUrAVh6P9EgMZvFsnHy4SEiUl46Xf0IObWVbMvPrr2gw8A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.53.3.tgz", + "integrity": "sha512-VYsFMpULAz87ZW6BVYw3I6sWesGpsP9OPcyKe8ofdg9LHxSbRMd7zrVrr5xi/3kMZtpWL/wC+UIJWJYVX5uTKg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.53.3.tgz", + "integrity": "sha512-3EhFi1FU6YL8HTUJZ51imGJWEX//ajQPfqWLI3BQq4TlvHy4X0MOr5q3D2Zof/ka0d5FNdPwZXm3Yyib/UEd+w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.53.3.tgz", + "integrity": "sha512-eoROhjcc6HbZCJr+tvVT8X4fW3/5g/WkGvvmwz/88sDtSJzO7r/blvoBDgISDiCjDRZmHpwud7h+6Q9JxFwq1Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.53.3.tgz", + "integrity": "sha512-OueLAWgrNSPGAdUdIjSWXw+u/02BRTcnfw9PN41D2vq/JSEPnJnVuBgw18VkN8wcd4fjUs+jFHVM4t9+kBSNLw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.53.3.tgz", + "integrity": "sha512-GOFuKpsxR/whszbF/bzydebLiXIHSgsEUp6M0JI8dWvi+fFa1TD6YQa4aSZHtpmh2/uAlj/Dy+nmby3TJ3pkTw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.53.3.tgz", + "integrity": "sha512-iah+THLcBJdpfZ1TstDFbKNznlzoxa8fmnFYK4V67HvmuNYkVdAywJSoteUszvBQ9/HqN2+9AZghbajMsFT+oA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.53.3.tgz", + "integrity": "sha512-J9QDiOIZlZLdcot5NXEepDkstocktoVjkaKUtqzgzpt2yWjGlbYiKyp05rWwk4nypbYUNoFAztEgixoLaSETkg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.53.3.tgz", + "integrity": "sha512-UhTd8u31dXadv0MopwGgNOBpUVROFKWVQgAg5N1ESyCz8AuBcMqm4AuTjrwgQKGDfoFuz02EuMRHQIw/frmYKQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@tanstack/query-core": { + "version": "5.90.12", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.90.12.tgz", + "integrity": "sha512-T1/8t5DhV/SisWjDnaiU2drl6ySvsHj1bHBCWNXd+/T+Hh1cf6JodyEYMd5sgwm+b/mETT4EV3H+zCVczCU5hg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/react-query": { + "version": "5.90.12", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.90.12.tgz", + "integrity": "sha512-graRZspg7EoEaw0a8faiUASCyJrqjKPdqJ9EwuDRUF9mEYJ1YPczI9H+/agJ0mOJkPCJDk0lsz5QTrLZ/jQ2rg==", + "license": "MIT", + "dependencies": { + "@tanstack/query-core": "5.90.12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^18 || ^19" + } + }, + "node_modules/@twa-dev/sdk": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@twa-dev/sdk/-/sdk-8.0.2.tgz", + "integrity": "sha512-Pp5GxnxP2blboVZFiM9aWjs4cb8IpW3x2jP3kLOMvIqy0jzNUTuFHkwHtx+zEvh/UcF2F+wmS8G6ebIA0XPXcg==", + "license": "MIT", + "dependencies": { + "@twa-dev/types": "^8.0.1" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0" + } + }, + "node_modules/@twa-dev/types": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@twa-dev/types/-/types-8.0.2.tgz", + "integrity": "sha512-ICQ6n4NaUPPzV3/GzflVQS6Nnu5QX2vr9OlOG8ZkFf3rSJXzRKazrLAbZlVhCPPWkIW3MMuELPsE6tByrA49qA==", + "license": "MIT" + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "24.10.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.2.tgz", + "integrity": "sha512-WOhQTZ4G8xZ1tjJTvKOpyEVSGgOTvJAfDK3FNFgELyaTpzhdgHVHeqW8V+UJvzF5BT+/B54T/1S2K6gd9c7bbA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/@types/react": { + "version": "19.2.7", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.7.tgz", + "integrity": "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==", + "devOptional": true, + "license": "MIT", + "peer": true, + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", + "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.2.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.49.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.49.0.tgz", + "integrity": "sha512-JXij0vzIaTtCwu6SxTh8qBc66kmf1xs7pI4UOiMDFVct6q86G0Zs7KRcEoJgY3Cav3x5Tq0MF5jwgpgLqgKG3A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "8.49.0", + "@typescript-eslint/type-utils": "8.49.0", + "@typescript-eslint/utils": "8.49.0", + "@typescript-eslint/visitor-keys": "8.49.0", + "ignore": "^7.0.0", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.49.0", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.49.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.49.0.tgz", + "integrity": "sha512-N9lBGA9o9aqb1hVMc9hzySbhKibHmB+N3IpoShyV6HyQYRGIhlrO5rQgttypi+yEeKsKI4idxC8Jw6gXKD4THA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@typescript-eslint/scope-manager": "8.49.0", + "@typescript-eslint/types": "8.49.0", + "@typescript-eslint/typescript-estree": "8.49.0", + "@typescript-eslint/visitor-keys": "8.49.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.49.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.49.0.tgz", + "integrity": "sha512-/wJN0/DKkmRUMXjZUXYZpD1NEQzQAAn9QWfGwo+Ai8gnzqH7tvqS7oNVdTjKqOcPyVIdZdyCMoqN66Ia789e7g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.49.0", + "@typescript-eslint/types": "^8.49.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.49.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.49.0.tgz", + "integrity": "sha512-npgS3zi+/30KSOkXNs0LQXtsg9ekZ8OISAOLGWA/ZOEn0ZH74Ginfl7foziV8DT+D98WfQ5Kopwqb/PZOaIJGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.49.0", + "@typescript-eslint/visitor-keys": "8.49.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.49.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.49.0.tgz", + "integrity": "sha512-8prixNi1/6nawsRYxet4YOhnbW+W9FK/bQPxsGB1D3ZrDzbJ5FXw5XmzxZv82X3B+ZccuSxo/X8q9nQ+mFecWA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.49.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.49.0.tgz", + "integrity": "sha512-KTExJfQ+svY8I10P4HdxKzWsvtVnsuCifU5MvXrRwoP2KOlNZ9ADNEWWsQTJgMxLzS5VLQKDjkCT/YzgsnqmZg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.49.0", + "@typescript-eslint/typescript-estree": "8.49.0", + "@typescript-eslint/utils": "8.49.0", + "debug": "^4.3.4", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.49.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.49.0.tgz", + "integrity": "sha512-e9k/fneezorUo6WShlQpMxXh8/8wfyc+biu6tnAqA81oWrEic0k21RHzP9uqqpyBBeBKu4T+Bsjy9/b8u7obXQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.49.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.49.0.tgz", + "integrity": "sha512-jrLdRuAbPfPIdYNppHJ/D0wN+wwNfJ32YTAm10eJVsFmrVpXQnDWBn8niCSMlWjvml8jsce5E/O+86IQtTbJWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.49.0", + "@typescript-eslint/tsconfig-utils": "8.49.0", + "@typescript-eslint/types": "8.49.0", + "@typescript-eslint/visitor-keys": "8.49.0", + "debug": "^4.3.4", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.49.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.49.0.tgz", + "integrity": "sha512-N3W7rJw7Rw+z1tRsHZbK395TWSYvufBXumYtEGzypgMUthlg0/hmCImeA8hgO2d2G4pd7ftpxxul2J8OdtdaFA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.7.0", + "@typescript-eslint/scope-manager": "8.49.0", + "@typescript-eslint/types": "8.49.0", + "@typescript-eslint/typescript-estree": "8.49.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.49.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.49.0.tgz", + "integrity": "sha512-LlKaciDe3GmZFphXIc79THF/YYBugZ7FS1pO581E/edlVVNbZKDy93evqmrfQ9/Y4uN0vVhX4iuchq26mK/iiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.49.0", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.1.2.tgz", + "integrity": "sha512-EcA07pHJouywpzsoTUqNh5NwGayl2PPVEJKUSinGGSxFGYn+shYbqMGBg6FXDqgXum9Ou/ecb+411ssw8HImJQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.5", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.53", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.18.0" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, + "license": "MIT", + "peer": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/antd": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/antd/-/antd-6.1.0.tgz", + "integrity": "sha512-RIe4W5saaL9SWgvqCcvz6LZta/KwT50B0YF7xYiWVZh0Gqfw2rJAsOMcp202Hxgm+YiyoSp4QqqvexKhuGGarw==", + "license": "MIT", + "dependencies": { + "@ant-design/colors": "^8.0.0", + "@ant-design/cssinjs": "^2.0.1", + "@ant-design/cssinjs-utils": "^2.0.2", + "@ant-design/fast-color": "^3.0.0", + "@ant-design/icons": "^6.1.0", + "@ant-design/react-slick": "~2.0.0", + "@babel/runtime": "^7.28.4", + "@rc-component/cascader": "~1.9.0", + "@rc-component/checkbox": "~1.0.1", + "@rc-component/collapse": "~1.1.2", + "@rc-component/color-picker": "~3.0.3", + "@rc-component/dialog": "~1.5.1", + "@rc-component/drawer": "~1.3.0", + "@rc-component/dropdown": "~1.0.2", + "@rc-component/form": "~1.4.0", + "@rc-component/image": "~1.5.2", + "@rc-component/input": "~1.1.2", + "@rc-component/input-number": "~1.6.2", + "@rc-component/mentions": "~1.6.0", + "@rc-component/menu": "~1.2.0", + "@rc-component/motion": "~1.1.6", + "@rc-component/mutate-observer": "^2.0.1", + "@rc-component/notification": "~1.2.0", + "@rc-component/pagination": "~1.2.0", + "@rc-component/picker": "~1.8.0", + "@rc-component/progress": "~1.0.2", + "@rc-component/qrcode": "~1.1.1", + "@rc-component/rate": "~1.0.1", + "@rc-component/resize-observer": "^1.0.1", + "@rc-component/segmented": "~1.2.3", + "@rc-component/select": "~1.3.2", + "@rc-component/slider": "~1.0.1", + "@rc-component/steps": "~1.2.2", + "@rc-component/switch": "~1.0.3", + "@rc-component/table": "~1.9.0", + "@rc-component/tabs": "~1.7.0", + "@rc-component/textarea": "~1.1.2", + "@rc-component/tooltip": "~1.4.0", + "@rc-component/tour": "~2.2.1", + "@rc-component/tree": "~1.1.0", + "@rc-component/tree-select": "~1.4.0", + "@rc-component/trigger": "^3.7.1", + "@rc-component/upload": "~1.1.0", + "@rc-component/util": "^1.4.0", + "clsx": "^2.1.1", + "dayjs": "^1.11.11", + "scroll-into-view-if-needed": "^3.1.0", + "throttle-debounce": "^5.0.2" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/ant-design" + }, + "peerDependencies": { + "react": ">=18.0.0", + "react-dom": ">=18.0.0" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz", + "integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.4", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/baseline-browser-mapping": { + "version": "2.9.6", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.6.tgz", + "integrity": "sha512-v9BVVpOTLB59C9E7aSnmIF8h7qRsFpx+A2nugVMTszEOMcfjlZMsXRm4LF23I3Z9AJxc8ANpIvzbzONoX9VJlg==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.js" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/browserslist": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "peer": true, + "dependencies": { + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001760", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001760.tgz", + "integrity": "sha512-7AAMPcueWELt1p3mi13HR/LHH0TJLT11cnwDJEs3xA4+CK/PLKeO9Kl1oru24htkyUKtkGCvAx4ohB0Ttry8Dw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/compute-scroll-into-view": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-3.1.1.tgz", + "integrity": "sha512-VRhuHOLoKYOy4UbilLbUzbYg93XLjv2PncJC50EuTWPA3gaja1UjBsUP/D/9/juV3vQFr6XBEzn9KCAHdUvOHw==", + "license": "MIT" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cookie": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "license": "MIT" + }, + "node_modules/dayjs": { + "version": "1.11.19", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.19.tgz", + "integrity": "sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==", + "license": "MIT", + "peer": true + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.267", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.267.tgz", + "integrity": "sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==", + "dev": true, + "license": "ISC" + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.39.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.1.tgz", + "integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.1", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.39.1", + "@eslint/plugin-kit": "^0.4.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.0.1.tgz", + "integrity": "sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.24.4", + "@babel/parser": "^7.24.4", + "hermes-parser": "^0.25.1", + "zod": "^3.25.0 || ^4.0.0", + "zod-validation-error": "^3.5.0 || ^4.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" + } + }, + "node_modules/eslint-plugin-react-refresh": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.24.tgz", + "integrity": "sha512-nLHIW7TEq3aLrEYWpVaJ1dRgFR+wLDPN8e8FpYAql/bMV2oBEfC37K0gLEGgv9fy66juNShSMV8OkTqzltcG/w==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "eslint": ">=8.40" + } + }, + "node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true, + "license": "ISC" + }, + "node_modules/follow-redirects": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "16.5.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-16.5.0.tgz", + "integrity": "sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hermes-estree": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz", + "integrity": "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==", + "dev": true, + "license": "MIT" + }, + "node_modules/hermes-parser": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.25.1.tgz", + "integrity": "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hermes-estree": "0.25.1" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-mobile": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-mobile/-/is-mobile-5.0.0.tgz", + "integrity": "sha512-Tz/yndySvLAEXh+Uk8liFCxOwVH6YutuR74utvOcu7I9Di+DwM0mtdPVZNaVvvBUM2OXxne/NhOs1zAO7riusQ==", + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/json2mq": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/json2mq/-/json2mq-0.2.0.tgz", + "integrity": "sha512-SzoRg7ux5DWTII9J2qkrZrqV1gt+rTaoufMxEzXbS26Uid0NwaJd123HcoB80TgubEppxxIGdNxCx50fEoEWQA==", + "license": "MIT", + "dependencies": { + "string-convert": "^0.2.0" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-releases": { + "version": "2.0.27", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", + "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/react": { + "version": "19.2.1", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.1.tgz", + "integrity": "sha512-DGrYcCWK7tvYMnWh79yrPHt+vdx9tY+1gPZa7nJQtO/p8bLTDaHp4dzwEhQB7pZ4Xe3ok4XKuEPrVuc+wlpkmw==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.1.tgz", + "integrity": "sha512-ibrK8llX2a4eOskq1mXKu/TGZj9qzomO+sNfO98M6d9zIPOEhlBkMkBUBLd1vgS0gQsLDBzA+8jJBVXDnfHmJg==", + "license": "MIT", + "peer": true, + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.1" + } + }, + "node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "license": "MIT" + }, + "node_modules/react-refresh": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.18.0.tgz", + "integrity": "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-router": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.10.1.tgz", + "integrity": "sha512-gHL89dRa3kwlUYtRQ+m8NmxGI6CgqN+k4XyGjwcFoQwwCWF6xXpOCUlDovkXClS0d0XJN/5q7kc5W3kiFEd0Yw==", + "license": "MIT", + "dependencies": { + "cookie": "^1.0.1", + "set-cookie-parser": "^2.6.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, + "node_modules/react-router-dom": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.10.1.tgz", + "integrity": "sha512-JNBANI6ChGVjA5bwsUIwJk7LHKmqB4JYnYfzFwyp2t12Izva11elds2jx7Yfoup2zssedntwU0oZ5DEmk5Sdaw==", + "license": "MIT", + "dependencies": { + "react-router": "7.10.1" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/rollup": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.53.3.tgz", + "integrity": "sha512-w8GmOxZfBmKknvdXU1sdM9NHcoQejwF/4mNgj2JuEEdRaHwwF12K7e9eXn1nLZ07ad+du76mkVsyeb2rKGllsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.53.3", + "@rollup/rollup-android-arm64": "4.53.3", + "@rollup/rollup-darwin-arm64": "4.53.3", + "@rollup/rollup-darwin-x64": "4.53.3", + "@rollup/rollup-freebsd-arm64": "4.53.3", + "@rollup/rollup-freebsd-x64": "4.53.3", + "@rollup/rollup-linux-arm-gnueabihf": "4.53.3", + "@rollup/rollup-linux-arm-musleabihf": "4.53.3", + "@rollup/rollup-linux-arm64-gnu": "4.53.3", + "@rollup/rollup-linux-arm64-musl": "4.53.3", + "@rollup/rollup-linux-loong64-gnu": "4.53.3", + "@rollup/rollup-linux-ppc64-gnu": "4.53.3", + "@rollup/rollup-linux-riscv64-gnu": "4.53.3", + "@rollup/rollup-linux-riscv64-musl": "4.53.3", + "@rollup/rollup-linux-s390x-gnu": "4.53.3", + "@rollup/rollup-linux-x64-gnu": "4.53.3", + "@rollup/rollup-linux-x64-musl": "4.53.3", + "@rollup/rollup-openharmony-arm64": "4.53.3", + "@rollup/rollup-win32-arm64-msvc": "4.53.3", + "@rollup/rollup-win32-ia32-msvc": "4.53.3", + "@rollup/rollup-win32-x64-gnu": "4.53.3", + "@rollup/rollup-win32-x64-msvc": "4.53.3", + "fsevents": "~2.3.2" + } + }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + }, + "node_modules/scroll-into-view-if-needed": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/scroll-into-view-if-needed/-/scroll-into-view-if-needed-3.1.0.tgz", + "integrity": "sha512-49oNpRjWRvnU8NyGVmUaYG4jtTkNonFZI86MmGRDqBphEK2EXT9gdEUoQPZhuBM8yWHxCWbobltqYO5M4XrUvQ==", + "license": "MIT", + "dependencies": { + "compute-scroll-into-view": "^3.0.2" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/set-cookie-parser": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz", + "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==", + "license": "MIT" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/string-convert": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/string-convert/-/string-convert-0.2.1.tgz", + "integrity": "sha512-u/1tdPl4yQnPBjnVrmdLo9gtuLvELKsAoRapekWggdiQNvvvum+jYF329d84NAa660KQw7pB2n36KrIKVoXa3A==", + "license": "MIT" + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/stylis": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.6.tgz", + "integrity": "sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==", + "license": "MIT" + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/throttle-debounce": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/throttle-debounce/-/throttle-debounce-5.0.2.tgz", + "integrity": "sha512-B71/4oyj61iNH0KeCamLuE2rmKuTO5byTOSVwECM5FA7TiAiAW+UqTKZ9ERueC4qvgSttUhdmq1mXC3kJqGX7A==", + "license": "MIT", + "engines": { + "node": ">=12.22" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/ts-api-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", + "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-eslint": { + "version": "8.49.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.49.0.tgz", + "integrity": "sha512-zRSVH1WXD0uXczCXw+nsdjGPUdx4dfrs5VQoHnUWmv1U3oNlAKv4FUNdLDhVUg+gYn+a5hUESqch//Rv5wVhrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.49.0", + "@typescript-eslint/parser": "8.49.0", + "@typescript-eslint/typescript-estree": "8.49.0", + "@typescript-eslint/utils": "8.49.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "dev": true, + "license": "MIT" + }, + "node_modules/update-browserslist-db": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.2.tgz", + "integrity": "sha512-E85pfNzMQ9jpKkA7+TJAi4TJN+tBCuWh5rUcS/sv6cFi+1q9LYDwDI5dpUL0u/73EElyQ8d3TEaeW4sPedBqYA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/vite": { + "version": "7.2.7", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.2.7.tgz", + "integrity": "sha512-ITcnkFeR3+fI8P1wMgItjGrR10170d8auB4EpMLPqmx6uxElH3a/hHGQabSHKdqd4FXWO1nFIp9rRn7JQ34ACQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.1.13.tgz", + "integrity": "sha512-AvvthqfqrAhNH9dnfmrfKzX5upOdjUVJYFqNSlkmGf64gRaTzlPwz99IHYnVs28qYAybvAlBV+H7pn0saFY4Ig==", + "dev": true, + "license": "MIT", + "peer": true, + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-validation-error": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-4.0.2.tgz", + "integrity": "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "zod": "^3.25.0 || ^4.0.0" + } + }, + "node_modules/zustand": { + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.9.tgz", + "integrity": "sha512-ALBtUj0AfjJt3uNRQoL1tL2tMvj6Gp/6e39dnfT6uzpelGru8v1tPOGBzayOWbPJvujM8JojDk3E1LxeFisBNg==", + "license": "MIT", + "engines": { + "node": ">=12.20.0" + }, + "peerDependencies": { + "@types/react": ">=18.0.0", + "immer": ">=9.0.6", + "react": ">=18.0.0", + "use-sync-external-store": ">=1.2.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + }, + "use-sync-external-store": { + "optional": true + } + } + } + } +} + +``` + +# =================================================================== +# Файл: package.json +# =================================================================== + +``` +{ + "name": "rmser-view", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc -b && vite build", + "lint": "eslint .", + "preview": "vite preview" + }, + "dependencies": { + "@tanstack/react-query": "^5.90.12", + "@twa-dev/sdk": "^8.0.2", + "antd": "^6.1.0", + "axios": "^1.13.2", + "clsx": "^2.1.1", + "react": "^19.2.0", + "react-dom": "^19.2.0", + "react-router-dom": "^7.10.1", + "zustand": "^5.0.9" + }, + "devDependencies": { + "@eslint/js": "^9.39.1", + "@types/node": "^24.10.1", + "@types/react": "^19.2.5", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^5.1.1", + "eslint": "^9.39.1", + "eslint-plugin-react-hooks": "^7.0.1", + "eslint-plugin-react-refresh": "^0.4.24", + "globals": "^16.5.0", + "typescript": "~5.9.3", + "typescript-eslint": "^8.46.4", + "vite": "^7.2.4" + } +} + +``` + +# =================================================================== +# Файл: src/App.tsx +# =================================================================== + +``` +import { useEffect, useState } from "react"; +import { BrowserRouter, Routes, Route, Navigate } from "react-router-dom"; +import { Result, Button } from "antd"; +import { Providers } from "./components/layout/Providers"; +import { AppLayout } from "./components/layout/AppLayout"; +import { OcrLearning } from "./pages/OcrLearning"; +import { InvoiceDraftPage } from "./pages/InvoiceDraftPage"; +import { DraftsList } from "./pages/DraftsList"; +import { SettingsPage } from "./pages/SettingsPage"; +import { UNAUTHORIZED_EVENT } from "./services/api"; + +// Компонент-заглушка для внешних браузеров +const NotInTelegramScreen = () => ( +
+ + Перейти в бота + + } + /> +
+); + +function App() { + const [isUnauthorized, setIsUnauthorized] = useState(false); + const tg = window.Telegram?.WebApp; + + // Проверяем, есть ли данные от Telegram + const isInTelegram = !!tg?.initData; + + useEffect(() => { + const handleUnauthorized = () => setIsUnauthorized(true); + window.addEventListener(UNAUTHORIZED_EVENT, handleUnauthorized); + + if (tg) { + tg.expand(); // Расширяем приложение на все окно + } + + return () => { + window.removeEventListener(UNAUTHORIZED_EVENT, handleUnauthorized); + }; + }, [tg]); + + // Если открыто не в Telegram — блокируем всё + if (!isInTelegram) { + return ; + } + + // Если бэкенд вернул 401 + if (isUnauthorized) { + return ( +
+ +
+ ); + } + + return ( + + + + }> + } /> + } /> + } /> + } /> + } /> + } /> + + + + + ); +} + +export default App; + +``` + +# =================================================================== +# Файл: src/components/invoices/CreateContainerModal.tsx +# =================================================================== + +``` +import React, { useState } from 'react'; +import { Modal, Form, Input, InputNumber, Button, message } from 'antd'; +import { api } from '../../services/api'; +import type { ProductContainer } from '../../services/types'; + +interface Props { + visible: boolean; + onCancel: () => void; + productId: string; + productBaseUnit: string; + // Callback возвращает уже полный объект с ID от сервера + onSuccess: (container: ProductContainer) => void; +} + +export const CreateContainerModal: React.FC = ({ + visible, onCancel, productId, productBaseUnit, onSuccess +}) => { + const [loading, setLoading] = useState(false); + const [form] = Form.useForm(); + + const handleOk = async () => { + try { + const values = await form.validateFields(); + setLoading(true); + + // 1. Отправляем запрос на БЭКЕНД + const res = await api.createContainer({ + product_id: productId, + name: values.name, + count: values.count + }); + + message.success('Фасовка создана'); + + // 2. БЭКЕНД вернул ID. Теперь мы собираем объект для UI + // Мы не придумываем ID сами, мы берем res.container_id + const newContainer: ProductContainer = { + id: res.container_id, // <--- ID от сервера + name: values.name, + count: values.count + }; + + // 3. Возвращаем полный объект родителю + onSuccess(newContainer); + + form.resetFields(); + } catch { + message.error('Ошибка создания фасовки'); + } finally { + setLoading(false); + } + }; + + return ( + Отмена, + , + ]} + > +
+ + + + + + +
+
+ ); +}; +``` + +# =================================================================== +# Файл: src/components/invoices/DraftItemRow.tsx +# =================================================================== + +``` +import React, { useMemo, useState, useEffect } from "react"; +import { + Card, + Flex, + InputNumber, + Typography, + Select, + Tag, + Button, + Divider, + Modal, + Popconfirm, +} from "antd"; +import { + SyncOutlined, + PlusOutlined, + WarningFilled, + DeleteOutlined, +} from "@ant-design/icons"; +import { CatalogSelect } from "../ocr/CatalogSelect"; +import { CreateContainerModal } from "./CreateContainerModal"; +import type { + DraftItem, + UpdateDraftItemRequest, + ProductSearchResult, + ProductContainer, + Recommendation, +} from "../../services/types"; + +const { Text } = Typography; + +interface Props { + item: DraftItem; + onUpdate: (itemId: string, changes: UpdateDraftItemRequest) => void; + onDelete: (itemId: string) => void; + isUpdating: boolean; + recommendations?: Recommendation[]; +} + +export const DraftItemRow: React.FC = ({ + item, + onUpdate, + onDelete, + isUpdating, + recommendations = [], +}) => { + const [isModalOpen, setIsModalOpen] = useState(false); + + // State Input + const [localQuantity, setLocalQuantity] = useState( + item.quantity?.toString() ?? null + ); + const [localPrice, setLocalPrice] = useState( + item.price?.toString() ?? null + ); + + // Sync Effect + useEffect(() => { + const serverQty = item.quantity; + const currentLocal = parseFloat(localQuantity?.replace(",", ".") || "0"); + if (Math.abs(serverQty - currentLocal) > 0.001) + setLocalQuantity(serverQty.toString().replace(".", ",")); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [item.quantity]); + + useEffect(() => { + const serverPrice = item.price; + const currentLocal = parseFloat(localPrice?.replace(",", ".") || "0"); + if (Math.abs(serverPrice - currentLocal) > 0.001) + setLocalPrice(serverPrice.toString().replace(".", ",")); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [item.price]); + + // Product Logic + const [searchedProduct, setSearchedProduct] = + useState(null); + const [addedContainers, setAddedContainers] = useState< + Record + >({}); + + const activeProduct = useMemo(() => { + if (searchedProduct && searchedProduct.id === item.product_id) + return searchedProduct; + return item.product as unknown as ProductSearchResult | undefined; + }, [searchedProduct, item.product, item.product_id]); + + const containers = useMemo(() => { + if (!activeProduct) return []; + const baseContainers = activeProduct.containers || []; + const manuallyAdded = addedContainers[activeProduct.id] || []; + const combined = [...baseContainers]; + manuallyAdded.forEach((c) => { + if (!combined.find((existing) => existing.id === c.id)) combined.push(c); + }); + return combined; + }, [activeProduct, addedContainers]); + + const baseUom = + activeProduct?.main_unit?.name || activeProduct?.measure_unit || "ед."; + + const containerOptions = useMemo(() => { + if (!activeProduct) return []; + const opts = [ + { value: "BASE_UNIT", label: `Базовая (${baseUom})` }, + ...containers.map((c) => ({ + value: c.id, + label: `${c.name} (=${Number(c.count)} ${baseUom})`, + })), + ]; + if ( + item.container_id && + item.container && + !containers.find((c) => c.id === item.container_id) + ) { + opts.push({ + value: item.container.id, + label: `${item.container.name} (=${Number( + item.container.count + )} ${baseUom})`, + }); + } + return opts; + }, [activeProduct, containers, baseUom, item.container_id, item.container]); + + // --- WARNING LOGIC --- + const activeWarning = useMemo(() => { + if (!item.product_id) return null; + return recommendations.find((r) => r.ProductID === item.product_id); + }, [item.product_id, recommendations]); + + const showWarningModal = () => { + if (!activeWarning) return; + Modal.warning({ + title: "Внимание: проблемный товар", + content: ( +
+

+ {activeWarning.ProductName} +

+

{activeWarning.Reason}

+

+ {activeWarning.Type} +

+
+ ), + okText: "Понятно", + maskClosable: true, + }); + }; + + // --- Helpers --- + const parseToNum = (val: string | null | undefined): number => { + if (!val) return 0; + return parseFloat(val.replace(",", ".")); + }; + + const getUpdatePayload = ( + overrides: Partial + ): UpdateDraftItemRequest => { + const currentQty = + localQuantity !== null ? parseToNum(localQuantity) : item.quantity; + const currentPrice = + localPrice !== null ? parseToNum(localPrice) : item.price; + + return { + product_id: item.product_id || undefined, + container_id: item.container_id, + quantity: currentQty ?? 1, + price: currentPrice ?? 0, + ...overrides, + }; + }; + + // --- Handlers --- + const handleProductChange = ( + prodId: string, + productObj?: ProductSearchResult + ) => { + if (productObj) setSearchedProduct(productObj); + onUpdate( + item.id, + getUpdatePayload({ product_id: prodId, container_id: null }) + ); + }; + + const handleContainerChange = (val: string) => { + const newVal = val === "BASE_UNIT" ? null : val; + onUpdate(item.id, getUpdatePayload({ container_id: newVal })); + }; + + const handleBlur = (field: "quantity" | "price") => { + const localVal = field === "quantity" ? localQuantity : localPrice; + if (localVal === null) return; + const numVal = parseToNum(localVal); + if (numVal !== item[field]) { + onUpdate(item.id, getUpdatePayload({ [field]: numVal })); + } + }; + + const handleContainerCreated = (newContainer: ProductContainer) => { + setIsModalOpen(false); + if (activeProduct) { + setAddedContainers((prev) => ({ + ...prev, + [activeProduct.id]: [...(prev[activeProduct.id] || []), newContainer], + })); + } + onUpdate(item.id, getUpdatePayload({ container_id: newContainer.id })); + }; + + const cardBorderColor = !item.product_id + ? "#ffa39e" + : item.is_matched + ? "#b7eb8f" + : "#d9d9d9"; + const uiSum = parseToNum(localQuantity) * parseToNum(localPrice); + + return ( + <> + + + +
+ {/* Показываем raw_name только если это OCR строка. Если создана вручную и пустая - плейсхолдер */} + + {item.raw_name || "Новая позиция"} + + {item.raw_amount > 0 && ( + + (чек: {item.raw_amount} x {item.raw_price}) + + )} +
+
+ {isUpdating && } + + {/* Warning Icon */} + {activeWarning && ( + + )} + + {!item.product_id && ( + + ? + + )} + + {/* Кнопка удаления */} + onDelete(item.id)} + okText="Да" + cancelText="Нет" + placement="left" + > +
+
+ + + + {activeProduct && ( + ({ + value: c.id, + label: `${c.name} (=${Number(c.count)} ${baseUom})` + })) + ]} + // Рендерим кнопку добавления внизу выпадающего списка + dropdownRender={(menu) => ( + <> + {menu} + + + + )} + /> + + )} + + {/* Количество */} +
+
+ Количество (в выбранных единицах): +
+
+ + {currentUomName} +
+
+ + {/* Кнопка сохранения */} + +
+ + {/* Модальное окно создания фасовки */} + {activeProduct && ( + setIsModalOpen(false)} + productId={activeProduct.id} + productBaseUnit={baseUom} + onSuccess={handleContainerCreated} + /> + )} +
+ ); +}; +``` + +# =================================================================== +# Файл: src/components/ocr/CatalogSelect.tsx +# =================================================================== + +``` +import React, { useState, useEffect, useRef } from 'react'; +import { Select, Spin } from 'antd'; +import { api } from '../../services/api'; +import type { CatalogItem, ProductSearchResult } from '../../services/types'; + +interface Props { + value?: string; + onChange?: (value: string, productObj?: ProductSearchResult) => void; + disabled?: boolean; + initialProduct?: CatalogItem | ProductSearchResult; +} + +// Интерфейс для элемента выпадающего списка +interface SelectOption { + label: string; + value: string; + data: ProductSearchResult; +} + +export const CatalogSelect: React.FC = ({ value, onChange, disabled, initialProduct }) => { + const [options, setOptions] = useState([]); + const [fetching, setFetching] = useState(false); + + const fetchRef = useRef(null); + + useEffect(() => { + if (initialProduct && initialProduct.id === value) { + const name = initialProduct.name; + const code = initialProduct.code; + setOptions([{ + label: code ? `${name} [${code}]` : name, + value: initialProduct.id, + data: initialProduct as ProductSearchResult + }]); + } + }, [initialProduct, value]); + + const fetchProducts = async (search: string) => { + if (!search) return; + setFetching(true); + setOptions([]); + + try { + const results = await api.searchProducts(search); + const newOptions = results.map(item => ({ + label: item.code ? `${item.name} [${item.code}]` : item.name, + value: item.id, + data: item + })); + setOptions(newOptions); + } catch (e) { + console.error(e); + } finally { + setFetching(false); + } + }; + + const handleSearch = (val: string) => { + if (fetchRef.current !== null) { + window.clearTimeout(fetchRef.current); + } + fetchRef.current = window.setTimeout(() => { + fetchProducts(val); + }, 500); + }; + + // Исправлено: добавлен | undefined для option + const handleChange = (val: string, option: SelectOption | SelectOption[] | undefined) => { + if (onChange) { + // В single mode option - это один объект или undefined + const opt = Array.isArray(option) ? option[0] : option; + onChange(val, opt?.data); + } + }; + + return ( + } + style={{ marginBottom: 12 }} + value={searchText} + onChange={e => setSearchText(e.target.value)} + allowClear + /> + + }} + pagination={{ pageSize: 10, size: "small", simple: true }} + renderItem={(item) => { + // Унификация полей (только snake_case) + const rawName = item.raw_name || 'Без названия'; + const product = item.product; + const productName = product?.name || 'Товар не найден'; + const qty = item.quantity || 1; + + // Логика отображения Единицы или Фасовки + const container = item.container; + let displayUnit = ''; + + if (container) { + // Если есть фасовка: "Пачка 180г" + displayUnit = container.name; + } else { + // Иначе базовая ед.: "кг" + displayUnit = product?.measure_unit || 'ед.'; + } + + return ( + +
+ Чек + {rawName} +
+
+ + + {productName} + + x {qty} {displayUnit} + + +
+
+ ); + }} + /> + + ); +}; +``` + +# =================================================================== +# Файл: src/components/recommendations/RecommendationCard.tsx +# =================================================================== + +``` +import React from 'react'; +import { Card, Tag, Typography, Button } from 'antd'; +import { WarningOutlined, InfoCircleOutlined } from '@ant-design/icons'; +import type { Recommendation } from '../../services/types'; + +const { Text, Paragraph } = Typography; + +interface Props { + item: Recommendation; +} + +export const RecommendationCard: React.FC = ({ item }) => { + // Выбираем цвет тега в зависимости от типа проблемы + const getTagColor = (type: string) => { + switch (type) { + case 'UNUSED_IN_RECIPES': return 'volcano'; + case 'NO_INCOMING': return 'gold'; + default: return 'blue'; + } + }; + + const getIcon = (type: string) => { + return type === 'UNUSED_IN_RECIPES' ? : ; + }; + + return ( + + {getIcon(item.Type)} + {item.ProductName} + + } + extra={{item.Type}} + style={{ marginBottom: 12, boxShadow: '0 2px 8px rgba(0,0,0,0.05)' }} + > + + {item.Reason} + +
+ + {new Date(item.CreatedAt).toLocaleDateString()} + + {/* Кнопка действия (заглушка на будущее) */} + +
+
+ ); +}; +``` + +# =================================================================== +# Файл: src/hooks/useOcr.ts +# =================================================================== + +``` +import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; +import { api } from '../services/api'; +import type { MatchRequest, ProductMatch, CatalogItem, UnmatchedItem } from '../services/types'; +import { message } from 'antd'; + +export const useOcr = () => { + const queryClient = useQueryClient(); + + const catalogQuery = useQuery({ + queryKey: ['catalog'], + queryFn: api.getCatalogItems, + staleTime: 1000 * 60 * 5, + }); + + const matchesQuery = useQuery({ + queryKey: ['matches'], + queryFn: api.getMatches, + }); + + const unmatchedQuery = useQuery({ + queryKey: ['unmatched'], + queryFn: api.getUnmatched, + staleTime: 0, + }); + + const createMatchMutation = useMutation({ + // Теперь типы совпадают, any не нужен + mutationFn: (newMatch: MatchRequest) => api.createMatch(newMatch), + onSuccess: () => { + message.success('Связь сохранена'); + queryClient.invalidateQueries({ queryKey: ['matches'] }); + queryClient.invalidateQueries({ queryKey: ['unmatched'] }); + }, + onError: () => { + message.error('Ошибка при сохранении'); + }, + }); + + return { + catalog: catalogQuery.data || [], + matches: matchesQuery.data || [], + unmatched: unmatchedQuery.data || [], + isLoading: catalogQuery.isPending || matchesQuery.isPending, + isError: catalogQuery.isError || matchesQuery.isError, + createMatch: createMatchMutation.mutate, + isCreating: createMatchMutation.isPending, + }; +}; +``` + +# =================================================================== +# Файл: src/hooks/useRecommendations.ts +# =================================================================== + +``` +import { useQuery } from '@tanstack/react-query'; +import { api } from '../services/api'; +import type { Recommendation } from '../services/types'; + +export const useRecommendations = () => { + return useQuery({ + queryKey: ['recommendations'], + queryFn: api.getRecommendations, + // Обновлять данные каждые 30 секунд, если вкладка активна + refetchInterval: 30000, + }); +}; +``` + +# =================================================================== +# Файл: src/main.tsx +# =================================================================== + +``` +import React from 'react' +import ReactDOM from 'react-dom/client' +import App from './App.tsx' +// Если есть глобальные стили, они подключаются тут. +// Если файла index.css нет, убери эту строку. +// import './index.css' + +ReactDOM.createRoot(document.getElementById('root')!).render( + + + , +) +``` + +# =================================================================== +# Файл: src/pages/Dashboard.tsx +# =================================================================== + +``` +import React from 'react'; +import { Typography, Row, Col, Statistic, Spin, Alert, Empty } from 'antd'; +import { useRecommendations } from '../hooks/useRecommendations'; +import { RecommendationCard } from '../components/recommendations/RecommendationCard'; + +const { Title } = Typography; + +export const Dashboard: React.FC = () => { + const { data: recommendations, isPending, isError, error } = useRecommendations(); + + if (isPending) { + return ( +
+ +
+ ); + } + + if (isError) { + return ( + + ); + } + + // Группировка для статистики + const unusedCount = recommendations?.filter(r => r.Type === 'UNUSED_IN_RECIPES').length || 0; + const noIncomingCount = recommendations?.filter(r => r.Type === 'NO_INCOMING').length || 0; + + return ( +
+ Сводка проблем + + {/* Блок статистики */} + + + + + + + + + + Рекомендации ({recommendations?.length}) + + {recommendations && recommendations.length > 0 ? ( + recommendations.map((rec) => ( + + )) + ) : ( + + )} +
+ ); +}; +``` + +# =================================================================== +# Файл: src/pages/DraftsList.tsx +# =================================================================== + +``` +import React from 'react'; +import { useQuery } from '@tanstack/react-query'; +import { List, Typography, Tag, Spin, Empty } from 'antd'; +import { useNavigate } from 'react-router-dom'; +import { ArrowRightOutlined } from '@ant-design/icons'; +import { api } from '../services/api'; + +const { Title, Text } = Typography; + +export const DraftsList: React.FC = () => { + const navigate = useNavigate(); + + const { data: drafts, isLoading, isError } = useQuery({ + queryKey: ['drafts'], + queryFn: api.getDrafts, + refetchOnWindowFocus: true + }); + + const getStatusTag = (status: string) => { + switch (status) { + case 'PROCESSING': return Обработка; + case 'READY_TO_VERIFY': return Проверка; + case 'COMPLETED': return Готово; + case 'ERROR': return Ошибка; + case 'CANCELED': return Отменен; + default: return {status}; + } + }; + + if (isLoading) { + return
; + } + + if (isError) { + return
Ошибка загрузки списка
; + } + + return ( +
+ Черновики накладных + + {(!drafts || drafts.length === 0) ? ( + + ) : ( + ( + navigate(`/invoice/${item.id}`)} + > +
+
+ + {item.document_number || 'Без номера'} + + {getStatusTag(item.status)} +
+ +
+ {new Date(item.date_incoming).toLocaleDateString()} + {item.items_count} поз. +
+ +
+ + {item.total_sum.toLocaleString('ru-RU', { style: 'currency', currency: 'RUB' })} + + +
+
+
+ )} + /> + )} +
+ ); +}; +``` + +# =================================================================== +# Файл: src/pages/InvoiceDraftPage.tsx +# =================================================================== + +``` +import React, { useEffect, useMemo, useState } from "react"; +import { useParams, useNavigate } from "react-router-dom"; +import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; +import { + Spin, + Alert, + Button, + Form, + Select, + DatePicker, + Input, + Typography, + message, + Row, + Col, + Affix, + Modal, + Tag, +} from "antd"; +import { + ArrowLeftOutlined, + CheckOutlined, + DeleteOutlined, + ExclamationCircleFilled, + RestOutlined, + PlusOutlined, +} from "@ant-design/icons"; +import dayjs from "dayjs"; +import { api } from "../services/api"; +import { DraftItemRow } from "../components/invoices/DraftItemRow"; +import type { + UpdateDraftItemRequest, + CommitDraftRequest, +} from "../services/types"; + +const { Text } = Typography; +const { TextArea } = Input; +const { confirm } = Modal; + +export const InvoiceDraftPage: React.FC = () => { + const { id } = useParams<{ id: string }>(); + const navigate = useNavigate(); + const queryClient = useQueryClient(); + const [form] = Form.useForm(); + + const [updatingItems, setUpdatingItems] = useState>(new Set()); + + // --- ЗАПРОСЫ --- + + const dictQuery = useQuery({ + queryKey: ["dictionaries"], + queryFn: api.getDictionaries, + staleTime: 1000 * 60 * 5, + }); + + const recommendationsQuery = useQuery({ + queryKey: ["recommendations"], + queryFn: api.getRecommendations, + }); + + const draftQuery = useQuery({ + queryKey: ["draft", id], + queryFn: () => api.getDraft(id!), + enabled: !!id, + refetchInterval: (query) => { + const status = query.state.data?.status; + return status === "PROCESSING" ? 3000 : false; + }, + }); + + const draft = draftQuery.data; + const stores = dictQuery.data?.stores || []; + const suppliers = dictQuery.data?.suppliers || []; + + // --- МУТАЦИИ --- + + const updateItemMutation = useMutation({ + mutationFn: (vars: { itemId: string; payload: UpdateDraftItemRequest }) => + api.updateDraftItem(id!, vars.itemId, vars.payload), + onMutate: async ({ itemId }) => { + setUpdatingItems((prev) => new Set(prev).add(itemId)); + }, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ["draft", id] }); + }, + onError: () => { + message.error("Не удалось сохранить строку"); + }, + onSettled: (_data, _err, vars) => { + setUpdatingItems((prev) => { + const next = new Set(prev); + next.delete(vars.itemId); + return next; + }); + }, + }); + + // ДОБАВЛЕНО: Добавление строки + const addItemMutation = useMutation({ + mutationFn: () => api.addDraftItem(id!), + onSuccess: () => { + message.success("Строка добавлена"); + queryClient.invalidateQueries({ queryKey: ["draft", id] }); + // Можно сделать скролл вниз, но пока оставим как есть + }, + onError: () => { + message.error("Ошибка создания строки"); + }, + }); + + // ДОБАВЛЕНО: Удаление строки + const deleteItemMutation = useMutation({ + mutationFn: (itemId: string) => api.deleteDraftItem(id!, itemId), + onSuccess: () => { + message.success("Строка удалена"); + queryClient.invalidateQueries({ queryKey: ["draft", id] }); + }, + onError: () => { + message.error("Ошибка удаления строки"); + }, + }); + + const commitMutation = useMutation({ + mutationFn: (payload: CommitDraftRequest) => api.commitDraft(id!, payload), + onSuccess: (data) => { + message.success(`Накладная ${data.document_number} создана!`); + navigate("/invoices"); + }, + onError: () => { + message.error("Ошибка при создании накладной"); + }, + }); + + const deleteDraftMutation = useMutation({ + mutationFn: () => api.deleteDraft(id!), + onSuccess: () => { + if (draft?.status === "CANCELED") { + message.info("Черновик удален окончательно"); + navigate("/invoices"); + } else { + message.warning("Черновик отменен"); + queryClient.invalidateQueries({ queryKey: ["draft", id] }); + } + }, + onError: () => { + message.error("Ошибка при удалении"); + }, + }); + + // --- ЭФФЕКТЫ --- + useEffect(() => { + if (draft) { + const currentValues = form.getFieldsValue(); + if (!currentValues.store_id && draft.store_id) + form.setFieldValue("store_id", draft.store_id); + if (!currentValues.supplier_id && draft.supplier_id) + form.setFieldValue("supplier_id", draft.supplier_id); + if (!currentValues.comment && draft.comment) + form.setFieldValue("comment", draft.comment); + if (!currentValues.date_incoming) + form.setFieldValue( + "date_incoming", + draft.date_incoming ? dayjs(draft.date_incoming) : dayjs() + ); + } + }, [draft, form]); + + // --- ХЕЛПЕРЫ --- + const totalSum = useMemo(() => { + return ( + draft?.items.reduce( + (acc, item) => acc + Number(item.quantity) * Number(item.price), + 0 + ) || 0 + ); + }, [draft?.items]); + + const invalidItemsCount = useMemo(() => { + return draft?.items.filter((i) => !i.product_id).length || 0; + }, [draft?.items]); + + const handleItemUpdate = ( + itemId: string, + changes: UpdateDraftItemRequest + ) => { + updateItemMutation.mutate({ itemId, payload: changes }); + }; + + const handleCommit = async () => { + try { + const values = await form.validateFields(); + + if (invalidItemsCount > 0) { + message.warning( + `Осталось ${invalidItemsCount} нераспознанных товаров!` + ); + return; + } + + commitMutation.mutate({ + date_incoming: values.date_incoming.format("YYYY-MM-DD"), + store_id: values.store_id, + supplier_id: values.supplier_id, + comment: values.comment || "", + }); + } catch { + message.error("Заполните обязательные поля (Склад, Поставщик)"); + } + }; + + const isCanceled = draft?.status === "CANCELED"; + + const handleDelete = () => { + confirm({ + title: isCanceled ? "Удалить окончательно?" : "Отменить черновик?", + icon: , + content: isCanceled + ? "Черновик пропадет из списка навсегда." + : 'Черновик получит статус "Отменен", но останется в списке.', + okText: isCanceled ? "Удалить навсегда" : "Отменить", + okType: "danger", + cancelText: "Назад", + onOk() { + deleteDraftMutation.mutate(); + }, + }); + }; + + // --- RENDER --- + const showSpinner = + draftQuery.isLoading || + (draft?.status === "PROCESSING" && + (!draft?.items || draft.items.length === 0)); + + if (showSpinner) { + return ( +
+ +
+ ); + } + + if (draftQuery.isError || !draft) { + return ; + } + + return ( +
+ {/* Header */} +
+
+
+ + +
+ + {/* Form: Склады и Поставщики */} +
+
+ + + + + + + + + ({ label: s.name, value: s.id }))} + size="middle" + showSearch + filterOption={(input, option) => + (option?.label ?? "") + .toLowerCase() + .includes(input.toLowerCase()) + } + /> + + +