From 88620f3fb6eb96cbfab2178ddd4e7432c68363d1 Mon Sep 17 00:00:00 2001 From: SERTY Date: Mon, 2 Feb 2026 13:53:38 +0300 Subject: [PATCH] =?UTF-8?q?0202-=D1=84=D0=B8=D0=BD=D0=B8=D1=88=20=D0=BF?= =?UTF-8?q?=D0=B5=D1=80=D0=B5=D0=B4=20=D0=B4=D0=B5=D1=81=D0=BA=D1=82=D0=BE?= =?UTF-8?q?=D0=BF=D0=BE=D0=BC=20=D0=BF=D0=B5=D1=80=D0=B5=D1=81=D1=87=D0=B5?= =?UTF-8?q?=D1=82=20=D0=BF=D0=BE=D0=BF=D1=80=D0=B0=D0=B2=D0=B8=D0=BB=20?= =?UTF-8?q?=D1=80=D0=B5=D0=B4=D0=B0=D0=BA=D1=82=D0=B8=D1=80=D0=BE=D0=B2?= =?UTF-8?q?=D0=B0=D0=BD=D0=B8=D0=B5=20=D1=81=20=D0=BF=D0=B5=D1=80=D0=B5?= =?UTF-8?q?=D0=BF=D1=80=D0=BE=D0=B2=D0=B5=D0=B4=D0=B5=D0=BD=D0=B8=D0=B5?= =?UTF-8?q?=D0=BC=20=D0=B3=D0=B0=D0=BB=D0=BA=D0=B0=20=D0=B0=D0=B2=D1=82?= =?UTF-8?q?=D0=BE=D0=BF=D1=80=D0=BE=D0=B2=D0=B5=D0=B4=D0=B5=D0=BD=D0=B8?= =?UTF-8?q?=D1=8F=20=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D0=B0=D0=B5=D1=82=20?= =?UTF-8?q?=D1=80=D0=B5=D0=BA=D0=BE=D0=BC=D0=B5=D0=BD=D0=B4=D0=B0=D1=86?= =?UTF-8?q?=D0=B8=D0=B8=20=D0=BF=D0=BE=D1=87=D0=B8=D0=BD=D0=B8=D0=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cmd/main.go | 49 +- internal/domain/account/entity.go | 17 + internal/domain/drafts/entity.go | 10 +- internal/domain/invoices/entity.go | 1 + internal/domain/recommendations/entity.go | 21 +- .../repository/account/postgres.go | 101 +- .../repository/drafts/postgres.go | 11 +- .../repository/invoices/postgres.go | 14 + .../repository/recommendations/postgres.go | 165 +- internal/infrastructure/rms/client.go | 162 +- internal/services/drafts/service.go | 210 +- internal/services/invoices/service.go | 43 +- internal/services/recommend/service.go | 28 +- internal/services/sync/service.go | 107 +- internal/services/worker/sync_worker.go | 136 + internal/transport/http/handlers/drafts.go | 54 +- internal/transport/http/handlers/invoices.go | 20 + .../http/handlers/recommendations.go | 37 +- internal/transport/http/handlers/settings.go | 108 +- internal/transport/telegram/bot.go | 241 +- internal/transport/telegram/fsm.go | 9 +- ...2040746_add_sync_fields_to_rms_servers.sql | 17 + ...51336_add_server_id_to_recommendations.sql | 12 + pkg/logger/logger.go | 50 +- rmser-view/project_context.md | 10659 ---------------- rmser-view/src/App.tsx | 54 +- .../src/components/OperatorRestricted.tsx | 90 + .../components/common/ExcelPreviewModal.tsx | 53 +- .../src/components/invoices/DraftEditor.tsx | 53 +- .../src/components/invoices/InvoiceViewer.tsx | 6 +- .../src/components/settings/SyncBlock.tsx | 76 + rmser-view/src/hooks/useUserRole.ts | 63 + rmser-view/src/pages/DraftsList.tsx | 57 +- rmser-view/src/pages/SettingsPage.tsx | 243 +- rmser-view/src/services/api.ts | 10 +- rmser-view/src/services/types.ts | 5 + rmser-view/src/utils/calculations.ts | 75 +- 37 files changed, 1905 insertions(+), 11162 deletions(-) create mode 100644 internal/services/worker/sync_worker.go create mode 100644 migrations/20250202040746_add_sync_fields_to_rms_servers.sql create mode 100644 migrations/20250202051336_add_server_id_to_recommendations.sql delete mode 100644 rmser-view/project_context.md create mode 100644 rmser-view/src/components/OperatorRestricted.tsx create mode 100644 rmser-view/src/components/settings/SyncBlock.tsx create mode 100644 rmser-view/src/hooks/useUserRole.ts diff --git a/cmd/main.go b/cmd/main.go index a2d9e6b..e61a1cd 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -1,6 +1,7 @@ package main import ( + "context" "log" "os" "time" @@ -17,8 +18,8 @@ import ( "rmser/internal/services/auth" "rmser/internal/transport/http/middleware" - "rmser/internal/transport/ws" tgBot "rmser/internal/transport/telegram" + "rmser/internal/transport/ws" // Repositories accountPkg "rmser/internal/infrastructure/repository/account" @@ -43,6 +44,7 @@ import ( photosServicePkg "rmser/internal/services/photos" recServicePkg "rmser/internal/services/recommend" "rmser/internal/services/sync" + "rmser/internal/services/worker" // Handlers "rmser/internal/transport/http/handlers" @@ -100,13 +102,21 @@ func main() { ykClient := yookassa.NewClient(cfg.YooKassa.ShopID, cfg.YooKassa.SecretKey) billingService := billingServicePkg.NewService(billingRepo, accountRepo, ykClient) - syncService := sync.NewService(rmsFactory, accountRepo, catalogRepo, recipesRepo, invoicesRepo, opsRepo, supplierRepo) + syncService := sync.NewService(rmsFactory, cryptoManager, accountRepo, catalogRepo, recipesRepo, invoicesRepo, opsRepo, supplierRepo) + + // Создаем сервис рекомендаций до SyncWorker, так как он нужен для обновления рекомендаций recService := recServicePkg.NewService(recRepo) + + // 6.1 SyncWorker для фоновой синхронизации + syncWorker := worker.NewSyncWorker(syncService, accountRepo, rmsFactory, recService, logger.Log) + workerCtx, workerCancel := context.WithCancel(context.Background()) + go syncWorker.Run(workerCtx) + defer workerCancel() ocrService := ocrServicePkg.NewService(ocrRepo, catalogRepo, draftsRepo, accountRepo, photosRepo, pyClient, cfg.App.StoragePath) // Устанавливаем DevIDs для OCR сервиса ocrService.SetDevIDs(cfg.App.DevIDs) draftsService := draftsServicePkg.NewService(draftsRepo, ocrRepo, catalogRepo, accountRepo, supplierRepo, photosRepo, invoicesRepo, rmsFactory, billingService) - invoicesService := invoicesServicePkg.NewService(invoicesRepo, draftsRepo, supplierRepo, rmsFactory) + invoicesService := invoicesServicePkg.NewService(invoicesRepo, draftsRepo, supplierRepo, accountRepo, rmsFactory) photosService := photosServicePkg.NewService(photosRepo, draftsRepo, accountRepo) // 7. WebSocket сервер для desktop авторизации @@ -121,7 +131,7 @@ func main() { billingHandler := handlers.NewBillingHandler(billingService) ocrHandler := handlers.NewOCRHandler(ocrService) photosHandler := handlers.NewPhotosHandler(photosService) - recommendHandler := handlers.NewRecommendationsHandler(recService) + recommendHandler := handlers.NewRecommendationsHandler(recService, accountRepo) settingsHandler := handlers.NewSettingsHandler(accountRepo, catalogRepo) settingsHandler.SetRMSFactory(rmsFactory) invoicesHandler := handlers.NewInvoiceHandler(invoicesService, syncService) @@ -215,6 +225,7 @@ func main() { api.GET("/ocr/search", ocrHandler.SearchProducts) // Invoices + api.GET("/invoices/stats", invoicesHandler.GetStats) api.GET("/invoices/:id", invoicesHandler.GetInvoice) api.POST("/invoices/sync", invoicesHandler.SyncInvoices) @@ -225,8 +236,36 @@ func main() { // Запускаем в горутине, чтобы не держать соединение go func() { if err := syncService.SyncAllData(userID, force); err != nil { - logger.Log.Error("Manual sync failed", zap.Error(err)) + logger.Log.Error("Manual sync failed", + zap.String("user_id", userID.String()), + zap.Error(err)) + return } + + // Обновляем рекомендации после успешной синхронизации + // Получаем активный сервер пользователя + server, err := accountRepo.GetActiveServer(userID) + if err != nil { + logger.Log.Error("Ошибка получения активного сервера для обновления рекомендаций", + zap.String("user_id", userID.String()), + zap.Error(err)) + return + } + if server != nil { + if err := recService.RefreshRecommendations(server.ID); err != nil { + logger.Log.Error("Ошибка обновления рекомендаций после ручной синхронизации", + zap.String("user_id", userID.String()), + zap.String("server_id", server.ID.String()), + zap.Error(err)) + } + if err := accountRepo.UpdateLastSync(server.ID); err != nil { + logger.Log.Error("Ошибка обновления времени синхронизации", + zap.String("user_id", userID.String()), + zap.String("server_id", server.ID.String()), + zap.Error(err)) + } + } + }() c.JSON(200, gin.H{"status": "sync_started", "message": "Синхронизация запущена в фоне"}) }) diff --git a/internal/domain/account/entity.go b/internal/domain/account/entity.go index 043d795..5be275d 100644 --- a/internal/domain/account/entity.go +++ b/internal/domain/account/entity.go @@ -4,6 +4,7 @@ import ( "time" "github.com/google/uuid" + "gorm.io/gorm" ) // Роли пользователей @@ -75,6 +76,14 @@ type RMSServer struct { // Stats InvoiceCount int `gorm:"default:0" json:"invoice_count"` + // Sync settings + SyncInterval int `gorm:"default:360" json:"sync_interval"` // Интервал синхронизации в минутах (default: 6 часов) + LastSyncAt *time.Time `json:"last_sync_at"` // Время последней успешной синхронизации + LastActivityAt *time.Time `json:"last_activity_at"` // Время последнего действия пользователя + + // Soft delete + DeletedAt gorm.DeletedAt `gorm:"index" json:"-"` + CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` } @@ -134,4 +143,12 @@ type Repository interface { // SetMuteDraftNotifications включает/выключает уведомления для пользователя SetMuteDraftNotifications(userID, serverID uuid.UUID, mute bool) error + + // === Синхронизация и активность === + // UpdateLastActivity обновляет время последней активности пользователя на сервере + UpdateLastActivity(serverID uuid.UUID) error + // UpdateLastSync обновляет время последней успешной синхронизации + UpdateLastSync(serverID uuid.UUID) error + // GetServersForSync возвращает серверы, готовые для синхронизации + GetServersForSync(idleThreshold time.Duration) ([]RMSServer, error) } diff --git a/internal/domain/drafts/entity.go b/internal/domain/drafts/entity.go index 6f7bdda..2edf795 100644 --- a/internal/domain/drafts/entity.go +++ b/internal/domain/drafts/entity.go @@ -82,6 +82,12 @@ type DraftInvoiceItem struct { IsMatched bool `gorm:"default:false" json:"is_matched"` } +// LinkedDraftInfo содержит информацию о связанном черновике +type LinkedDraftInfo struct { + DraftID uuid.UUID + PhotoURL string +} + type Repository interface { Create(draft *DraftInvoice) error GetByID(id uuid.UUID) (*DraftInvoice, error) @@ -102,6 +108,6 @@ type Repository interface { // GetActive возвращает активные черновики для СЕРВЕРА (а не юзера) GetActive(serverID uuid.UUID) ([]DraftInvoice, error) - // GetRMSInvoiceIDToPhotoURLMap возвращает мапу rms_invoice_id -> sender_photo_url для сервера, где rms_invoice_id не NULL - GetRMSInvoiceIDToPhotoURLMap(serverID uuid.UUID) (map[uuid.UUID]string, error) + // GetLinkedDraftsMap возвращает мапу rms_invoice_id -> LinkedDraftInfo для сервера, где rms_invoice_id не NULL + GetLinkedDraftsMap(serverID uuid.UUID) (map[uuid.UUID]LinkedDraftInfo, error) } diff --git a/internal/domain/invoices/entity.go b/internal/domain/invoices/entity.go index 32ec187..4b280ce 100644 --- a/internal/domain/invoices/entity.go +++ b/internal/domain/invoices/entity.go @@ -47,4 +47,5 @@ type Repository interface { GetByPeriod(serverID uuid.UUID, from, to time.Time) ([]Invoice, error) SaveInvoices(invoices []Invoice) error CountRecent(serverID uuid.UUID, days int) (int64, error) + GetStats(serverID uuid.UUID) (total int64, lastMonth int64, last24h int64, err error) } diff --git a/internal/domain/recommendations/entity.go b/internal/domain/recommendations/entity.go index 9872280..8598999 100644 --- a/internal/domain/recommendations/entity.go +++ b/internal/domain/recommendations/entity.go @@ -19,6 +19,7 @@ const ( // Recommendation - Результат анализа type Recommendation struct { ID uuid.UUID `gorm:"type:uuid;primary_key;default:gen_random_uuid()"` + RMSServerID uuid.UUID `gorm:"type:uuid;not null;index"` Type string `gorm:"type:varchar(50);index"` ProductID uuid.UUID `gorm:"type:uuid;index"` ProductName string `gorm:"type:varchar(255)"` @@ -29,15 +30,15 @@ type Recommendation struct { // Repository отвечает за аналитические выборки и хранение результатов type Repository interface { - // Методы анализа (возвращают список структур, но не пишут в БД) - FindUnusedGoods() ([]Recommendation, error) - FindNoIncomingIngredients(days int) ([]Recommendation, error) - FindStaleGoods(days int) ([]Recommendation, error) - FindDishesInRecipes() ([]Recommendation, error) - FindPurchasedButUnused(days int) ([]Recommendation, error) - FindUsageWithoutPurchase(days int) ([]Recommendation, error) + // Методы анализа — добавить serverID + FindUnusedGoods(serverID uuid.UUID) ([]Recommendation, error) + FindNoIncomingIngredients(serverID uuid.UUID, days int) ([]Recommendation, error) + FindStaleGoods(serverID uuid.UUID, days int) ([]Recommendation, error) + FindDishesInRecipes(serverID uuid.UUID) ([]Recommendation, error) + FindPurchasedButUnused(serverID uuid.UUID, days int) ([]Recommendation, error) + FindUsageWithoutPurchase(serverID uuid.UUID, days int) ([]Recommendation, error) - // Методы "Кэша" в БД - SaveAll(items []Recommendation) error // Удаляет старые и пишет новые - GetAll() ([]Recommendation, error) + // Методы хранения — добавить serverID + SaveAll(serverID uuid.UUID, items []Recommendation) error + GetAll(serverID uuid.UUID) ([]Recommendation, error) } diff --git a/internal/infrastructure/repository/account/postgres.go b/internal/infrastructure/repository/account/postgres.go index 155bcdc..9d6a6c1 100644 --- a/internal/infrastructure/repository/account/postgres.go +++ b/internal/infrastructure/repository/account/postgres.go @@ -7,8 +7,10 @@ import ( "time" "rmser/internal/domain/account" + "rmser/pkg/logger" "github.com/google/uuid" + "go.uber.org/zap" "gorm.io/gorm" ) @@ -78,7 +80,8 @@ func (r *pgRepository) ConnectServer(userID uuid.UUID, rawURL, login, encryptedP var created bool err := r.db.Transaction(func(tx *gorm.DB) error { - err := tx.Where("base_url = ?", cleanURL).First(&server).Error + // Сначала ищем среди удаленных серверов + err := tx.Unscoped().Where("base_url = ?", cleanURL).First(&server).Error if err != nil && err != gorm.ErrRecordNotFound { return err } @@ -100,8 +103,17 @@ func (r *pgRepository) ConnectServer(userID uuid.UUID, rawURL, login, encryptedP return err } created = true + } else if server.DeletedAt.Valid { + // --- СЦЕНАРИЙ 2: ВОССТАНОВЛЕНИЕ УДАЛЕННОГО СЕРВЕРА --- + // Восстанавливаем сервер, сохраняя старые значения Balance, InvoiceCount и ID + server.Name = name + server.DeletedAt = gorm.DeletedAt{} // Сбрасываем deleted_at + if err := tx.Save(&server).Error; err != nil { + return err + } + created = true // При восстановлении пользователь становится владельцем } else { - // --- СЦЕНАРИЙ 2: СУЩЕСТВУЮЩИЙ СЕРВЕР --- + // --- СЦЕНАРИЙ 3: СУЩЕСТВУЮЩИЙ АКТИВНЫЙ СЕРВЕР --- var userCount int64 tx.Model(&account.ServerUser{}).Where("server_id = ?", server.ID).Count(&userCount) if userCount >= int64(server.MaxUsers) { @@ -156,9 +168,92 @@ func (r *pgRepository) SaveServerSettings(server *account.RMSServer) error { "root_group_guid": server.RootGroupGUID, "auto_process": server.AutoProcess, "max_users": server.MaxUsers, + "sync_interval": server.SyncInterval, }).Error } +// UpdateLastActivity обновляет время последней активности пользователя +func (r *pgRepository) UpdateLastActivity(serverID uuid.UUID) error { + result := r.db.Model(&account.RMSServer{}). + Where("id = ?", serverID). + Update("last_activity_at", gorm.Expr("NOW()")) + + if result.Error != nil { + logger.Log.Error("Failed to update last_activity_at", + zap.String("server_id", serverID.String()), + zap.Error(result.Error)) + return result.Error + } + + if result.RowsAffected == 0 { + logger.Log.Warn("UpdateLastActivity: server not found", + zap.String("server_id", serverID.String())) + return fmt.Errorf("сервер не найден") + } + + return nil +} + +// UpdateLastSync обновляет время последней успешной синхронизации +func (r *pgRepository) UpdateLastSync(serverID uuid.UUID) error { + result := r.db.Model(&account.RMSServer{}). + Where("id = ?", serverID). + Update("last_sync_at", gorm.Expr("NOW()")) + + if result.Error != nil { + logger.Log.Error("Failed to update last_sync_at", + zap.String("server_id", serverID.String()), + zap.Error(result.Error)) + return result.Error + } + + if result.RowsAffected == 0 { + logger.Log.Warn("UpdateLastSync: server not found", + zap.String("server_id", serverID.String())) + return fmt.Errorf("сервер не найден") + } + + return nil +} + +// GetServersForSync возвращает серверы, готовые для синхронизации +func (r *pgRepository) GetServersForSync(idleThreshold time.Duration) ([]account.RMSServer, error) { + var servers []account.RMSServer + + // Конвертируем duration в минуты для SQL + idleMinutes := int(idleThreshold.Minutes()) + + query := ` + SELECT * FROM rms_servers + WHERE + deleted_at IS NULL + AND ( + -- Случай 1: Настало время периодической синхронизации + (EXTRACT(EPOCH FROM (NOW() - COALESCE(last_sync_at, '1970-01-01'::timestamp))) / 60) >= sync_interval + OR + -- Случай 2: Прошло N мин с последней активности, и активность была ПОЗЖЕ синхронизации + ( + last_activity_at > last_sync_at + AND (EXTRACT(EPOCH FROM (NOW() - last_activity_at)) / 60) >= ? + ) + ) + ` + + err := r.db.Raw(query, idleMinutes).Scan(&servers).Error + if err != nil { + logger.Log.Error("Failed to get servers for sync", + zap.Int("idle_threshold_minutes", idleMinutes), + zap.Error(err)) + return nil, err + } + + logger.Log.Info("Servers ready for sync", + zap.Int("count", len(servers)), + zap.Int("idle_threshold_minutes", idleMinutes)) + + return servers, nil +} + func (r *pgRepository) SetActiveServer(userID, serverID uuid.UUID) error { return r.db.Transaction(func(tx *gorm.DB) error { // Проверка доступа @@ -252,7 +347,7 @@ func (r *pgRepository) GetAllAvailableServers(userID uuid.UUID) ([]account.RMSSe } func (r *pgRepository) DeleteServer(serverID uuid.UUID) 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 diff --git a/internal/infrastructure/repository/drafts/postgres.go b/internal/infrastructure/repository/drafts/postgres.go index 8ad1554..c66aaad 100644 --- a/internal/infrastructure/repository/drafts/postgres.go +++ b/internal/infrastructure/repository/drafts/postgres.go @@ -160,20 +160,23 @@ func (r *pgRepository) GetActive(serverID uuid.UUID) ([]drafts.DraftInvoice, err return list, err } -func (r *pgRepository) GetRMSInvoiceIDToPhotoURLMap(serverID uuid.UUID) (map[uuid.UUID]string, error) { +func (r *pgRepository) GetLinkedDraftsMap(serverID uuid.UUID) (map[uuid.UUID]drafts.LinkedDraftInfo, error) { var draftsList []drafts.DraftInvoice err := r.db. - Select("rms_invoice_id", "sender_photo_url"). + Select("id", "rms_invoice_id", "sender_photo_url"). Where("rms_server_id = ? AND rms_invoice_id IS NOT NULL", serverID). Find(&draftsList).Error if err != nil { return nil, err } - result := make(map[uuid.UUID]string) + result := make(map[uuid.UUID]drafts.LinkedDraftInfo) for _, d := range draftsList { if d.RMSInvoiceID != nil { - result[*d.RMSInvoiceID] = d.SenderPhotoURL + result[*d.RMSInvoiceID] = drafts.LinkedDraftInfo{ + DraftID: d.ID, + PhotoURL: d.SenderPhotoURL, + } } } return result, nil diff --git a/internal/infrastructure/repository/invoices/postgres.go b/internal/infrastructure/repository/invoices/postgres.go index 4782502..b80c9e4 100644 --- a/internal/infrastructure/repository/invoices/postgres.go +++ b/internal/infrastructure/repository/invoices/postgres.go @@ -87,3 +87,17 @@ func (r *pgRepository) CountRecent(serverID uuid.UUID, days int) (int64, error) Count(&count).Error return count, err } + +func (r *pgRepository) GetStats(serverID uuid.UUID) (total int64, lastMonth int64, last24h int64, err error) { + query := ` + SELECT + COUNT(*) FILTER (WHERE status != 'DELETED') as total, + COUNT(*) FILTER (WHERE status != 'DELETED' AND created_at >= NOW() - INTERVAL '1 month') as last_month, + COUNT(*) FILTER (WHERE status != 'DELETED' AND created_at >= NOW() - INTERVAL '24 hours') as last_24h + FROM invoices + WHERE rms_server_id = $1 + ` + + err = r.db.Raw(query, serverID).Row().Scan(&total, &lastMonth, &last24h) + return total, lastMonth, last24h, err +} diff --git a/internal/infrastructure/repository/recommendations/postgres.go b/internal/infrastructure/repository/recommendations/postgres.go index c10f294..188f9b0 100644 --- a/internal/infrastructure/repository/recommendations/postgres.go +++ b/internal/infrastructure/repository/recommendations/postgres.go @@ -3,12 +3,12 @@ package recommendations import ( "fmt" "strconv" - "time" + + "github.com/google/uuid" + "gorm.io/gorm" "rmser/internal/domain/operations" "rmser/internal/domain/recommendations" - - "gorm.io/gorm" ) type pgRepository struct { @@ -21,11 +21,18 @@ func NewRepository(db *gorm.DB) recommendations.Repository { // --- Методы Хранения --- -func (r *pgRepository) SaveAll(items []recommendations.Recommendation) error { +func (r *pgRepository) SaveAll(serverID uuid.UUID, items []recommendations.Recommendation) error { return r.db.Transaction(func(tx *gorm.DB) error { - if err := tx.Session(&gorm.Session{AllowGlobalUpdate: true}).Unscoped().Delete(&recommendations.Recommendation{}).Error; err != nil { + // Удаляем только записи ЭТОГО сервера + if err := tx.Where("rms_server_id = ?", serverID).Delete(&recommendations.Recommendation{}).Error; err != nil { return err } + + // Проставляем server_id для всех записей + for i := range items { + items[i].RMSServerID = serverID + } + if len(items) > 0 { if err := tx.CreateInBatches(items, 100).Error; err != nil { return err @@ -35,16 +42,16 @@ func (r *pgRepository) SaveAll(items []recommendations.Recommendation) error { }) } -func (r *pgRepository) GetAll() ([]recommendations.Recommendation, error) { +func (r *pgRepository) GetAll(serverID uuid.UUID) ([]recommendations.Recommendation, error) { var items []recommendations.Recommendation - err := r.db.Find(&items).Error + err := r.db.Where("rms_server_id = ?", serverID).Find(&items).Error return items, err } // --- Методы Аналитики --- // 1. Товары (GOODS/PREPARED), не используемые в техкартах -func (r *pgRepository) FindUnusedGoods() ([]recommendations.Recommendation, error) { +func (r *pgRepository) FindUnusedGoods(serverID uuid.UUID) ([]recommendations.Recommendation, error) { var results []recommendations.Recommendation query := ` @@ -54,27 +61,30 @@ func (r *pgRepository) FindUnusedGoods() ([]recommendations.Recommendation, erro 'Товар не используется ни в одной техкарте' as reason, ? as type FROM products p - WHERE p.type IN ('GOODS', 'PREPARED') - AND p.is_deleted = false -- Проверка на удаление + WHERE p.rms_server_id = ? + AND p.type IN ('GOODS', 'PREPARED') + AND p.is_deleted = false AND p.id NOT IN ( - SELECT DISTINCT product_id FROM recipe_items + SELECT DISTINCT ri.product_id FROM recipe_items ri + JOIN recipes r ON ri.recipe_id = r.id + WHERE r.rms_server_id = ? ) AND p.id NOT IN ( - SELECT DISTINCT product_id FROM recipes + SELECT DISTINCT r.product_id FROM recipes r + WHERE r.rms_server_id = ? ) ORDER BY p.name ASC ` - if err := r.db.Raw(query, recommendations.TypeUnused).Scan(&results).Error; err != nil { + if err := r.db.Raw(query, recommendations.TypeUnused, serverID, serverID, serverID).Scan(&results).Error; err != nil { return nil, err } return results, nil } // 2. Закупается, но нет в техкартах -func (r *pgRepository) FindPurchasedButUnused(days int) ([]recommendations.Recommendation, error) { +func (r *pgRepository) FindPurchasedButUnused(serverID uuid.UUID, days int) ([]recommendations.Recommendation, error) { var results []recommendations.Recommendation - dateFrom := time.Now().AddDate(0, 0, -days) query := ` SELECT DISTINCT @@ -84,26 +94,33 @@ func (r *pgRepository) FindPurchasedButUnused(days int) ([]recommendations.Recom ? as type FROM store_operations so JOIN products p ON so.product_id = p.id - WHERE - so.op_type = ? - AND so.period_from >= ? - AND p.is_deleted = false -- Проверка на удаление - AND p.id NOT IN ( - SELECT DISTINCT product_id FROM recipe_items + WHERE + so.rms_server_id = ? + AND so.op_type = ? + AND so.period_to >= CURRENT_DATE - INTERVAL '1 day' + AND p.is_deleted = false + AND p.id NOT IN ( + SELECT DISTINCT ri.product_id FROM recipe_items ri + JOIN recipes r ON ri.recipe_id = r.id + WHERE r.rms_server_id = ? ) ORDER BY p.name ASC ` - if err := r.db.Raw(query, recommendations.TypePurchasedButUnused, operations.OpTypePurchase, dateFrom).Scan(&results).Error; err != nil { + if err := r.db.Raw(query, + recommendations.TypePurchasedButUnused, + serverID, + operations.OpTypePurchase, + serverID, + ).Scan(&results).Error; err != nil { return nil, err } return results, nil } // 3. Ингредиенты в актуальных техкартах без закупок -func (r *pgRepository) FindNoIncomingIngredients(days int) ([]recommendations.Recommendation, error) { +func (r *pgRepository) FindNoIncomingIngredients(serverID uuid.UUID, days int) ([]recommendations.Recommendation, error) { var results []recommendations.Recommendation - dateFrom := time.Now().AddDate(0, 0, -days) query := ` SELECT @@ -115,31 +132,38 @@ func (r *pgRepository) FindNoIncomingIngredients(days int) ([]recommendations.Re JOIN recipes r ON ri.recipe_id = r.id JOIN products p ON ri.product_id = p.id JOIN products parent ON r.product_id = parent.id - WHERE - (r.date_to IS NULL OR r.date_to >= CURRENT_DATE) + WHERE + r.rms_server_id = ? + AND (r.date_to IS NULL OR r.date_to >= CURRENT_DATE) AND p.type = 'GOODS' - AND p.is_deleted = false -- Сам ингредиент не удален - AND parent.is_deleted = false -- Блюдо, в которое он входит, не удалено + AND p.is_deleted = false + AND parent.is_deleted = false AND p.id NOT IN ( - SELECT product_id - FROM store_operations - WHERE op_type = ? - AND period_from >= ? + SELECT so.product_id + FROM store_operations so + WHERE so.rms_server_id = ? + AND so.op_type = ? + AND so.period_to >= CURRENT_DATE - INTERVAL '1 day' ) GROUP BY p.id, p.name ORDER BY p.name ASC ` - if err := r.db.Raw(query, strconv.Itoa(days), recommendations.TypeNoIncoming, operations.OpTypePurchase, dateFrom).Scan(&results).Error; err != nil { + if err := r.db.Raw(query, + strconv.Itoa(days), + recommendations.TypeNoIncoming, + serverID, + serverID, + operations.OpTypePurchase, + ).Scan(&results).Error; err != nil { return nil, err } return results, nil } // 4. Товары, которые закупаем, но не расходуем ("Висяки") -func (r *pgRepository) FindStaleGoods(days int) ([]recommendations.Recommendation, error) { +func (r *pgRepository) FindStaleGoods(serverID uuid.UUID, days int) ([]recommendations.Recommendation, error) { var results []recommendations.Recommendation - dateFrom := time.Now().AddDate(0, 0, -days) query := ` SELECT DISTINCT @@ -149,30 +173,38 @@ func (r *pgRepository) FindStaleGoods(days int) ([]recommendations.Recommendatio ? as type FROM store_operations so JOIN products p ON so.product_id = p.id - WHERE - so.op_type = ? - AND so.period_from >= ? - AND p.is_deleted = false -- Проверка на удаление - AND p.id NOT IN ( - SELECT product_id - FROM store_operations - WHERE op_type = ? - AND period_from >= ? + WHERE + so.rms_server_id = ? + AND so.op_type = ? + AND so.period_to >= CURRENT_DATE - INTERVAL '1 day' + AND p.is_deleted = false + AND p.id NOT IN ( + SELECT so2.product_id + FROM store_operations so2 + WHERE so2.rms_server_id = ? + AND so2.op_type = ? + AND so2.period_to >= CURRENT_DATE - INTERVAL '1 day' ) ORDER BY p.name ASC ` reason := fmt.Sprintf("Были закупки, но нет расхода за %d дн.", days) - if err := r.db.Raw(query, reason, recommendations.TypeStale, operations.OpTypePurchase, dateFrom, operations.OpTypeUsage, dateFrom). - Scan(&results).Error; err != nil { + if err := r.db.Raw(query, + reason, + recommendations.TypeStale, + serverID, + operations.OpTypePurchase, + serverID, + operations.OpTypeUsage, + ).Scan(&results).Error; err != nil { return nil, err } return results, nil } // 5. Блюдо используется в техкарте другого блюда -func (r *pgRepository) FindDishesInRecipes() ([]recommendations.Recommendation, error) { +func (r *pgRepository) FindDishesInRecipes(serverID uuid.UUID) ([]recommendations.Recommendation, error) { var results []recommendations.Recommendation query := ` @@ -186,23 +218,23 @@ func (r *pgRepository) FindDishesInRecipes() ([]recommendations.Recommendation, JOIN recipes r ON ri.recipe_id = r.id JOIN products parent ON r.product_id = parent.id WHERE - child.type = 'DISH' - AND child.is_deleted = false -- Вложенное блюдо не удалено - AND parent.is_deleted = false -- Родительское блюдо не удалено + r.rms_server_id = ? + AND child.type = 'DISH' + AND child.is_deleted = false + AND parent.is_deleted = false AND (r.date_to IS NULL OR r.date_to >= CURRENT_DATE) ORDER BY child.name ASC ` - if err := r.db.Raw(query, recommendations.TypeDishInRecipe).Scan(&results).Error; err != nil { + if err := r.db.Raw(query, recommendations.TypeDishInRecipe, serverID).Scan(&results).Error; err != nil { return nil, err } return results, nil } // 6. Есть расход (Usage), но нет прихода (Purchase) -func (r *pgRepository) FindUsageWithoutPurchase(days int) ([]recommendations.Recommendation, error) { +func (r *pgRepository) FindUsageWithoutPurchase(serverID uuid.UUID, days int) ([]recommendations.Recommendation, error) { var results []recommendations.Recommendation - dateFrom := time.Now().AddDate(0, 0, -days) query := ` SELECT DISTINCT @@ -212,30 +244,31 @@ func (r *pgRepository) FindUsageWithoutPurchase(days int) ([]recommendations.Rec ? as type FROM store_operations so JOIN products p ON so.product_id = p.id - WHERE - so.op_type = ? -- Есть расход (продажа/списание) - AND so.period_from >= ? - AND p.type = 'GOODS' -- Только для товаров - AND p.is_deleted = false -- Товар жив - AND p.id NOT IN ( -- Но не было закупок - SELECT product_id - FROM store_operations - WHERE op_type = ? - AND period_from >= ? + WHERE + so.rms_server_id = ? + AND so.op_type = ? + AND so.period_to >= CURRENT_DATE - INTERVAL '1 day' + AND p.type = 'GOODS' + AND p.is_deleted = false + AND p.id NOT IN ( + SELECT so2.product_id + FROM store_operations so2 + WHERE so2.rms_server_id = ? + AND so2.op_type = ? + AND so2.period_to >= CURRENT_DATE - INTERVAL '1 day' ) ORDER BY p.name ASC ` reason := fmt.Sprintf("Товар расходуется (продажи/списания), но не закупался последние %d дн.", days) - // Аргументы: reason, type, OpUsage, date, OpPurchase, date if err := r.db.Raw(query, reason, recommendations.TypeUsageNoIncoming, + serverID, operations.OpTypeUsage, - dateFrom, + serverID, operations.OpTypePurchase, - dateFrom, ).Scan(&results).Error; err != nil { return nil, err } diff --git a/internal/infrastructure/rms/client.go b/internal/infrastructure/rms/client.go index e91c0bc..041150b 100644 --- a/internal/infrastructure/rms/client.go +++ b/internal/infrastructure/rms/client.go @@ -40,6 +40,7 @@ type ClientI interface { FetchInvoices(from, to time.Time) ([]invoices.Invoice, error) FetchStoreOperations(presetID string, from, to time.Time) ([]StoreReportItemXML, error) CreateIncomingInvoice(inv invoices.Invoice) (string, error) + UnprocessIncomingInvoice(inv invoices.Invoice) error GetProductByID(id uuid.UUID) (*ProductFullDTO, error) UpdateProduct(product ProductFullDTO) (*ProductFullDTO, error) } @@ -555,9 +556,24 @@ func (c *Client) FetchStoreOperations(presetID string, from, to time.Time) ([]St return report.Items, nil } -// CreateIncomingInvoice отправляет накладную в iiko -func (c *Client) CreateIncomingInvoice(inv invoices.Invoice) (string, error) { - // 1. Маппинг Domain -> XML DTO +// buildInvoiceXML формирует XML payload для накладной на основе доменной сущности +func (c *Client) buildInvoiceXML(inv invoices.Invoice) ([]byte, error) { + // Защита от паники с recover + var panicErr error + defer func() { + if r := recover(); r != nil { + logger.Log.Error("Паника в buildInvoiceXML", + zap.Any("panic", r), + zap.Stack("stack"), + ) + panicErr = fmt.Errorf("panic recovered: %v", r) + } + }() + if panicErr != nil { + return nil, panicErr + } + + // Маппинг Domain -> XML DTO // Статус по умолчанию NEW, если не передан status := inv.Status @@ -592,7 +608,21 @@ func (c *Client) CreateIncomingInvoice(inv invoices.Invoice) (string, error) { reqDTO.ID = inv.ID.String() } + // Логирование перед циклом по Items + logger.Log.Debug("Начинаем формирование XML для позиций накладной", + zap.Int("items_count", len(inv.Items)), + ) + for i, item := range inv.Items { + // Проверка что продукт загружен (по полю ID) + if item.Product.ID == uuid.Nil { + logger.Log.Warn("Пропуск позиции: Product не загружен", + zap.String("product_id", item.ProductID.String()), + zap.Int("index", i), + ) + continue + } + amount, _ := item.Amount.Float64() price, _ := item.Price.Float64() sum, _ := item.Sum.Float64() @@ -610,18 +640,47 @@ func (c *Client) CreateIncomingInvoice(inv invoices.Invoice) (string, error) { xmlItem.ContainerId = item.ContainerID.String() } + // Проверка MainUnitID перед обращением + if item.Product.MainUnitID != nil { + xmlItem.AmountUnit = item.Product.MainUnitID.String() + } + + // Логирование каждого добавленного item + logger.Log.Debug("Добавление позиции в XML", + zap.String("product_id", item.ProductID.String()), + zap.Float64("amount", amount), + zap.String("product_name", item.Product.Name), + ) + reqDTO.ItemsWrapper.Items = append(reqDTO.ItemsWrapper.Items, xmlItem) } - // 2. Маршалинг в XML + // Маршалинг в XML xmlBytes, err := xml.Marshal(reqDTO) if err != nil { - return "", fmt.Errorf("xml marshal error: %w", err) + return nil, fmt.Errorf("xml marshal error: %w", err) } // Добавляем XML header вручную xmlPayload := []byte(xml.Header + string(xmlBytes)) - // 3. Получение токена + // Логирование XML перед отправкой + logger.Log.Debug("XML payload подготовлен", + zap.String("xml_payload", string(xmlPayload)), + zap.Int("payload_size", len(xmlPayload)), + ) + + return xmlPayload, nil +} + +// CreateIncomingInvoice отправляет накладную в iiko +func (c *Client) CreateIncomingInvoice(inv invoices.Invoice) (string, error) { + // 1. Формирование XML payload + xmlPayload, err := c.buildInvoiceXML(inv) + if err != nil { + return "", fmt.Errorf("ошибка формирования XML: %w", err) + } + + // 2. Получение токена if err := c.ensureToken(); err != nil { return "", err } @@ -630,7 +689,7 @@ func (c *Client) CreateIncomingInvoice(inv invoices.Invoice) (string, error) { token := c.token c.mu.RUnlock() - // 4. Формирование URL + // 3. Формирование URL endpoint, _ := url.Parse(c.baseURL + "/resto/api/documents/import/incomingInvoice") q := endpoint.Query() q.Set("key", token) @@ -646,7 +705,7 @@ func (c *Client) CreateIncomingInvoice(inv invoices.Invoice) (string, error) { zap.String("body_payload", string(xmlPayload)), ) - // 5. Отправка + // 4. Отправка req, err := http.NewRequest("POST", fullURL, bytes.NewReader(xmlPayload)) if err != nil { return "", err @@ -666,9 +725,9 @@ func (c *Client) CreateIncomingInvoice(inv invoices.Invoice) (string, error) { } // Логируем ответ для симметрии - logger.Log.Info("RMS POST Response Debug", + logger.Log.Debug("Получен ответ от iiko", zap.Int("status_code", resp.StatusCode), - zap.String("response_body", string(respBody)), + zap.String("raw_response", string(respBody)), ) if resp.StatusCode != http.StatusOK { @@ -691,6 +750,89 @@ func (c *Client) CreateIncomingInvoice(inv invoices.Invoice) (string, error) { return result.DocumentNumber, nil } +// UnprocessIncomingInvoice выполняет распроведение накладной в iiko +func (c *Client) UnprocessIncomingInvoice(inv invoices.Invoice) error { + // 1. Формирование XML payload + xmlPayload, err := c.buildInvoiceXML(inv) + if err != nil { + return fmt.Errorf("ошибка формирования XML: %w", err) + } + + // 2. Получение токена + if err := c.ensureToken(); err != nil { + return err + } + + c.mu.RLock() + token := c.token + c.mu.RUnlock() + + // 3. Формирование URL + endpoint, _ := url.Parse(c.baseURL + "/resto/api/documents/unprocess/incomingInvoice") + q := endpoint.Query() + q.Set("key", token) + endpoint.RawQuery = q.Encode() + + fullURL := endpoint.String() + + // Логирование запроса + logger.Log.Info("RMS Unprocess Request", + zap.String("method", "POST"), + zap.String("url", fullURL), + zap.String("document_number", inv.DocumentNumber), + zap.String("invoice_id", inv.ID.String()), + ) + + // 4. Отправка POST запроса + req, err := http.NewRequest("POST", fullURL, bytes.NewReader(xmlPayload)) + if err != nil { + return fmt.Errorf("ошибка создания запроса: %w", err) + } + req.Header.Set("Content-Type", "application/xml") + + resp, err := c.httpClient.Do(req) + if err != nil { + return fmt.Errorf("ошибка сети: %w", err) + } + defer resp.Body.Close() + + // Читаем ответ + respBody, err := io.ReadAll(resp.Body) + if err != nil { + return fmt.Errorf("ошибка чтения ответа: %w", err) + } + + // Логируем ответ + logger.Log.Debug("Получен ответ от iiko на распроведение", + zap.Int("status_code", resp.StatusCode), + zap.String("raw_response", string(respBody)), + ) + + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("http error %d: %s", resp.StatusCode, string(respBody)) + } + + // Проверка результата валидации + var result DocumentValidationResult + if err := xml.Unmarshal(respBody, &result); err != nil { + return fmt.Errorf("ошибка разбора XML ответа: %w", err) + } + + if !result.Valid { + logger.Log.Warn("RMS Invoice Unprocess Failed", + zap.String("error", result.ErrorMessage), + zap.String("additional", result.AdditionalInfo), + ) + return fmt.Errorf("распроведение не удалось: %s (info: %s)", result.ErrorMessage, result.AdditionalInfo) + } + + logger.Log.Info("RMS Invoice Unprocess Success", + zap.String("document_number", result.DocumentNumber), + ) + + return nil +} + // GetProductByID получает полную структуру товара по ID (через /list?ids=...) func (c *Client) GetProductByID(id uuid.UUID) (*ProductFullDTO, error) { // Параметр ids должен быть списком. iiko ожидает ids=UUID diff --git a/internal/services/drafts/service.go b/internal/services/drafts/service.go index 16170f5..dfae425 100644 --- a/internal/services/drafts/service.go +++ b/internal/services/drafts/service.go @@ -157,9 +157,6 @@ func (s *Service) UpdateDraftHeader(id uuid.UUID, storeID *uuid.UUID, supplierID if err != nil { return err } - if draft.Status == drafts.StatusCompleted { - return errors.New("черновик уже отправлен") - } draft.StoreID = storeID draft.SupplierID = supplierID draft.DateIncoming = &date @@ -227,7 +224,7 @@ func (s *Service) DeleteItem(draftID, itemID uuid.UUID) (float64, error) { return sumFloat, nil } -// RecalculateItemFields - логика пересчета Qty/Price/Sum +// RecalculateItemFields - логика пересчета Q->P->S->Q (Quantity -> Price -> Sum -> Quantity) с использованием decimal для точности func (s *Service) RecalculateItemFields(item *drafts.DraftInvoiceItem, editedField drafts.EditedField) { if item.LastEditedField1 != editedField { item.LastEditedField2 = item.LastEditedField1 @@ -265,6 +262,29 @@ func (s *Service) RecalculateItemFields(item *drafts.DraftInvoiceItem, editedFie case drafts.FieldSum: item.Sum = item.Quantity.Mul(item.Price) } + + // Дополнительная проверка для гарантии консистентности всех полей (Q->P->S->Q) + // Используется только для обеспечения точности, не влияет на логику выбора пересчитываемого поля + if !item.Price.IsZero() && !item.Quantity.IsZero() { + calculatedSum := item.Quantity.Mul(item.Price) + if !calculatedSum.Equal(item.Sum) { + item.Sum = calculatedSum + } + } + + if !item.Price.IsZero() && !item.Sum.IsZero() { + calculatedQuantity := item.Sum.Div(item.Price) + if !calculatedQuantity.Equal(item.Quantity) { + item.Quantity = calculatedQuantity + } + } + + if !item.Quantity.IsZero() && !item.Sum.IsZero() { + calculatedPrice := item.Sum.Div(item.Quantity) + if !calculatedPrice.Equal(item.Price) { + item.Price = calculatedPrice + } + } } // UpdateItem обновлен для поддержки динамического пересчета @@ -293,17 +313,10 @@ func (s *Service) UpdateItem(draftID, itemID uuid.UUID, productID *uuid.UUID, co } } - field := drafts.EditedField(editedField) - switch field { - case drafts.FieldQuantity: - currentItem.Quantity = qty - case drafts.FieldPrice: - currentItem.Price = price - case drafts.FieldSum: - currentItem.Sum = sum - } - - s.RecalculateItemFields(currentItem, field) + // Просто присваиваем значения от фронтенда без пересчета + currentItem.Quantity = qty + currentItem.Price = price + currentItem.Sum = sum if draft.Status == drafts.StatusCanceled { draft.Status = drafts.StatusReadyToVerify @@ -311,20 +324,18 @@ func (s *Service) UpdateItem(draftID, itemID uuid.UUID, productID *uuid.UUID, co } updates := map[string]interface{}{ - "product_id": currentItem.ProductID, - "container_id": currentItem.ContainerID, - "quantity": currentItem.Quantity, - "price": currentItem.Price, - "sum": currentItem.Sum, - "last_edited_field1": currentItem.LastEditedField1, - "last_edited_field2": currentItem.LastEditedField2, - "is_matched": currentItem.IsMatched, + "product_id": currentItem.ProductID, + "container_id": currentItem.ContainerID, + "quantity": currentItem.Quantity, + "price": currentItem.Price, + "sum": currentItem.Sum, + "is_matched": currentItem.IsMatched, } return s.draftRepo.UpdateItem(itemID, updates) } -func (s *Service) CommitDraft(draftID, userID uuid.UUID) (string, error) { +func (s *Service) CommitDraft(draftID, userID uuid.UUID, isProcessed bool) (string, error) { server, err := s.accountRepo.GetActiveServer(userID) if err != nil { return "", fmt.Errorf("active server not found: %w", err) @@ -347,17 +358,13 @@ func (s *Service) CommitDraft(draftID, userID uuid.UUID) (string, error) { return "", errors.New("черновик принадлежит другому серверу") } - if draft.Status == drafts.StatusCompleted { - return "", errors.New("накладная уже отправлена") - } - client, err := s.rmsFactory.GetClientForUser(userID) if err != nil { return "", err } targetStatus := "NEW" - if server.AutoProcess { + if isProcessed { targetStatus = "PROCESSED" } @@ -373,6 +380,11 @@ func (s *Service) CommitDraft(draftID, userID uuid.UUID) (string, error) { Items: make([]invoices.InvoiceItem, 0, len(draft.Items)), } + // Если черновик уже был отправлен ранее, передаем RMSInvoiceID для обновления + if draft.RMSInvoiceID != nil { + inv.ID = *draft.RMSInvoiceID + } + for _, dItem := range draft.Items { if dItem.ProductID == nil { continue @@ -405,6 +417,12 @@ func (s *Service) CommitDraft(draftID, userID uuid.UUID) (string, error) { Price: priceToSend, Sum: sum, ContainerID: dItem.ContainerID, + Product: func() catalog.Product { + if dItem.Product != nil { + return *dItem.Product + } + return catalog.Product{} + }(), } inv.Items = append(inv.Items, invItem) } @@ -415,7 +433,22 @@ func (s *Service) CommitDraft(draftID, userID uuid.UUID) (string, error) { docNum, err := client.CreateIncomingInvoice(inv) if err != nil { - return "", err + // Если накладная уже проведена, пробуем распровести и повторить + if strings.Contains(err.Error(), "Changing processed") { + logger.Log.Info("Накладная проведена, выполняю распроведение...", zap.String("doc_num", draft.DocumentNumber)) + + if unprocessErr := client.UnprocessIncomingInvoice(inv); unprocessErr != nil { + return "", fmt.Errorf("не удалось распровести накладную: %w", unprocessErr) + } + + // Повторяем попытку создания накладной после распроведения + docNum, err = client.CreateIncomingInvoice(inv) + if err != nil { + return "", err + } + } else { + return "", err + } } invoices, err := client.FetchInvoices(*draft.DateIncoming, *draft.DateIncoming) @@ -434,6 +467,7 @@ func (s *Service) CommitDraft(draftID, userID uuid.UUID) (string, error) { for _, invoice := range invoices { if invoice.DocumentNumber == docNum { draft.RMSInvoiceID = &invoice.ID + draft.DocumentNumber = invoice.DocumentNumber found = true break } @@ -555,19 +589,20 @@ func (s *Service) CreateProductContainer(userID uuid.UUID, productID uuid.UUID, } type UnifiedInvoiceDTO struct { - ID uuid.UUID `json:"id"` - Type string `json:"type"` - DocumentNumber string `json:"document_number"` - IncomingNumber string `json:"incoming_number"` - DateIncoming time.Time `json:"date_incoming"` - Status string `json:"status"` - TotalSum float64 `json:"total_sum"` - StoreName string `json:"store_name"` - ItemsCount int `json:"items_count"` - CreatedAt time.Time `json:"created_at"` - IsAppCreated bool `json:"is_app_created"` - PhotoURL string `json:"photo_url"` - ItemsPreview string `json:"items_preview"` + ID uuid.UUID `json:"id"` + Type string `json:"type"` + DocumentNumber string `json:"document_number"` + IncomingNumber string `json:"incoming_number"` + DateIncoming time.Time `json:"date_incoming"` + Status string `json:"status"` + TotalSum float64 `json:"total_sum"` + StoreName string `json:"store_name"` + ItemsCount int `json:"items_count"` + CreatedAt time.Time `json:"created_at"` + IsAppCreated bool `json:"is_app_created"` + PhotoURL string `json:"photo_url"` + ItemsPreview string `json:"items_preview"` + DraftID *uuid.UUID `json:"draft_id,omitempty"` // ID черновика для SYNCED накладных } func (s *Service) GetUnifiedList(userID uuid.UUID, from, to time.Time) ([]UnifiedInvoiceDTO, error) { @@ -586,7 +621,7 @@ func (s *Service) GetUnifiedList(userID uuid.UUID, from, to time.Time) ([]Unifie return nil, err } - photoMap, err := s.draftRepo.GetRMSInvoiceIDToPhotoURLMap(server.ID) + linkedDraftsMap, err := s.draftRepo.GetLinkedDraftsMap(server.ID) if err != nil { return nil, err } @@ -647,9 +682,11 @@ func (s *Service) GetUnifiedList(userID uuid.UUID, from, to time.Time) ([]Unifie isAppCreated := false photoURL := "" - if url, exists := photoMap[inv.ID]; exists { + var draftID *uuid.UUID + if linkedInfo, exists := linkedDraftsMap[inv.ID]; exists { isAppCreated = true - photoURL = url + photoURL = linkedInfo.PhotoURL + draftID = &linkedInfo.DraftID } var itemsPreview string @@ -679,6 +716,7 @@ func (s *Service) GetUnifiedList(userID uuid.UUID, from, to time.Time) ([]Unifie IsAppCreated: isAppCreated, PhotoURL: photoURL, ItemsPreview: itemsPreview, + DraftID: draftID, }) } @@ -739,6 +777,14 @@ func (s *Service) CreateDraft(userID uuid.UUID) (*drafts.DraftInvoice, error) { return nil, err } + // Обновляем время последней активности сервера + if err := s.accountRepo.UpdateLastActivity(server.ID); err != nil { + logger.Log.Warn("Не удалось обновить время активности", + zap.String("server_id", server.ID.String()), + zap.Error(err)) + // Не возвращаем ошибку - это некритично + } + return draft, nil } @@ -967,11 +1013,6 @@ func (s *Service) SaveDraftFull(draftID, userID uuid.UUID, req drafts.UpdateDraf return err } - // Проверяем, что черновик не завершен - if draft.Status == drafts.StatusCompleted { - return errors.New("черновик уже отправлен") - } - // Обновляем шапку черновика, если переданы поля headerUpdated := false @@ -1084,54 +1125,17 @@ func (s *Service) SaveDraftFull(draftID, userID uuid.UUID, req drafts.UpdateDraf } } - // Определяем, какое поле редактируется - editedField := itemReq.EditedField - if editedField == "" { - if itemReq.Sum != nil { - editedField = "sum" - } else if itemReq.Price != nil { - editedField = "price" - } else if itemReq.Quantity != nil { - editedField = "quantity" - } - } - - // Обновляем числовые поля - qty := decimal.Zero + // Просто присваиваем значения от фронтенда без пересчета if itemReq.Quantity != nil { - qty = decimal.NewFromFloat(*itemReq.Quantity) - } else { - qty = currentItem.Quantity + currentItem.Quantity = decimal.NewFromFloat(*itemReq.Quantity) } - - price := decimal.Zero if itemReq.Price != nil { - price = decimal.NewFromFloat(*itemReq.Price) - } else { - price = currentItem.Price + currentItem.Price = decimal.NewFromFloat(*itemReq.Price) } - - sum := decimal.Zero if itemReq.Sum != nil { - sum = decimal.NewFromFloat(*itemReq.Sum) - } else { - sum = currentItem.Sum + currentItem.Sum = decimal.NewFromFloat(*itemReq.Sum) } - // Применяем изменения в зависимости от редактируемого поля - field := drafts.EditedField(editedField) - switch field { - case drafts.FieldQuantity: - currentItem.Quantity = qty - case drafts.FieldPrice: - currentItem.Price = price - case drafts.FieldSum: - currentItem.Sum = sum - } - - // Пересчитываем поля - s.RecalculateItemFields(currentItem, field) - // Обновляем статус черновика, если он был отменен if draft.Status == drafts.StatusCanceled { draft.Status = drafts.StatusReadyToVerify @@ -1142,14 +1146,12 @@ func (s *Service) SaveDraftFull(draftID, userID uuid.UUID, req drafts.UpdateDraf // Сохраняем обновленную позицию updates := map[string]interface{}{ - "product_id": currentItem.ProductID, - "container_id": currentItem.ContainerID, - "quantity": currentItem.Quantity, - "price": currentItem.Price, - "sum": currentItem.Sum, - "last_edited_field1": currentItem.LastEditedField1, - "last_edited_field2": currentItem.LastEditedField2, - "is_matched": currentItem.IsMatched, + "product_id": currentItem.ProductID, + "container_id": currentItem.ContainerID, + "quantity": currentItem.Quantity, + "price": currentItem.Price, + "sum": currentItem.Sum, + "is_matched": currentItem.IsMatched, } if err := s.draftRepo.UpdateItem(itemID, updates); err != nil { @@ -1158,5 +1160,13 @@ func (s *Service) SaveDraftFull(draftID, userID uuid.UUID, req drafts.UpdateDraf } } + // Обновляем время последней активности сервера + if err := s.accountRepo.UpdateLastActivity(draft.RMSServerID); err != nil { + logger.Log.Warn("Не удалось обновить время активности", + zap.String("server_id", draft.RMSServerID.String()), + zap.Error(err)) + // Не возвращаем ошибку - это некритично + } + return nil } diff --git a/internal/services/invoices/service.go b/internal/services/invoices/service.go index b1dd549..0d2e1b7 100644 --- a/internal/services/invoices/service.go +++ b/internal/services/invoices/service.go @@ -8,6 +8,7 @@ import ( "github.com/shopspring/decimal" "go.uber.org/zap" + "rmser/internal/domain/account" "rmser/internal/domain/drafts" invDomain "rmser/internal/domain/invoices" "rmser/internal/domain/suppliers" @@ -19,16 +20,18 @@ type Service struct { repo invDomain.Repository draftsRepo drafts.Repository supplierRepo suppliers.Repository + accountRepo account.Repository rmsFactory *rms.Factory // Здесь можно добавить репозитории каталога и контрагентов для валидации, // но для краткости пока опустим глубокую валидацию. } -func NewService(repo invDomain.Repository, draftsRepo drafts.Repository, supplierRepo suppliers.Repository, rmsFactory *rms.Factory) *Service { +func NewService(repo invDomain.Repository, draftsRepo drafts.Repository, supplierRepo suppliers.Repository, accountRepo account.Repository, rmsFactory *rms.Factory) *Service { return &Service{ repo: repo, draftsRepo: draftsRepo, supplierRepo: supplierRepo, + accountRepo: accountRepo, rmsFactory: rmsFactory, } } @@ -99,6 +102,13 @@ func (s *Service) SendInvoiceToRMS(req CreateRequestDTO, userID uuid.UUID) (stri return docNum, nil } +// InvoiceStatsDTO - DTO для статистики накладных +type InvoiceStatsDTO struct { + Total int64 `json:"total"` + LastMonth int64 `json:"last_month"` + Last24h int64 `json:"last_24h"` +} + // InvoiceDetailsDTO - DTO для ответа на запрос деталей накладной type InvoiceDetailsDTO struct { ID uuid.UUID `json:"id"` @@ -145,7 +155,7 @@ func (s *Service) GetInvoice(id uuid.UUID) (*InvoiceDetailsDTO, error) { Number: inv.DocumentNumber, Date: inv.DateIncoming.Format("2006-01-02"), Status: "COMPLETED", // Для синхронизированных накладных статус всегда COMPLETED - Items: make([]struct { + Items: make([]struct { Name string `json:"name"` Quantity float64 `json:"quantity"` Price float64 `json:"price"` @@ -166,3 +176,32 @@ func (s *Service) GetInvoice(id uuid.UUID) (*InvoiceDetailsDTO, error) { return dto, nil } + +// GetStats возвращает статистику по накладным для пользователя +func (s *Service) GetStats(userID uuid.UUID) (*InvoiceStatsDTO, error) { + // Получаем активный сервер пользователя + server, err := s.accountRepo.GetActiveServer(userID) + if err != nil { + return nil, fmt.Errorf("ошибка получения активного сервера: %w", err) + } + + if server == nil { + return &InvoiceStatsDTO{ + Total: 0, + LastMonth: 0, + Last24h: 0, + }, nil + } + + // Получаем статистику из репозитория + total, lastMonth, last24h, err := s.repo.GetStats(server.ID) + if err != nil { + return nil, fmt.Errorf("ошибка получения статистики: %w", err) + } + + return &InvoiceStatsDTO{ + Total: total, + LastMonth: lastMonth, + Last24h: last24h, + }, nil +} diff --git a/internal/services/recommend/service.go b/internal/services/recommend/service.go index 5157e2f..d50f5b2 100644 --- a/internal/services/recommend/service.go +++ b/internal/services/recommend/service.go @@ -1,6 +1,7 @@ package recommend import ( + "github.com/google/uuid" "go.uber.org/zap" "rmser/internal/domain/recommendations" @@ -20,56 +21,56 @@ func NewService(repo recommendations.Repository) *Service { return &Service{repo: repo} } -// RefreshRecommendations выполняет анализ и сохраняет результаты в БД -func (s *Service) RefreshRecommendations() error { - logger.Log.Info("Запуск пересчета рекомендаций...") +// RefreshRecommendations выполняет анализ и сохраняет результаты в БД для конкретного сервера +func (s *Service) RefreshRecommendations(serverID uuid.UUID) error { + logger.Log.Info("Запуск пересчета рекомендаций...", zap.String("server_id", serverID.String())) var all []recommendations.Recommendation // 1. Unused - if unused, err := s.repo.FindUnusedGoods(); err == nil { + if unused, err := s.repo.FindUnusedGoods(serverID); err == nil { all = append(all, unused...) } else { logger.Log.Error("Ошибка unused", zap.Error(err)) } // 2. Purchased but Unused - if purchUnused, err := s.repo.FindPurchasedButUnused(AnalyzeDaysNoIncoming); err == nil { + if purchUnused, err := s.repo.FindPurchasedButUnused(serverID, AnalyzeDaysNoIncoming); err == nil { all = append(all, purchUnused...) } else { logger.Log.Error("Ошибка purchased_unused", zap.Error(err)) } // 3. No Incoming (Ингредиенты без закупок) - if noInc, err := s.repo.FindNoIncomingIngredients(AnalyzeDaysNoIncoming); err == nil { + if noInc, err := s.repo.FindNoIncomingIngredients(serverID, AnalyzeDaysNoIncoming); err == nil { all = append(all, noInc...) } else { logger.Log.Error("Ошибка no_incoming", zap.Error(err)) } - // 4. Usage without Purchase (Расход без прихода) <-- НОВОЕ - if usageNoPurch, err := s.repo.FindUsageWithoutPurchase(AnalyzeDaysNoIncoming); err == nil { + // 4. Usage without Purchase (Расход без прихода) + if usageNoPurch, err := s.repo.FindUsageWithoutPurchase(serverID, AnalyzeDaysNoIncoming); err == nil { all = append(all, usageNoPurch...) } else { logger.Log.Error("Ошибка usage_no_purchase", zap.Error(err)) } // 5. Stale (Неликвид) - if stale, err := s.repo.FindStaleGoods(AnalyzeDaysStale); err == nil { + if stale, err := s.repo.FindStaleGoods(serverID, AnalyzeDaysStale); err == nil { all = append(all, stale...) } else { logger.Log.Error("Ошибка stale", zap.Error(err)) } // 6. Dish in Recipe - if dishInRec, err := s.repo.FindDishesInRecipes(); err == nil { + if dishInRec, err := s.repo.FindDishesInRecipes(serverID); err == nil { all = append(all, dishInRec...) } else { logger.Log.Error("Ошибка dish_in_recipe", zap.Error(err)) } // Сохраняем - if err := s.repo.SaveAll(all); err != nil { + if err := s.repo.SaveAll(serverID, all); err != nil { return err } @@ -77,6 +78,7 @@ func (s *Service) RefreshRecommendations() error { return nil } -func (s *Service) GetRecommendations() ([]recommendations.Recommendation, error) { - return s.repo.GetAll() +// GetRecommendations возвращает рекомендации для конкретного сервера +func (s *Service) GetRecommendations(serverID uuid.UUID) ([]recommendations.Recommendation, error) { + return s.repo.GetAll(serverID) } diff --git a/internal/services/sync/service.go b/internal/services/sync/service.go index da3ae68..baeb33a 100644 --- a/internal/services/sync/service.go +++ b/internal/services/sync/service.go @@ -15,6 +15,7 @@ import ( "rmser/internal/domain/recipes" "rmser/internal/domain/suppliers" "rmser/internal/infrastructure/rms" + "rmser/pkg/crypto" "rmser/pkg/logger" ) @@ -24,17 +25,19 @@ const ( ) type Service struct { - rmsFactory *rms.Factory - accountRepo account.Repository - catalogRepo catalog.Repository - recipeRepo recipes.Repository - invoiceRepo invoices.Repository - opRepo operations.Repository - supplierRepo suppliers.Repository + rmsFactory *rms.Factory + cryptoManager *crypto.CryptoManager + accountRepo account.Repository + catalogRepo catalog.Repository + recipeRepo recipes.Repository + invoiceRepo invoices.Repository + opRepo operations.Repository + supplierRepo suppliers.Repository } func NewService( rmsFactory *rms.Factory, + cryptoManager *crypto.CryptoManager, accountRepo account.Repository, catalogRepo catalog.Repository, recipeRepo recipes.Repository, @@ -43,16 +46,73 @@ func NewService( supplierRepo suppliers.Repository, ) *Service { return &Service{ - rmsFactory: rmsFactory, - accountRepo: accountRepo, - catalogRepo: catalogRepo, - recipeRepo: recipeRepo, - invoiceRepo: invoiceRepo, - opRepo: opRepo, - supplierRepo: supplierRepo, + rmsFactory: rmsFactory, + cryptoManager: cryptoManager, + accountRepo: accountRepo, + catalogRepo: catalogRepo, + recipeRepo: recipeRepo, + invoiceRepo: invoiceRepo, + opRepo: opRepo, + supplierRepo: supplierRepo, } } +// SyncAllDataForServer запускает полную синхронизацию для конкретного сервера +func (s *Service) SyncAllDataForServer(serverID uuid.UUID, force bool) error { + logger.Log.Info("Запуск синхронизации по серверу", zap.String("server_id", serverID.String()), zap.Bool("force", force)) + + // 1. Получаем информацию о сервере + server, err := s.accountRepo.GetServerByID(serverID) + if err != nil || server == nil { + return fmt.Errorf("server not found: %s", serverID) + } + + // 2. Получаем креды владельца сервера для подключения + baseURL, login, encryptedPass, err := s.getOwnerCredentials(serverID) + if err != nil { + return fmt.Errorf("failed to get owner credentials: %w", err) + } + + // 3. Расшифровываем пароль + plainPass, err := s.cryptoManager.Decrypt(encryptedPass) + if err != nil { + return fmt.Errorf("failed to decrypt password: %w", err) + } + + // 4. Создаем клиент RMS + client := s.rmsFactory.CreateClientFromRawCredentials(baseURL, login, plainPass) + + return s.syncAllWithClient(client, serverID, force) +} + +// getOwnerCredentials возвращает учетные данные владельца сервера +func (s *Service) getOwnerCredentials(serverID uuid.UUID) (url, login, encryptedPass string, err error) { + // Находим владельца сервера + users, err := s.accountRepo.GetServerUsers(serverID) + if err != nil { + return "", "", "", err + } + + var ownerLink *account.ServerUser + for i := range users { + if users[i].Role == account.RoleOwner { + ownerLink = &users[i] + break + } + } + + if ownerLink == nil { + return "", "", "", fmt.Errorf("owner not found for server %s", serverID) + } + + // Если у владельца есть личные креды - используем их + if ownerLink.Login != "" && ownerLink.EncryptedPassword != "" { + return ownerLink.Server.BaseURL, ownerLink.Login, ownerLink.EncryptedPassword, nil + } + + return "", "", "", fmt.Errorf("owner has no credentials for server %s", serverID) +} + // SyncAllData запускает полную синхронизацию для конкретного пользователя func (s *Service) SyncAllData(userID uuid.UUID, force bool) error { logger.Log.Info("Запуск синхронизации", zap.String("user_id", userID.String()), zap.Bool("force", force)) @@ -68,6 +128,12 @@ func (s *Service) SyncAllData(userID uuid.UUID, force bool) error { } serverID := server.ID + return s.syncAllWithClient(client, serverID, force) +} + +// syncAllWithClient выполняет синхронизацию с готовым клиентом +func (s *Service) syncAllWithClient(client rms.ClientI, serverID uuid.UUID, force bool) error { + // 2. Справочники if err := s.syncStores(client, serverID); err != nil { logger.Log.Error("Sync Stores failed", zap.Error(err)) @@ -96,13 +162,12 @@ func (s *Service) SyncAllData(userID uuid.UUID, force bool) error { logger.Log.Error("Sync Invoices failed", zap.Error(err)) } - // 7. Складские операции (тяжелый запрос) - // Для MVP можно отключить, если долго грузится - // if err := s.SyncStoreOperations(client, serverID); err != nil { - // logger.Log.Error("Sync Operations failed", zap.Error(err)) - // } + // 7. Складские операции + if err := s.SyncStoreOperations(client, serverID); err != nil { + logger.Log.Error("Sync Operations failed", zap.Error(err)) + } - logger.Log.Info("Синхронизация завершена", zap.String("user_id", userID.String())) + logger.Log.Info("Синхронизация завершена", zap.String("server_id", serverID.String())) return nil } @@ -236,7 +301,7 @@ func (s *Service) syncInvoices(c rms.ClientI, serverID uuid.UUID, force bool) er // SyncStoreOperations публичный, если нужно вызывать отдельно func (s *Service) SyncStoreOperations(c rms.ClientI, serverID uuid.UUID) error { dateTo := time.Now() - dateFrom := dateTo.AddDate(0, 0, -30) + dateFrom := dateTo.AddDate(0, 0, -90) // 90 дней — соответствует периоду анализа рекомендаций if err := s.syncReport(c, serverID, PresetPurchases, operations.OpTypePurchase, dateFrom, dateTo); err != nil { return fmt.Errorf("purchases sync error: %w", err) diff --git a/internal/services/worker/sync_worker.go b/internal/services/worker/sync_worker.go new file mode 100644 index 0000000..42cb9c6 --- /dev/null +++ b/internal/services/worker/sync_worker.go @@ -0,0 +1,136 @@ +package worker + +import ( + "context" + "time" + + "github.com/google/uuid" + "go.uber.org/zap" + + "rmser/internal/domain/account" + "rmser/internal/infrastructure/rms" + "rmser/internal/services/recommend" +) + +// SyncService интерфейс для синхронизации данных +type SyncService interface { + // SyncAllDataForServer синхронизирует данные для конкретного сервера + SyncAllDataForServer(serverID uuid.UUID, force bool) error +} + +// SyncWorker фоновый процесс для автоматической синхронизации данных с iiko серверами +type SyncWorker struct { + syncService SyncService // сервис для синхронизации + accountRepo account.Repository // репозиторий для работы с серверами + rmsFactory *rms.Factory // фабрика для создания клиентов RMS + recService *recommend.Service // сервис рекомендаций + logger *zap.Logger + tickerInterval time.Duration // интервал проверки (например, 1 минута) + idleThreshold time.Duration // порог простоя (10 минут) +} + +// NewSyncWorker создает новый экземпляр SyncWorker +func NewSyncWorker( + syncService SyncService, + accountRepo account.Repository, + rmsFactory *rms.Factory, + recService *recommend.Service, + logger *zap.Logger, +) *SyncWorker { + return &SyncWorker{ + syncService: syncService, + accountRepo: accountRepo, + rmsFactory: rmsFactory, + recService: recService, + logger: logger, + tickerInterval: 1 * time.Minute, + idleThreshold: 10 * time.Minute, + } +} + +// Run запускает фоновый процесс синхронизации +func (w *SyncWorker) Run(ctx context.Context) { + w.logger.Info("Запуск SyncWorker", + zap.Duration("ticker_interval", w.tickerInterval), + zap.Duration("idle_threshold", w.idleThreshold)) + + ticker := time.NewTicker(w.tickerInterval) + defer ticker.Stop() + + // Первый запуск сразу + w.processSync(ctx) + + for { + select { + case <-ctx.Done(): + w.logger.Info("Остановка SyncWorker") + return + case <-ticker.C: + w.processSync(ctx) + } + } +} + +// processSync обрабатывает синхронизацию для всех серверов, готовых к синхронизации +func (w *SyncWorker) processSync(ctx context.Context) { + // Получаем серверы, готовые для синхронизации + servers, err := w.accountRepo.GetServersForSync(w.idleThreshold) + if err != nil { + w.logger.Error("Ошибка получения серверов для синхронизации", zap.Error(err)) + return + } + + if len(servers) == 0 { + return + } + + w.logger.Info("Найдены серверы для синхронизации", + zap.Int("count", len(servers))) + + for _, server := range servers { + // Обрабатываем каждый сервер в отдельной горутине + go w.syncServer(ctx, server) + } +} + +// syncServer выполняет синхронизацию для конкретного сервера +func (w *SyncWorker) syncServer(ctx context.Context, server account.RMSServer) { + defer func() { + if r := recover(); r != nil { + w.logger.Error("Паника при синхронизации сервера", + zap.String("server_id", server.ID.String()), + zap.Any("recover", r)) + } + }() + + w.logger.Info("Начало синхронизации сервера", + zap.String("server_id", server.ID.String()), + zap.String("server_name", server.Name)) + + // Вызываем синхронизацию через syncService + err := w.syncService.SyncAllDataForServer(server.ID, false) + if err != nil { + w.logger.Error("Ошибка синхронизации сервера", + zap.String("server_id", server.ID.String()), + zap.Error(err)) + return + } + + // Обновляем время последней синхронизации + err = w.accountRepo.UpdateLastSync(server.ID) + if err != nil { + w.logger.Error("Ошибка обновления времени синхронизации", + zap.String("server_id", server.ID.String()), + zap.Error(err)) + } + + // Обновляем рекомендации после успешной синхронизации + if err := w.recService.RefreshRecommendations(server.ID); err != nil { + w.logger.Error("Ошибка обновления рекомендаций", + zap.String("server_id", server.ID.String()), + zap.Error(err)) + } + + w.logger.Info("Синхронизация сервера завершена успешно", + zap.String("server_id", server.ID.String())) +} diff --git a/internal/transport/http/handlers/drafts.go b/internal/transport/http/handlers/drafts.go index dce574b..f85e3fb 100644 --- a/internal/transport/http/handlers/drafts.go +++ b/internal/transport/http/handlers/drafts.go @@ -203,50 +203,98 @@ type CommitRequestDTO struct { SupplierID string `json:"supplier_id"` Comment string `json:"comment"` IncomingDocNum string `json:"incoming_document_number"` + IsProcessed bool `json:"is_processed"` } func (h *DraftsHandler) CommitDraft(c *gin.Context) { - userID := c.MustGet("userID").(uuid.UUID) + // Защита от паники + defer func() { + if r := recover(); r != nil { + logger.Log.Error("CRITICAL PANIC in CommitDraft Handler", + zap.Any("panic", r), + zap.Stack("stack"), + ) + c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("Internal Server Error: %v", r)}) + } + }() + + logger.Log.Info("--- HANDLER: Start CommitDraft ---", zap.String("path", c.Request.URL.Path)) + + userID, ok := c.Get("userID") + if !ok { + logger.Log.Error("HANDLER: UserID missing in context") + c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"}) + return + } + userUUID := userID.(uuid.UUID) + logger.Log.Info("HANDLER: UserID extracted", zap.String("user_id", userUUID.String())) + draftID, err := uuid.Parse(c.Param("id")) if err != nil { + logger.Log.Warn("HANDLER: Invalid DraftID", zap.String("param", c.Param("id")), zap.Error(err)) c.JSON(http.StatusBadRequest, gin.H{"error": "invalid draft id"}) return } + logger.Log.Info("HANDLER: DraftID parsed", zap.String("draft_id", draftID.String())) var req CommitRequestDTO if err := c.ShouldBindJSON(&req); err != nil { + logger.Log.Error("HANDLER: JSON Binding failed", zap.Error(err)) c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } + logger.Log.Info("HANDLER: Payload bound", + zap.String("date_incoming", req.DateIncoming), + zap.String("store_id", req.StoreID), + zap.String("supplier_id", req.SupplierID), + zap.String("incoming_doc_num", req.IncomingDocNum), + zap.Bool("is_processed", req.IsProcessed), + ) date, err := time.Parse("2006-01-02", req.DateIncoming) if err != nil { + logger.Log.Error("HANDLER: Date parsing failed", zap.String("date", req.DateIncoming), zap.Error(err)) c.JSON(http.StatusBadRequest, gin.H{"error": "invalid date format"}) return } + storeID, err := uuid.Parse(req.StoreID) if err != nil { + logger.Log.Error("HANDLER: StoreID parsing failed", zap.String("store_id", req.StoreID), zap.Error(err)) c.JSON(http.StatusBadRequest, gin.H{"error": "invalid store id"}) return } + supplierID, err := uuid.Parse(req.SupplierID) if err != nil { + logger.Log.Error("HANDLER: SupplierID parsing failed", zap.String("supplier_id", req.SupplierID), zap.Error(err)) c.JSON(http.StatusBadRequest, gin.H{"error": "invalid supplier id"}) return } + logger.Log.Info("HANDLER: Calling UpdateDraftHeader...", + zap.String("draft_id", draftID.String()), + zap.String("store_id", storeID.String()), + zap.String("supplier_id", supplierID.String()), + zap.Time("date", date), + ) + if err := h.service.UpdateDraftHeader(draftID, &storeID, &supplierID, date, req.Comment, req.IncomingDocNum); err != nil { + logger.Log.Error("HANDLER: UpdateDraftHeader failed", zap.Error(err)) c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to update header: " + err.Error()}) return } - docNum, err := h.service.CommitDraft(draftID, userID) + logger.Log.Info("HANDLER: Calling CommitDraft service...", zap.String("draft_id", draftID.String()), zap.String("user_id", userUUID.String())) + + docNum, err := h.service.CommitDraft(draftID, userUUID, req.IsProcessed) if err != nil { - logger.Log.Error("Commit failed", zap.Error(err)) + logger.Log.Warn("HANDLER: CommitDraft service failed", zap.Error(err)) c.JSON(http.StatusBadGateway, gin.H{"error": "RMS error: " + err.Error()}) return } + logger.Log.Info("HANDLER: Success!", zap.String("doc_num", docNum)) c.JSON(http.StatusOK, gin.H{"status": "completed", "document_number": docNum}) } diff --git a/internal/transport/http/handlers/invoices.go b/internal/transport/http/handlers/invoices.go index 3264fa6..62312e6 100644 --- a/internal/transport/http/handlers/invoices.go +++ b/internal/transport/http/handlers/invoices.go @@ -110,3 +110,23 @@ func (h *InvoiceHandler) SyncInvoices(c *gin.Context) { "message": "Синхронизация запущена", }) } + +// GetStats godoc +// @Summary Получить статистику по накладным +// @Description Возвращает статистику по накладным для текущего пользователя +// @Tags invoices +// @Produce json +// @Success 200 {object} invService.InvoiceStatsDTO +// @Failure 500 {object} map[string]string +func (h *InvoiceHandler) GetStats(c *gin.Context) { + userID := c.MustGet("userID").(uuid.UUID) + + stats, err := h.service.GetStats(userID) + if err != nil { + logger.Log.Error("Ошибка получения статистики", zap.Error(err)) + c.JSON(http.StatusInternalServerError, gin.H{"error": "Ошибка получения статистики"}) + return + } + + c.JSON(http.StatusOK, stats) +} diff --git a/internal/transport/http/handlers/recommendations.go b/internal/transport/http/handlers/recommendations.go index d7b9df7..69f577d 100644 --- a/internal/transport/http/handlers/recommendations.go +++ b/internal/transport/http/handlers/recommendations.go @@ -4,29 +4,56 @@ import ( "net/http" "github.com/gin-gonic/gin" + "github.com/google/uuid" "go.uber.org/zap" + "rmser/internal/domain/account" "rmser/internal/services/recommend" "rmser/pkg/logger" ) type RecommendationsHandler struct { - service *recommend.Service + service *recommend.Service + accountRepo account.Repository } -func NewRecommendationsHandler(service *recommend.Service) *RecommendationsHandler { - return &RecommendationsHandler{service: service} +func NewRecommendationsHandler(service *recommend.Service, accountRepo account.Repository) *RecommendationsHandler { + return &RecommendationsHandler{ + service: service, + accountRepo: accountRepo, + } } // GetRecommendations godoc // @Summary Получить список рекомендаций -// @Description Возвращает сгенерированные рекомендации (проблемные зоны учета) +// @Description Возвращает сгенерированные рекомендации (проблемные зоны учета) для активного сервера // @Tags recommendations // @Produce json // @Success 200 {array} recommendations.Recommendation // @Failure 500 {object} map[string]string func (h *RecommendationsHandler) GetRecommendations(c *gin.Context) { - recs, err := h.service.GetRecommendations() + userID := c.MustGet("userID").(uuid.UUID) + + // Получаем активный сервер пользователя + server, err := h.accountRepo.GetActiveServer(userID) + if err != nil { + logger.Log.Error("Ошибка получения активного сервера", zap.Error(err)) + c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to get active server"}) + return + } + if server == nil { + c.JSON(http.StatusNotFound, gin.H{"error": "no active server"}) + return + } + + // Сначала обновляем рекомендации + if err := h.service.RefreshRecommendations(server.ID); err != nil { + logger.Log.Error("Ошибка обновления рекомендаций", zap.Error(err)) + // Не прерываем выполнение, продолжаем с текущими данными + } + + // Затем получаем рекомендации + recs, err := h.service.GetRecommendations(server.ID) if err != nil { logger.Log.Error("Ошибка получения рекомендаций", zap.Error(err)) c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) diff --git a/internal/transport/http/handlers/settings.go b/internal/transport/http/handlers/settings.go index 19a6007..e68ce5d 100644 --- a/internal/transport/http/handlers/settings.go +++ b/internal/transport/http/handlers/settings.go @@ -2,6 +2,7 @@ package handlers import ( "net/http" + "time" "github.com/gin-gonic/gin" "github.com/google/uuid" @@ -48,13 +49,16 @@ func (h *SettingsHandler) SetNotifier(n Notifier) { // 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 + 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 + SyncInterval int `json:"sync_interval"` // Интервал синхронизации в минутах + LastSyncAt *time.Time `json:"last_sync_at"` // Время последней синхронизации + LastActivityAt *time.Time `json:"last_activity_at"` // Время последней активности } // GetSettings возвращает настройки активного сервера + роль пользователя @@ -77,11 +81,14 @@ func (h *SettingsHandler) GetSettings(c *gin.Context) { } resp := SettingsResponse{ - ID: server.ID.String(), - Name: server.Name, - BaseURL: server.BaseURL, - AutoConduct: server.AutoProcess, - Role: string(role), + ID: server.ID.String(), + Name: server.Name, + BaseURL: server.BaseURL, + AutoConduct: server.AutoProcess, + Role: string(role), + SyncInterval: server.SyncInterval, + LastSyncAt: server.LastSyncAt, + LastActivityAt: server.LastActivityAt, } if server.DefaultStoreID != nil { @@ -96,16 +103,17 @@ func (h *SettingsHandler) GetSettings(c *gin.Context) { c.JSON(http.StatusOK, resp) } -// UpdateSettingsDTO +// UpdateSettingsDTO - DTO для частичного обновления настроек (PATCH-семантика) type UpdateSettingsDTO struct { - Name string `json:"name"` - DefaultStoreID string `json:"default_store_id"` - RootGroupID string `json:"root_group_id"` - AutoProcess bool `json:"auto_process"` - AutoConduct bool `json:"auto_conduct"` + Name *string `json:"name"` + DefaultStoreID *string `json:"default_store_id"` + RootGroupID *string `json:"root_group_id"` + AutoProcess *bool `json:"auto_process"` // Legacy для обратной совместимости + AutoConduct *bool `json:"auto_conduct"` // Новое поле + SyncInterval *int `json:"sync_interval,omitempty"` // Интервал синхронизации в минутах (5 - 10080) } -// UpdateSettings сохраняет настройки +// UpdateSettings сохраняет настройки с PATCH-семантикой func (h *SettingsHandler) UpdateSettings(c *gin.Context) { userID := c.MustGet("userID").(uuid.UUID) @@ -115,6 +123,11 @@ func (h *SettingsHandler) UpdateSettings(c *gin.Context) { return } + // Логирование полученных данных для отладки + logger.Log.Info("Получен запрос на обновление настроек", + zap.Any("request", req), + ) + server, err := h.accountRepo.GetActiveServer(userID) if err != nil || server == nil { c.JSON(http.StatusNotFound, gin.H{"error": "No active server"}) @@ -132,31 +145,56 @@ func (h *SettingsHandler) UpdateSettings(c *gin.Context) { return } - if req.Name != "" { - server.Name = req.Name + // Обновление имени (только если передано) + if req.Name != nil { + server.Name = *req.Name } - if req.AutoConduct { - server.AutoProcess = true - } else { - server.AutoProcess = req.AutoProcess || req.AutoConduct + // Обновление флага авто-проведения + if req.AutoConduct != nil { + server.AutoProcess = *req.AutoConduct + } else if req.AutoProcess != nil { + // Fallback для старых клиентов, которые используют legacy поле + server.AutoProcess = *req.AutoProcess } - if req.DefaultStoreID != "" { - if uid, err := uuid.Parse(req.DefaultStoreID); err == nil { - server.DefaultStoreID = &uid + // Обновление интервала синхронизации + if req.SyncInterval != nil { + // Валидация диапазона: от 5 минут до 1 недели (10080 минут) + if *req.SyncInterval < 5 || *req.SyncInterval > 10080 { + c.JSON(http.StatusBadRequest, gin.H{"error": "sync_interval должен быть от 5 минут до 1 недели (10080 минут)"}) + return } - } else { - server.DefaultStoreID = nil + server.SyncInterval = *req.SyncInterval } - if req.RootGroupID != "" { - if uid, err := uuid.Parse(req.RootGroupID); err == nil { - server.RootGroupGUID = &uid + // Обновление DefaultStoreID + if req.DefaultStoreID != nil { + if *req.DefaultStoreID == "" { + // Пустая строка -> сбрасываем в nil + server.DefaultStoreID = nil + } else { + // UUID -> обновляем + if uid, err := uuid.Parse(*req.DefaultStoreID); err == nil { + server.DefaultStoreID = &uid + } } - } else { - server.RootGroupGUID = nil } + // Если nil -> не трогаем текущее значение + + // Обновление RootGroupID + if req.RootGroupID != nil { + if *req.RootGroupID == "" { + // Пустая строка -> сбрасываем в nil + server.RootGroupGUID = nil + } else { + // UUID -> обновляем + if uid, err := uuid.Parse(*req.RootGroupID); err == nil { + server.RootGroupGUID = &uid + } + } + } + // Если nil -> не трогаем текущее значение if err := h.accountRepo.SaveServerSettings(server); err != nil { logger.Log.Error("Failed to save settings", zap.Error(err)) diff --git a/internal/transport/telegram/bot.go b/internal/transport/telegram/bot.go index 5fc65bf..bd0b018 100644 --- a/internal/transport/telegram/bot.go +++ b/internal/transport/telegram/bot.go @@ -202,17 +202,17 @@ func (bot *Bot) registrationMiddleware(next tele.HandlerFunc) tele.HandlerFunc { func (bot *Bot) handleStartCommand(c tele.Context) error { payload := c.Message().Payload - + // Обработка desktop авторизации if payload != "" && strings.HasPrefix(payload, "auth_") { sessionID := strings.TrimPrefix(payload, "auth_") telegramID := c.Sender().ID - + logger.Log.Info("Обработка desktop авторизации", zap.String("session_id", sessionID), zap.Int64("telegram_id", telegramID), ) - + if err := bot.authService.ConfirmDesktopAuth(sessionID, telegramID); err != nil { logger.Log.Error("Ошибка подтверждения desktop авторизации", zap.String("session_id", sessionID), @@ -221,10 +221,10 @@ func (bot *Bot) handleStartCommand(c tele.Context) error { ) return c.Send("❌ Ошибка авторизации. Попробуйте снова.", tele.ModeHTML) } - + return c.Send("✅ Авторизация успешна! Вы можете вернуться в приложение.", tele.ModeHTML) } - + if payload != "" && strings.HasPrefix(payload, "invite_") { return bot.handleInviteLink(c, strings.TrimPrefix(payload, "invite_")) } @@ -417,19 +417,70 @@ func (bot *Bot) renderServersMenu(c tele.Context) error { } 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()) + btn := menu.Data(label, "srv_menu_"+s.ID.String()) rows = append(rows, menu.Row(btn)) } btnAdd := menu.Data("➕ Добавить сервер", "act_add_server") - btnDel := menu.Data("⚙️ Управление / Удаление", "act_del_server_menu") btnBack := menu.Data("🔙 Назад", "nav_main") - rows = append(rows, menu.Row(btnAdd, btnDel)) + rows = append(rows, menu.Row(btnAdd)) rows = append(rows, menu.Row(btnBack)) menu.Inline(rows...) - txt := fmt.Sprintf("🖥 Ваши серверы (%d):\n\nНажмите на сервер, чтобы сделать его активным.", len(servers)) + txt := fmt.Sprintf("🖥 Ваши серверы (%d):\n\nНажмите на сервер для управления.", len(servers)) + return c.EditOrSend(txt, menu, tele.ModeHTML) +} + +// renderServerMenu показывает подменю управления конкретным сервером +func (bot *Bot) renderServerMenu(c tele.Context, serverID uuid.UUID) error { + userDB, _ := bot.accountRepo.GetUserByTelegramID(c.Sender().ID) + server, err := bot.accountRepo.GetServerByID(serverID) + if err != nil { + return c.Send("Ошибка: сервер не найден") + } + + role, _ := bot.accountRepo.GetUserRole(userDB.ID, server.ID) + activeServer, _ := bot.accountRepo.GetActiveServer(userDB.ID) + isActive := activeServer != nil && activeServer.ID == server.ID + + menu := &tele.ReplyMarkup{} + var rows []tele.Row + + // Кнопка "Выбрать активным" (доступна всем) + if !isActive { + btnSetActive := menu.Data("✅ Выбрать активным", "srv_set_active_"+server.ID.String()) + rows = append(rows, menu.Row(btnSetActive)) + } else { + btnActive := menu.Data("🟢 Активный сервер", "noop") + rows = append(rows, menu.Row(btnActive)) + } + + // Кнопка "Показать URL/Логин" (доступна Admin и Owner) + if role == account.RoleOwner || role == account.RoleAdmin { + btnShowCreds := menu.Data("👁 Показать URL/Логин", "srv_show_creds_"+server.ID.String()) + btnInvite := menu.Data("📩 Пригласить сотрудника", fmt.Sprintf("gen_invite_%s", server.ID.String())) + rows = append(rows, menu.Row(btnShowCreds, btnInvite)) + } + + // Кнопка "Обновить логин-пароль" (только Owner) + if role == account.RoleOwner { + btnUpdateCreds := menu.Data("✏️ Обновить логин-пароль", "srv_update_creds_"+server.ID.String()) + rows = append(rows, menu.Row(btnUpdateCreds)) + } + + // Кнопка "Удалить сервер" (только Owner) + if role == account.RoleOwner { + btnDelete := menu.Data("❌ Удалить сервер", "srv_delete_"+server.ID.String()) + rows = append(rows, menu.Row(btnDelete)) + } + + btnBack := menu.Data("🔙 Назад к списку", "nav_servers") + rows = append(rows, menu.Row(btnBack)) + menu.Inline(rows...) + + txt := fmt.Sprintf("⚙️ Управление сервером\n\n🏢 Название: %s\n🔗 URL: %s\n👤 Ваша роль: %s", + server.Name, server.BaseURL, role) return c.EditOrSend(txt, menu, tele.ModeHTML) } @@ -536,6 +587,131 @@ func (bot *Bot) handleCallback(c tele.Context) error { return bot.handleBillingCallbacks(c, data, userDB) } + // Обработка кнопок подменю сервера + if strings.HasPrefix(data, "srv_menu_") { + serverIDStr := strings.TrimPrefix(data, "srv_menu_") + serverIDStr = strings.TrimSpace(serverIDStr) + if idx := strings.Index(serverIDStr, "|"); idx != -1 { + serverIDStr = serverIDStr[:idx] + } + targetID := parseUUID(serverIDStr) + return bot.renderServerMenu(c, targetID) + } + + if strings.HasPrefix(data, "srv_set_active_") { + serverIDStr := strings.TrimPrefix(data, "srv_set_active_") + serverIDStr = strings.TrimSpace(serverIDStr) + if idx := strings.Index(serverIDStr, "|"); idx != -1 { + serverIDStr = serverIDStr[:idx] + } + 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: "Ошибка: доступ запрещен"}) + } + bot.rmsFactory.ClearCacheForUser(userDB.ID) + c.Respond(&tele.CallbackResponse{Text: "✅ Сервер выбран активным"}) + return bot.renderServerMenu(c, targetID) + } + + if strings.HasPrefix(data, "srv_show_creds_") { + serverIDStr := strings.TrimPrefix(data, "srv_show_creds_") + serverIDStr = strings.TrimSpace(serverIDStr) + if idx := strings.Index(serverIDStr, "|"); idx != -1 { + serverIDStr = serverIDStr[:idx] + } + targetID := parseUUID(serverIDStr) + server, err := bot.accountRepo.GetServerByID(targetID) + if err != nil { + return c.Respond(&tele.CallbackResponse{Text: "Ошибка: сервер не найден"}) + } + role, _ := bot.accountRepo.GetUserRole(userDB.ID, server.ID) + if role != account.RoleOwner && role != account.RoleAdmin { + return c.Respond(&tele.CallbackResponse{Text: "Ошибка: недостаточно прав"}) + } + // Получаем личные креды пользователя через GetServerUsers + serverUsers, err := bot.accountRepo.GetServerUsers(server.ID) + if err != nil { + return c.Respond(&tele.CallbackResponse{Text: "Ошибка получения данных"}) + } + var login string + for _, su := range serverUsers { + if su.UserID == userDB.ID { + login = su.Login + break + } + } + if login == "" { + return c.Respond(&tele.CallbackResponse{Text: "У вас нет сохраненных учетных данных"}) + } + c.Respond() + return c.Send(fmt.Sprintf("🔑 Учетные данные сервера\n\n🏢 Название: %s\n🔗 URL: %s\n👤 Логин: %s\n🔒 Пароль: ***скрыт***", + server.Name, server.BaseURL, login), tele.ModeHTML) + } + + if strings.HasPrefix(data, "srv_update_creds_") { + serverIDStr := strings.TrimPrefix(data, "srv_update_creds_") + serverIDStr = strings.TrimSpace(serverIDStr) + if idx := strings.Index(serverIDStr, "|"); idx != -1 { + serverIDStr = serverIDStr[:idx] + } + targetID := parseUUID(serverIDStr) + server, err := bot.accountRepo.GetServerByID(targetID) + if err != nil { + return c.Respond(&tele.CallbackResponse{Text: "Ошибка: сервер не найден"}) + } + role, _ := bot.accountRepo.GetUserRole(userDB.ID, server.ID) + if role != account.RoleOwner { + return c.Respond(&tele.CallbackResponse{Text: "Ошибка: только владелец может обновлять учетные данные"}) + } + // Сохраняем ID сервера в контексте FSM + bot.fsm.UpdateContext(c.Sender().ID, func(ctx *UserContext) { + ctx.EditingServerID = server.ID + ctx.TempURL = server.BaseURL + }) + bot.fsm.SetState(c.Sender().ID, StateUpdateServerLogin) + c.Respond() + return c.EditOrSend("✏️ Обновление учетных данных\n\nВведите новый логин для сервера "+server.Name+".\n\n(Напишите 'отмена' для выхода)", tele.ModeHTML) + } + + if strings.HasPrefix(data, "srv_delete_") { + serverIDStr := strings.TrimPrefix(data, "srv_delete_") + serverIDStr = strings.TrimSpace(serverIDStr) + if idx := strings.Index(serverIDStr, "|"); idx != -1 { + serverIDStr = serverIDStr[:idx] + } + targetID := parseUUID(serverIDStr) + role, err := bot.accountRepo.GetUserRole(userDB.ID, targetID) + if err != nil { + return c.Respond(&tele.CallbackResponse{Text: "Ошибка прав доступа"}) + } + if role != account.RoleOwner { + return c.Respond(&tele.CallbackResponse{Text: "Только владелец может удалить сервер"}) + } + // Подтверждение удаления + menu := &tele.ReplyMarkup{} + btnYes := menu.Data("✅ Да, удалить", "srv_delete_confirm_"+targetID.String()) + btnNo := menu.Data("❌ Отмена", "srv_menu_"+targetID.String()) + menu.Inline(menu.Row(btnYes), menu.Row(btnNo)) + server, _ := bot.accountRepo.GetServerByID(targetID) + return c.EditOrSend("⚠️ Подтверждение удаления\n\nВы уверены, что хотите удалить сервер "+server.Name+"?\n\nЭто действие необратимо!", menu, tele.ModeHTML) + } + + if strings.HasPrefix(data, "srv_delete_confirm_") { + serverIDStr := strings.TrimPrefix(data, "srv_delete_confirm_") + serverIDStr = strings.TrimSpace(serverIDStr) + if idx := strings.Index(serverIDStr, "|"); idx != -1 { + serverIDStr = serverIDStr[:idx] + } + targetID := parseUUID(serverIDStr) + if err := bot.accountRepo.DeleteServer(targetID); err != nil { + return c.Respond(&tele.CallbackResponse{Text: "Ошибка удаления"}) + } + bot.rmsFactory.ClearCacheForUser(userDB.ID) + c.Respond(&tele.CallbackResponse{Text: "Сервер удален"}) + return bot.renderServersMenu(c) + } + if strings.HasPrefix(data, "set_server_") { serverIDStr := strings.TrimPrefix(data, "set_server_") serverIDStr = strings.TrimSpace(serverIDStr) @@ -799,6 +975,7 @@ func (bot *Bot) handleText(c tele.Context) error { userID := c.Sender().ID state := bot.fsm.GetState(userID) text := strings.TrimSpace(c.Text()) + userDB, _ := bot.accountRepo.GetUserByTelegramID(userID) if bot.maintenanceMode && !bot.isDev(userID) { return c.Send("Сервис на обслуживании", tele.ModeHTML) @@ -888,6 +1065,52 @@ func (bot *Bot) handleText(c tele.Context) error { ctx.BillingTargetURL = text }) return bot.renderTariffShowcase(c, text) + + case StateUpdateServerLogin: + ctx := bot.fsm.GetContext(userID) + if ctx.EditingServerID == uuid.Nil { + bot.fsm.Reset(userID) + return bot.renderMainMenu(c) + } + bot.fsm.UpdateContext(userID, func(uCtx *UserContext) { + uCtx.TempLogin = text + uCtx.State = StateUpdateServerPassword + }) + return c.Send("🔑 Введите новый пароль:") + + case StateUpdateServerPassword: + password := text + ctx := bot.fsm.GetContext(userID) + if ctx.EditingServerID == uuid.Nil { + bot.fsm.Reset(userID) + return bot.renderMainMenu(c) + } + server, err := bot.accountRepo.GetServerByID(ctx.EditingServerID) + if err != nil { + bot.fsm.Reset(userID) + return c.Send("❌ Ошибка: сервер не найден") + } + // Проверяем новые креды + msg, _ := bot.b.Send(c.Sender(), "⏳ Проверяю подключение...") + tempClient := bot.rmsFactory.CreateClientFromRawCredentials(ctx.TempURL, ctx.TempLogin, password) + if err := tempClient.Auth(); err != nil { + bot.b.Delete(msg) + bot.fsm.Reset(userID) + return c.Send(fmt.Sprintf("❌ Ошибка авторизации: %v\nПроверьте логин/пароль.", err)) + } + // Шифруем пароль и сохраняем + encPass, _ := bot.cryptoManager.Encrypt(password) + // Обновляем креды через ConnectServer (он обновит существующую связь) + _, err = bot.accountRepo.ConnectServer(userDB.ID, ctx.TempURL, ctx.TempLogin, encPass, server.Name) + bot.b.Delete(msg) + if err != nil { + bot.fsm.Reset(userID) + return c.Send("❌ Ошибка обновления данных") + } + bot.fsm.Reset(userID) + bot.rmsFactory.ClearCacheForUser(userDB.ID) + c.Send("✅ Учетные данные обновлены!\n\nТеперь вы можете использовать новые логин и пароль для подключения к серверу.", tele.ModeHTML) + return bot.renderServerMenu(c, server.ID) } return nil diff --git a/internal/transport/telegram/fsm.go b/internal/transport/telegram/fsm.go index 10a20aa..ce28d9e 100644 --- a/internal/transport/telegram/fsm.go +++ b/internal/transport/telegram/fsm.go @@ -17,10 +17,12 @@ const ( StateAddServerConfirmName StateAddServerInputName StateBillingGiftURL + StateUpdateServerLogin // Обновление логина для существующего сервера + StateUpdateServerPassword // Обновление пароля для существующего сервера // Состояния редактора черновиков (начиная с 100) - StateDraftEditItemName State = 100 // Ожидание ввода нового названия позиции - StateDraftEditItemQty State = 101 // Ожидание ввода количества + StateDraftEditItemName State = 100 // Ожидание ввода нового названия позиции + StateDraftEditItemQty State = 101 // Ожидание ввода количества StateDraftEditItemPrice State = 102 // Ожидание ввода цены ) @@ -34,6 +36,9 @@ type UserContext struct { TempPassword string TempServerName string + // Поля для обновления сервера + EditingServerID uuid.UUID // ID редактируемого сервера + // Поля для биллинга BillingTargetURL string diff --git a/migrations/20250202040746_add_sync_fields_to_rms_servers.sql b/migrations/20250202040746_add_sync_fields_to_rms_servers.sql new file mode 100644 index 0000000..99db25c --- /dev/null +++ b/migrations/20250202040746_add_sync_fields_to_rms_servers.sql @@ -0,0 +1,17 @@ +-- Добавляем поля для синхронизации в таблицу rms_servers +-- Миграция для отслеживания активности и времени синхронизации + +-- Добавляем колонку sync_interval со значением по умолчанию 360 (6 часов) +ALTER TABLE rms_servers ADD COLUMN sync_interval INTEGER NOT NULL DEFAULT 360; + +-- Добавляем колонку last_sync_at (время последней успешной синхронизации) +ALTER TABLE rms_servers ADD COLUMN last_sync_at TIMESTAMP WITH TIME ZONE; + +-- Добавляем колонку last_activity_at (время последнего действия пользователя) +ALTER TABLE rms_servers ADD COLUMN last_activity_at TIMESTAMP WITH TIME ZONE; + +-- Создаем индекс для оптимизации запросов на синхронизацию +CREATE INDEX idx_rms_servers_sync ON rms_servers(deleted_at, last_sync_at, sync_interval); + +-- Создаем индекс для оптимизации запросов по активности +CREATE INDEX idx_rms_servers_activity ON rms_servers(deleted_at, last_activity_at); diff --git a/migrations/20250202051336_add_server_id_to_recommendations.sql b/migrations/20250202051336_add_server_id_to_recommendations.sql new file mode 100644 index 0000000..c1fe5b6 --- /dev/null +++ b/migrations/20250202051336_add_server_id_to_recommendations.sql @@ -0,0 +1,12 @@ +-- +goose Up +ALTER TABLE recommendations ADD COLUMN IF NOT EXISTS rms_server_id UUID; +CREATE INDEX IF NOT EXISTS idx_recommendations_server_id ON recommendations(rms_server_id); + +-- Удаляем старые записи без server_id (они невалидны) +DELETE FROM recommendations WHERE rms_server_id IS NULL; + +-- Делаем поле NOT NULL после очистки +ALTER TABLE recommendations ALTER COLUMN rms_server_id SET NOT NULL; + +-- +goose Down +ALTER TABLE recommendations DROP COLUMN IF EXISTS rms_server_id; diff --git a/pkg/logger/logger.go b/pkg/logger/logger.go index df788a2..3078acf 100644 --- a/pkg/logger/logger.go +++ b/pkg/logger/logger.go @@ -1,25 +1,25 @@ -package logger - -import ( - "go.uber.org/zap" - "go.uber.org/zap/zapcore" -) - -var Log *zap.Logger - -func Init(mode string) { - var config zap.Config - - if mode == "release" { - config = zap.NewProductionConfig() - } else { - config = zap.NewDevelopmentConfig() - config.EncoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder - } - - var err error - Log, err = config.Build() - if err != nil { - panic("не удалось инициализировать логгер: " + err.Error()) - } -} \ No newline at end of file +package logger + +import ( + "go.uber.org/zap" + "go.uber.org/zap/zapcore" +) + +var Log *zap.Logger + +func Init(mode string) { + var config zap.Config + + if mode == "release" { + config = zap.NewProductionConfig() + } else { + config = zap.NewDevelopmentConfig() + config.EncoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder + } + + var err error + Log, err = config.Build() + if err != nil { + panic("не удалось инициализировать логгер: " + err.Error()) + } +} diff --git a/rmser-view/project_context.md b/rmser-view/project_context.md deleted file mode 100644 index a00433a..0000000 --- a/rmser-view/project_context.md +++ /dev/null @@ -1,10659 +0,0 @@ -# =================================================================== -# Полный контекст React Typescript проекта -# Сгенерировано: 2026-01-28 08:00:40 -# =================================================================== - -Это полный дамп исходного кода React Typescript (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": { - "@hello-pangea/dnd": "^18.0.1", - "@tanstack/react-query": "^5.90.12", - "@twa-dev/sdk": "^8.0.2", - "antd": "^6.1.0", - "axios": "^1.13.2", - "clsx": "^2.1.1", - "lucide-react": "^0.563.0", - "qrcode.react": "^4.2.0", - "react": "^19.2.0", - "react-dom": "^19.2.0", - "react-dropzone": "^14.3.8", - "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/@hello-pangea/dnd": { - "version": "18.0.1", - "resolved": "https://registry.npmjs.org/@hello-pangea/dnd/-/dnd-18.0.1.tgz", - "integrity": "sha512-xojVWG8s/TGrKT1fC8K2tIWeejJYTAeJuj36zM//yEm/ZrnZUSFGS15BpO+jGZT1ybWvyXmeDJwPYb4dhWlbZQ==", - "license": "Apache-2.0", - "dependencies": { - "@babel/runtime": "^7.26.7", - "css-box-model": "^1.2.1", - "raf-schd": "^4.0.3", - "react-redux": "^9.2.0", - "redux": "^5.0.1" - }, - "peerDependencies": { - "react": "^18.0.0 || ^19.0.0", - "react-dom": "^18.0.0 || ^19.0.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/@types/use-sync-external-store": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz", - "integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==", - "license": "MIT" - }, - "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/attr-accept": { - "version": "2.2.5", - "resolved": "https://registry.npmjs.org/attr-accept/-/attr-accept-2.2.5.tgz", - "integrity": "sha512-0bDNnY/u6pPwHDMoF0FieU354oBi0a8rD9FcsLwzcGWbc8KS8KPIi7y+s13OlVY+gMWc/9xEMUgNE6Qm8ZllYQ==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "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/css-box-model": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/css-box-model/-/css-box-model-1.2.1.tgz", - "integrity": "sha512-a7Vr4Q/kd/aw96bnJG332W9V9LkJO69JRcaCYDUqjp6/z0w6VcZjgAcTbgFxEPfBgdnAwlh3iwu+hLopa+flJw==", - "license": "MIT", - "dependencies": { - "tiny-invariant": "^1.0.6" - } - }, - "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/file-selector": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/file-selector/-/file-selector-2.1.2.tgz", - "integrity": "sha512-QgXo+mXTe8ljeqUFaX3QVHc5osSItJ/Km+xpocx0aSqWGMSCf6qYs/VnzZgS864Pjn5iceMRFigeAV7AfTlaig==", - "license": "MIT", - "dependencies": { - "tslib": "^2.7.0" - }, - "engines": { - "node": ">= 12" - } - }, - "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==", - "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/loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "license": "MIT", - "dependencies": { - "js-tokens": "^3.0.0 || ^4.0.0" - }, - "bin": { - "loose-envify": "cli.js" - } - }, - "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/lucide-react": { - "version": "0.563.0", - "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.563.0.tgz", - "integrity": "sha512-8dXPB2GI4dI8jV4MgUDGBeLdGk8ekfqVZ0BdLcrRzocGgG75ltNEmWS+gE7uokKF/0oSUuczNDT+g9hFJ23FkA==", - "license": "ISC", - "peerDependencies": { - "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" - } - }, - "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/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "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/prop-types": { - "version": "15.8.1", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", - "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", - "license": "MIT", - "dependencies": { - "loose-envify": "^1.4.0", - "object-assign": "^4.1.1", - "react-is": "^16.13.1" - } - }, - "node_modules/prop-types/node_modules/react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "license": "MIT" - }, - "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/qrcode.react": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/qrcode.react/-/qrcode.react-4.2.0.tgz", - "integrity": "sha512-QpgqWi8rD9DsS9EP3z7BT+5lY5SFhsqGjpgW5DY/i3mK4M9DTBNz3ErMi8BWYEfI3L0d8GIbGmcdFAS1uIRGjA==", - "license": "ISC", - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" - } - }, - "node_modules/raf-schd": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/raf-schd/-/raf-schd-4.0.3.tgz", - "integrity": "sha512-tQkJl2GRWh83ui2DiPTJz9wEiMN20syf+5oKfB03yYP7ioZcJwsIK8FjrtLwH1m7C7e+Tt2yYBlrOpdT+dyeIQ==", - "license": "MIT" - }, - "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", - "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-dropzone": { - "version": "14.3.8", - "resolved": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-14.3.8.tgz", - "integrity": "sha512-sBgODnq+lcA4P296DY4wacOZz3JFpD99fp+hb//iBO2HHnyeZU3FwWyXJ6salNpqQdsZrgMrotuko/BdJMV8Ug==", - "license": "MIT", - "dependencies": { - "attr-accept": "^2.2.4", - "file-selector": "^2.1.0", - "prop-types": "^15.8.1" - }, - "engines": { - "node": ">= 10.13" - }, - "peerDependencies": { - "react": ">= 16.8 || 18.0.0" - } - }, - "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-redux": { - "version": "9.2.0", - "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz", - "integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==", - "license": "MIT", - "dependencies": { - "@types/use-sync-external-store": "^0.0.6", - "use-sync-external-store": "^1.4.0" - }, - "peerDependencies": { - "@types/react": "^18.2.25 || ^19", - "react": "^18.0 || ^19", - "redux": "^5.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "redux": { - "optional": true - } - } - }, - "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/redux": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", - "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==", - "license": "MIT", - "peer": true - }, - "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/tiny-invariant": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", - "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", - "license": "MIT" - }, - "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/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "license": "0BSD" - }, - "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/use-sync-external-store": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", - "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", - "license": "MIT", - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.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": { - "@hello-pangea/dnd": "^18.0.1", - "@tanstack/react-query": "^5.90.12", - "@twa-dev/sdk": "^8.0.2", - "antd": "^6.1.0", - "axios": "^1.13.2", - "clsx": "^2.1.1", - "lucide-react": "^0.563.0", - "qrcode.react": "^4.2.0", - "react": "^19.2.0", - "react-dom": "^19.2.0", - "react-dropzone": "^14.3.8", - "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, - useLocation, -} 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 { InvoiceViewPage } from "./pages/InvoiceViewPage"; -import { DraftsList } from "./pages/DraftsList"; -import { SettingsPage } from "./pages/SettingsPage"; -import { UNAUTHORIZED_EVENT, MAINTENANCE_EVENT } from "./services/api"; -import MaintenancePage from "./pages/MaintenancePage"; -import { usePlatform } from "./hooks/usePlatform"; -import { useAuthStore } from "./stores/authStore"; -import { DesktopAuthScreen } from "./pages/desktop/auth/DesktopAuthScreen"; -import { MobileBrowserStub } from "./pages/desktop/auth/MobileBrowserStub"; -import { DesktopLayout } from "./layouts/DesktopLayout/DesktopLayout"; -import { InvoicesDashboard } from "./pages/desktop/dashboard/InvoicesDashboard"; - -// Компонент-заглушка для внешних браузеров -const NotInTelegramScreen = () => ( -
- - Перейти в бота - - } - /> -
-); - -// Protected Route для десктопной версии -const ProtectedDesktopRoute = ({ children }: { children: React.ReactNode }) => { - const { isAuthenticated } = useAuthStore(); - const location = useLocation(); - - if (!isAuthenticated) { - return ; - } - - return <>{children}; -}; - -// Внутренний компонент с логикой, которая требует контекста роутера -const AppContent = () => { - const [isUnauthorized, setIsUnauthorized] = useState(false); - const [isMaintenance, setIsMaintenance] = useState(false); - const tg = window.Telegram?.WebApp; - const platform = usePlatform(); - const location = useLocation(); // Теперь это безопасно, т.к. мы внутри BrowserRouter - - // Проверяем, есть ли данные от Telegram - const isInTelegram = !!tg?.initData; - - // Проверяем, находимся ли мы на десктопном роуте - const isDesktopRoute = location.pathname.startsWith("/web"); - - useEffect(() => { - const handleUnauthorized = () => setIsUnauthorized(true); - const handleMaintenance = () => setIsMaintenance(true); - window.addEventListener(UNAUTHORIZED_EVENT, handleUnauthorized); - window.addEventListener(MAINTENANCE_EVENT, handleMaintenance); - - if (tg) { - tg.expand(); - } - - return () => { - window.removeEventListener(UNAUTHORIZED_EVENT, handleUnauthorized); - window.removeEventListener(MAINTENANCE_EVENT, handleMaintenance); - }; - }, [tg]); - - // Если открыто не в Telegram и это не десктопный роут — блокируем всё - if (!isInTelegram && !isDesktopRoute) { - return ; - } - - // Если это десктопный роут и платформа - мобильный браузер - if (isDesktopRoute && platform === "MobileBrowser") { - return ; - } - - // Если бэкенд вернул 401 - if (isUnauthorized) { - return ( -
- -
- ); - } - - // Если бэкенд вернул 503 (режим технического обслуживания) - if (isMaintenance) { - return ; - } - - return ( - - {/* Мобильные роуты (существующие) */} - }> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - - - {/* Десктопные роуты */} - } /> - }> - - - - } - /> - - - ); -}; - -// Главный компонент-обертка -function App() { - return ( - - - - - - ); -} - -export default App; - -``` - -# =================================================================== -# Файл: src/components/DragDropZone.tsx -# =================================================================== - -``` -import React from 'react'; -import { useDropzone } from 'react-dropzone'; -import { InboxOutlined } from '@ant-design/icons'; -import { Typography } from 'antd'; - -const { Text } = Typography; - -interface DragDropZoneProps { - onDrop: (files: File[]) => void; - accept?: Record; - maxSize?: number; - maxFiles?: number; - disabled?: boolean; - className?: string; - children?: React.ReactNode; -} - -/** - * Компонент зоны перетаскивания файлов - * Обертка над react-dropzone с Ant Design стилизацией - */ -export const DragDropZone: React.FC = ({ - onDrop, - accept = { - 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': ['.xlsx'], - 'application/vnd.ms-excel': ['.xls'], - 'image/*': ['.png', '.jpg', '.jpeg', '.gif', '.webp'], - }, - maxSize = 10 * 1024 * 1024, // 10MB по умолчанию - maxFiles = 10, - disabled = false, - className = '', - children, -}) => { - const { getRootProps, getInputProps, isDragActive, isDragReject } = useDropzone({ - onDrop, - accept, - maxSize, - maxFiles, - disabled, - }); - - const getBorderColor = () => { - if (isDragReject) return '#ff4d4f'; - if (isDragActive) return '#1890ff'; - return '#d9d9d9'; - }; - - const getBackgroundColor = () => { - if (isDragActive) return '#e6f7ff'; - if (disabled) return '#f5f5f5'; - return '#fafafa'; - }; - - return ( -
- - {children || ( -
- -
- {isDragActive ? ( - Отпустите файлы здесь - ) : ( -
- Перетащите файлы сюда или нажмите для выбора -
- - Поддерживаются: .xlsx, .xls, изображения (макс. {maxSize / 1024 / 1024}MB) - -
- )} -
-
- )} -
- ); -}; - -``` - -# =================================================================== -# Файл: 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, useRef } from "react"; -import { Draggable } from "@hello-pangea/dnd"; -import { - Card, - Flex, - InputNumber, - Typography, - Select, - Tag, - Button, - Divider, - Modal, - Popconfirm, -} from "antd"; -import { - SyncOutlined, - PlusOutlined, - WarningFilled, - DeleteOutlined, -} from "@ant-design/icons"; -import { GripVertical } from "lucide-react"; -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; - index: number; - onUpdate: (itemId: string, changes: UpdateDraftItemRequest) => void; - onDelete: (itemId: string) => void; - isUpdating: boolean; - recommendations?: Recommendation[]; - isReordering: boolean; -} - -type FieldType = "quantity" | "price" | "sum"; - -export const DraftItemRow: React.FC = ({ - item, - index, - onUpdate, - onDelete, - isUpdating, - recommendations = [], - isReordering, -}) => { - const [isModalOpen, setIsModalOpen] = useState(false); - - // --- Локальное состояние значений (строки для удобства ввода) --- - const [localQty, setLocalQty] = useState(item.quantity); - const [localPrice, setLocalPrice] = useState(item.price); - const [localSum, setLocalSum] = useState(item.sum); - - // --- История редактирования (Stack) --- - // Храним 2 последних отредактированных поля. - // Инициализируем из пропсов или дефолтно ['quantity', 'price'], чтобы пересчитывалась сумма. - const editStack = useRef([ - (item.last_edited_field_1 as FieldType) || "quantity", - (item.last_edited_field_2 as FieldType) || "price", - ]); - - // Храним ссылку на предыдущую версию item, чтобы сравнивать изменения - - // --- Синхронизация с сервером --- - useEffect(() => { - // Если мы ждем ответа от сервера, не сбиваем локальный ввод - if (isUpdating) return; - - // Обновляем локальные стейты только когда меняются конкретные поля в item - setLocalQty(item.quantity); - setLocalPrice(item.price); - setLocalSum(item.sum); - - // Обновляем стек редактирования - if (item.last_edited_field_1 && item.last_edited_field_2) { - editStack.current = [ - item.last_edited_field_1 as FieldType, - item.last_edited_field_2 as FieldType, - ]; - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [ - // Зависим ТОЛЬКО от примитивов. Если объект item изменится, но цифры те же - эффект не сработает. - item.quantity, - item.price, - item.sum, - item.last_edited_field_1, - item.last_edited_field_2, - isUpdating, - ]); - - // --- Логика пересчета (Треугольник) --- - const recalculateLocally = (changedField: FieldType, newVal: number) => { - // 1. Обновляем стек истории - // Удаляем поле, если оно уже было в стеке, и добавляем в начало (LIFO для важности) - const currentStack = editStack.current.filter((f) => f !== changedField); - currentStack.unshift(changedField); - // Оставляем только 2 последних - if (currentStack.length > 2) currentStack.pop(); - editStack.current = currentStack; - - // 2. Определяем, какое поле нужно пересчитать (то, которого НЕТ в стеке) - const allFields: FieldType[] = ["quantity", "price", "sum"]; - const fieldToRecalc = allFields.find((f) => !currentStack.includes(f)); - - // 3. Выполняем расчет - let q = changedField === "quantity" ? newVal : localQty || 0; - let p = changedField === "price" ? newVal : localPrice || 0; - let s = changedField === "sum" ? newVal : localSum || 0; - - switch (fieldToRecalc) { - case "sum": - s = q * p; - setLocalSum(s); - break; - case "quantity": - if (p !== 0) { - q = s / p; - setLocalQty(q); - } else { - setLocalQty(0); - } - break; - case "price": - if (q !== 0) { - p = s / q; - setLocalPrice(p); - } else { - setLocalPrice(0); - } - break; - } - }; - - // --- Обработчики ввода --- - - const handleValueChange = (field: FieldType, val: number | null) => { - // Обновляем само поле - if (field === "quantity") setLocalQty(val); - if (field === "price") setLocalPrice(val); - if (field === "sum") setLocalSum(val); - - if (val !== null) { - recalculateLocally(field, val); - } - }; - - const handleBlur = (field: FieldType) => { - // Отправляем на сервер только измененное поле + маркер edited_field. - // Сервер сам проведет пересчет и вернет точные данные. - // Важно: отправляем текущее локальное значение. - - let val: number | null = null; - if (field === "quantity") val = localQty; - if (field === "price") val = localPrice; - if (field === "sum") val = localSum; - - if (val === null) return; - - // Сравниваем с текущим item, чтобы не спамить запросами, если число не поменялось - const serverVal = item[field]; - // Используем эпсилон для сравнения float - if (Math.abs(val - serverVal) > 0.0001) { - onUpdate(item.id, { - [field]: val, - edited_field: field, - }); - } - }; - - // --- Product & Container 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, - }); - }; - - // --- Handlers --- - const handleProductChange = ( - prodId: string, - productObj?: ProductSearchResult - ) => { - if (productObj) setSearchedProduct(productObj); - onUpdate(item.id, { - product_id: prodId, - container_id: null, // Сбрасываем фасовку - // При смене товара логично оставить Qty и Sum, пересчитав Price? - // Или оставить Qty и Price? Обычно цена меняется. - // Пока не трогаем числа, пусть остаются как были. - }); - }; - - const handleContainerChange = (val: string) => { - // "" пустая строка приходит при выборе "Базовая" (мы так настроим value) - const newVal = val === "BASE_UNIT" ? "" : val; - onUpdate(item.id, { container_id: newVal }); - }; - - const handleContainerCreated = (newContainer: ProductContainer) => { - setIsModalOpen(false); - if (activeProduct) { - setAddedContainers((prev) => ({ - ...prev, - [activeProduct.id]: [...(prev[activeProduct.id] || []), newContainer], - })); - } - onUpdate(item.id, { container_id: newContainer.id }); - }; - - const cardBorderColor = !item.product_id - ? "#ffa39e" - : item.is_matched - ? "#b7eb8f" - : "#d9d9d9"; - - return ( - <> - - {(provided, snapshot) => { - const style = { - marginBottom: "8px", - backgroundColor: snapshot.isDragging ? "#e6f7ff" : "transparent", - boxShadow: snapshot.isDragging - ? "0 4px 12px rgba(0, 0, 0, 0.15)" - : "none", - borderRadius: "4px", - transition: "background-color 0.2s ease, box-shadow 0.2s ease", - ...provided.draggableProps.style, - }; - - return ( -
- - {/* Drag handle - иконка для перетаскивания (показываем только в режиме перетаскивания) */} - {isReordering && ( -
{ - e.currentTarget.style.color = "#1890ff"; - }} - onMouseLeave={(e) => { - e.currentTarget.style.color = "#8c8c8c"; - }} - > - -
- )} - - - -
- - {item.raw_name || "Новая позиция"} - - {item.raw_amount > 0 && ( - - (чек: {item.raw_amount} x {item.raw_price}) - - )} -
-
- {isUpdating && ( - - )} - - {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} - - - - )} - /> -
- )} - - {/* Поле: Количество */} -
-
- Коэффициент (сколько товара в одной позиции чека): -
-
- setQuantity(Number(val))} - style={{ flex: 1 }} - placeholder="1" - /> - {currentUomName} -
-
- - {/* Кнопки действий */} -
- - - {initialValues && ( - - )} -
- - - {/* Модалка создания фасовки */} - {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 [notFound, setNotFound] = 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) { - setOptions([]); - setNotFound(false); - return; - } - setFetching(true); - // Не сбрасываем options сразу, чтобы не моргало - 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); - // Показываем "Не найдено" если результатов нет - setNotFound(results.length === 0); - } catch (e) { - console.error(e); - setNotFound(true); - } finally { - setFetching(false); - } - }; - - const handleSearch = (val: string) => { - if (fetchRef.current !== null) { - window.clearTimeout(fetchRef.current); - } - // Сбрасываем notFound при новом поиске - setNotFound(false); - // Запускаем поиск только если введено хотя бы 2 символа - if (val.length < 2) { - return; - } - fetchRef.current = window.setTimeout(() => { - fetchProducts(val); - }, 500); - }; - - const handleChange = ( - val: string, - option: SelectOption | SelectOption[] | undefined - ) => { - if (onChange) { - 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} - - -
-
- {(onDeleteMatch || onEditMatch) && ( -
- {onEditMatch && ( - - )} - {onDeleteMatch && ( - onDeleteMatch(rawName)} - okText="Да" - cancelText="Нет" - > - - - )} -
- )} -
- ); - }} - /> - - ); -}; - -``` - -# =================================================================== -# Файл: 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/components/settings/PhotoStorageTab.tsx -# =================================================================== - -``` -import React, { useState } from "react"; -import { - Card, - Image, - Button, - Popconfirm, - Tag, - Pagination, - Empty, - Spin, - message, - Tooltip, -} from "antd"; -import { - DeleteOutlined, - ReloadOutlined, - FileImageOutlined, - CheckCircleOutlined, - FileTextOutlined, -} from "@ant-design/icons"; -import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; -import { api, getStaticUrl } from "../../services/api"; -import { AxiosError } from "axios"; -import type { ReceiptPhoto, PhotoStatus } from "../../services/types"; - -export const PhotoStorageTab: React.FC = () => { - const [page, setPage] = useState(1); - const queryClient = useQueryClient(); - - const { data, isLoading, isError } = useQuery({ - queryKey: ["photos", page], - queryFn: () => api.getPhotos(page, 18), // 18 - удобно делится на 2, 3, 6 колонок - }); - - const deleteMutation = useMutation({ - mutationFn: ({ id, force }: { id: string; force: boolean }) => - api.deletePhoto(id, force), - onSuccess: () => { - queryClient.invalidateQueries({ queryKey: ["photos"] }); - message.success("Фото удалено"); - }, - // Исправленная типизация: - onError: (error: AxiosError<{ error: string }>) => { - if (error.response?.status === 409) { - message.warning( - "Это фото связано с черновиком. Используйте кнопку 'Удалить' с подтверждением." - ); - } else { - message.error(error.response?.data?.error || "Ошибка удаления"); - } - }, - }); - - const regenerateMutation = useMutation({ - mutationFn: (id: string) => api.regenerateDraftFromPhoto(id), - onSuccess: () => { - message.success("Черновик восстановлен"); - // Можно редиректить, но пока просто обновим список - }, - onError: () => { - message.error("Ошибка восстановления"); - }, - }); - - const getStatusTag = (status: PhotoStatus) => { - switch (status) { - case "ORPHAN": - return Без привязки; - case "HAS_DRAFT": - return ( - } color="processing"> - Черновик - - ); - case "HAS_INVOICE": - return ( - } color="success"> - В iiko - - ); - default: - return {status}; - } - }; - - if (isLoading) { - return ( -
- -
- ); - } - - if (isError) { - return ; - } - - if (!data?.photos?.length) { - return ; - } - - return ( -
-
- {data.photos.map((photo: ReceiptPhoto) => ( - - {photo.file_name} }} - /> -
- } - actions={[ - photo.can_regenerate ? ( - -
- } - description={getStatusTag(photo.status)} - /> - - ))} - - - - - ); -}; - -``` - -# =================================================================== -# Файл: src/components/settings/TeamList.tsx -# =================================================================== - -``` -import React from "react"; -import { - List, - Avatar, - Tag, - Button, - Select, - Popconfirm, - message, - Spin, - Alert, - Typography, -} from "antd"; -import { DeleteOutlined, UserOutlined } from "@ant-design/icons"; -import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; -import { api } from "../../services/api"; -import type { ServerUser, UserRole } from "../../services/types"; - -const { Text } = Typography; - -interface Props { - currentUserRole: UserRole; -} - -export const TeamList: React.FC = ({ currentUserRole }) => { - const queryClient = useQueryClient(); - - // Запрос списка пользователей - const { - data: users, - isLoading, - isError, - } = useQuery({ - queryKey: ["serverUsers"], - queryFn: api.getUsers, - }); - - // Мутация изменения роли - const updateRoleMutation = useMutation({ - mutationFn: ({ userId, newRole }: { userId: string; newRole: UserRole }) => - api.updateUserRole(userId, newRole), - onSuccess: () => { - message.success("Роль пользователя обновлена"); - queryClient.invalidateQueries({ queryKey: ["serverUsers"] }); - }, - onError: () => { - message.error("Не удалось изменить роль"); - }, - }); - - // Мутация удаления пользователя - const removeUserMutation = useMutation({ - mutationFn: (userId: string) => api.removeUser(userId), - onSuccess: () => { - message.success("Пользователь удален из команды"); - queryClient.invalidateQueries({ queryKey: ["serverUsers"] }); - }, - onError: () => { - message.error("Не удалось удалить пользователя"); - }, - }); - - // Хелперы для UI - const getRoleColor = (role: UserRole) => { - switch (role) { - case "OWNER": - return "gold"; - case "ADMIN": - return "blue"; - case "OPERATOR": - return "default"; - default: - return "default"; - } - }; - - const getRoleName = (role: UserRole) => { - switch (role) { - case "OWNER": - return "Владелец"; - case "ADMIN": - return "Админ"; - case "OPERATOR": - return "Оператор"; - default: - return role; - } - }; - - // Проверка прав на удаление - const canDelete = (targetUser: ServerUser) => { - if (targetUser.is_me) return false; // Себя удалить нельзя - if (targetUser.role === "OWNER") return false; // Владельца удалить нельзя - if (currentUserRole === "ADMIN" && targetUser.role === "ADMIN") - return false; // Админ не может удалить админа - return true; - }; - - if (isLoading) { - return ( -
- -
- ); - } - - if (isError) { - return ; - } - - return ( - <> - - - ( - - updateRoleMutation.mutate({ - userId: user.user_id, - newRole: val, - }) - } - options={[ - { value: "ADMIN", label: "Админ" }, - { value: "OPERATOR", label: "Оператор" }, - ]} - /> - ) : ( - - {getRoleName(user.role)} - - ), - - // Кнопка удаления - removeUserMutation.mutate(user.user_id)} - disabled={!canDelete(user)} - okText="Да" - cancelText="Нет" - > - - - - {/* Аватар пользователя */} - - -
- } /> - {user?.username || 'Пользователь'} -
-
-
- - ); -}; - -``` - -# =================================================================== -# Файл: src/layouts/DesktopLayout/DesktopLayout.tsx -# =================================================================== - -``` -import React from 'react'; -import { Layout } from 'antd'; -import { Outlet } from 'react-router-dom'; -import { DesktopHeader } from './DesktopHeader.tsx'; - -const { Content } = Layout; - -/** - * Основной layout для десктопной версии - * Использует Ant Design Layout с фиксированным Header - */ -export const DesktopLayout: React.FC = () => { - return ( - - - -
- -
-
-
- ); -}; - -``` - -# =================================================================== -# Файл: 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 -# =================================================================== - -``` -// src/pages/DraftsList.tsx - -import React, { useState, useMemo } from "react"; -import { useQuery } from "@tanstack/react-query"; -import { - List, - Typography, - Tag, - Spin, - Empty, - Flex, - Button, - Select, - DatePicker, -} from "antd"; -import { useNavigate } from "react-router-dom"; -import { - CheckCircleOutlined, - DeleteOutlined, - PlusOutlined, - ExclamationCircleOutlined, - LoadingOutlined, - CloseCircleOutlined, - StopOutlined, - SyncOutlined, - CloudServerOutlined, -} from "@ant-design/icons"; -import dayjs from "dayjs"; -import "dayjs/locale/ru"; -import { api } from "../services/api"; -import type { UnifiedInvoice } from "../services/types"; - -const { Title, Text } = Typography; - -type FilterType = "ALL" | "DRAFT" | "SYNCED"; - -dayjs.locale("ru"); - -const DayDivider: React.FC<{ date: string }> = ({ date }) => { - const d = dayjs(date); - const dayOfWeek = d.format("dddd"); - const formattedDate = d.format("D MMMM YYYY"); - - return ( -
- - {formattedDate} - - - {dayOfWeek} - -
- ); -}; - -export const DraftsList: React.FC = () => { - const navigate = useNavigate(); - - const [syncLoading, setSyncLoading] = useState(false); - const [filterType, setFilterType] = useState("ALL"); - const [currentPage, setCurrentPage] = useState(1); - const [pageSize, setPageSize] = useState(20); - const [startDate, setStartDate] = useState( - dayjs().subtract(30, "day") - ); - const [endDate, setEndDate] = useState(dayjs()); - - const { - data: invoices, - isLoading, - isError, - refetch, - } = useQuery({ - queryKey: [ - "drafts", - startDate?.format("YYYY-MM-DD"), - endDate?.format("YYYY-MM-DD"), - ], - queryFn: () => - api.getDrafts( - startDate?.format("YYYY-MM-DD"), - endDate?.format("YYYY-MM-DD") - ), - staleTime: 0, - refetchOnMount: true, - refetchOnWindowFocus: true, - }); - - const handleSync = async () => { - setSyncLoading(true); - try { - await api.syncInvoices(); - refetch(); - } finally { - setSyncLoading(false); - } - }; - - const getStatusTag = (item: UnifiedInvoice) => { - switch (item.status) { - case "PROCESSING": - return ( - } color="blue"> - Обработка - - ); - case "READY_TO_VERIFY": - return ( - } color="orange"> - Проверка - - ); - case "COMPLETED": - return ( - } color="green"> - Готово - - ); - case "ERROR": - return ( - } color="red"> - Ошибка - - ); - case "CANCELED": - return ( - } color="default"> - Отменен - - ); - case "NEW": - return ( - } color="blue"> - Новая - - ); - case "PROCESSED": - return ( - } color="green"> - Проведена - - ); - case "DELETED": - return ( - } color="red"> - Удалена - - ); - default: - return {item.status}; - } - }; - - const handleInvoiceClick = (item: UnifiedInvoice) => { - if (item.type === "DRAFT") { - navigate("/invoice/draft/" + item.id); - } else if (item.type === "SYNCED") { - navigate("/invoice/view/" + item.id); - } - }; - - const handleFilterChange = (value: FilterType) => { - setFilterType(value); - setCurrentPage(1); - }; - - const handlePageSizeChange = (value: number) => { - setPageSize(value); - setCurrentPage(1); - }; - - const getItemDate = (item: UnifiedInvoice) => - item.type === "DRAFT" ? item.created_at : item.date_incoming; - - const filteredAndSortedInvoices = useMemo(() => { - if (!invoices || invoices.length === 0) return []; - - let result = [...invoices]; - - if (filterType !== "ALL") { - result = result.filter((item) => item.type === filterType); - } - - result.sort((a, b) => { - const dateA = dayjs(getItemDate(a)).startOf("day"); - const dateB = dayjs(getItemDate(b)).startOf("day"); - - // Сначала по дате DESC - if (!dateA.isSame(dateB)) { - return dateB.valueOf() - dateA.valueOf(); - } - - // Внутри дня: DRAFT < SYNCED - if (a.type !== b.type) { - return a.type === "DRAFT" ? -1 : 1; - } - - // Внутри типа: по номеру DESC - return (b.document_number || "").localeCompare( - a.document_number || "", - "ru", - { numeric: true } - ); - }); - - return result; - }, [invoices, filterType]); - - const paginatedInvoices = useMemo(() => { - const startIndex = (currentPage - 1) * pageSize; - return filteredAndSortedInvoices.slice(startIndex, startIndex + pageSize); - }, [filteredAndSortedInvoices, currentPage, pageSize]); - - const groupedInvoices = useMemo(() => { - const groups: { [key: string]: UnifiedInvoice[] } = {}; - paginatedInvoices.forEach((item) => { - const dateKey = dayjs(getItemDate(item)).format("YYYY-MM-DD"); - if (!groups[dateKey]) { - groups[dateKey] = []; - } - groups[dateKey].push(item); - }); - return groups; - }, [paginatedInvoices]); - - const filterCounts = useMemo(() => { - if (!invoices) return { all: 0, draft: 0, synced: 0 }; - return { - all: invoices.length, - draft: invoices.filter((item) => item.type === "DRAFT").length, - synced: invoices.filter((item) => item.type === "SYNCED").length, - }; - }, [invoices]); - - const totalPages = Math.ceil( - (filteredAndSortedInvoices.length || 0) / pageSize - ); - - if (isError) { - return ( -
- Ошибка загрузки списка накладных -
- ); - } - - return ( -
- - - - Накладные - - - - - - )} - - )} -
- ); -}; - -``` - -# =================================================================== -# Файл: 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, - Image, -} from "antd"; -import { - ArrowLeftOutlined, - CheckOutlined, - DeleteOutlined, - ExclamationCircleFilled, - RestOutlined, - PlusOutlined, - FileImageOutlined, - SwapOutlined, -} from "@ant-design/icons"; -import dayjs from "dayjs"; -import { api, getStaticUrl } from "../services/api"; -import { DraftItemRow } from "../components/invoices/DraftItemRow"; -import type { - UpdateDraftItemRequest, - CommitDraftRequest, - ReorderDraftItemsRequest, -} from "../services/types"; -import { DragDropContext, Droppable, type DropResult } from "@hello-pangea/dnd"; - -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 [itemsOrder, setItemsOrder] = useState>({}); - const [isDragging, setIsDragging] = useState(false); - const [isReordering, setIsReordering] = useState(false); - - // Состояние для просмотра фото чека - const [previewVisible, setPreviewVisible] = useState(false); - - // --- ЗАПРОСЫ --- - - 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) => { - if (isDragging) return false; - 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"); - queryClient.invalidateQueries({ queryKey: ["drafts"] }); - }, - 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("Ошибка при удалении"); - }, - }); - - const reorderItemsMutation = useMutation({ - mutationFn: ({ - draftId, - payload, - }: { - draftId: string; - payload: ReorderDraftItemsRequest; - }) => api.reorderDraftItems(draftId, payload), - onError: (error) => { - message.error("Не удалось изменить порядок элементов"); - console.error("Reorder error:", 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.incoming_document_number && - draft.incoming_document_number - ) - form.setFieldValue( - "incoming_document_number", - draft.incoming_document_number - ); - - 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 || "", - incoming_document_number: values.incoming_document_number || "", - }); - } catch { - message.error("Заполните обязательные поля (Склад, Поставщик)"); - } - }; - - const isCanceled = draft?.status === "CANCELED"; - - const handleDelete = () => { - confirm({ - title: isCanceled ? "Удалить окончательно?" : "Отменить черновик?", - icon: , - content: isCanceled - ? "Черновик пропадет из списка навсегда." - : 'Черновик получит статус "Отменен", но останется в списке.', - okText: isCanceled ? "Удалить навсегда" : "Отменить", - okType: "danger", - cancelText: "Назад", - onOk() { - deleteDraftMutation.mutate(); - }, - }); - }; - - const handleDragStart = () => { - setIsDragging(true); - }; - - const handleDragEnd = async (result: DropResult) => { - setIsDragging(false); - const { source, destination } = result; - - // Если нет назначения или позиция не изменилась - if ( - !destination || - (source.droppableId === destination.droppableId && - source.index === destination.index) - ) { - return; - } - - if (!draft) return; - - // Сохраняем предыдущее состояние для отката - const previousItems = [...draft.items]; - const previousOrder = { ...itemsOrder }; - - // Создаём новый массив с изменённым порядком - const newItems = [...draft.items]; - const [removed] = newItems.splice(source.index, 1); - newItems.splice(destination.index, 0, removed); - - // Обновляем локальное состояние немедленно для быстрого UI - queryClient.setQueryData(["draft", id], { - ...draft, - items: newItems, - }); - - // Подготавливаем payload для API - const reorderPayload: ReorderDraftItemsRequest = { - items: newItems.map((item, index) => ({ - id: item.id, - order: index, - })), - }; - - // Отправляем запрос на сервер - try { - await reorderItemsMutation.mutateAsync({ - draftId: draft.id, - payload: reorderPayload, - }); - } catch { - // При ошибке откатываем локальное состояние - queryClient.setQueryData(["draft", id], { - ...draft, - items: previousItems, - }); - setItemsOrder(previousOrder); - } - }; - - // --- RENDER --- - const showSpinner = - draftQuery.isLoading || - (draft?.status === "PROCESSING" && - (!draft?.items || draft.items.length === 0)); - - if (showSpinner) { - return ( -
- -
- ); - } - - if (draftQuery.isError || !draft) { - return ; - } - - return ( -
- {/* Header */} -
-
-
- - {/* Правая часть хедера: Кнопка чека, Кнопка перетаскивания и Кнопка удаления */} -
- {/* Кнопка просмотра чека (только если есть URL) */} - {draft.photo_url && ( - - )} - - {/* Кнопка переключения режима перетаскивания */} - - - -
-
- - {/* Form: Склады и Поставщики */} -
-
- - - - - - - - {/* Входящий номер */} - - - - - - - - - ({ label: s.name, value: s.id }))} - size="middle" - showSearch - filterOption={(input, option) => - (option?.label ?? "") - .toLowerCase() - .includes(input.toLowerCase()) - } - /> - - -