From de94ad707b4a51b7f3f9cd642ec703579c712728 Mon Sep 17 00:00:00 2001 From: Arsen Date: Wed, 4 Feb 2026 00:17:04 +0500 Subject: [PATCH] Initial commit MKD fixes --- .env.example | 27 + .env.production | 13 + .gitignore | 24 + App.tsx | 1218 + README.md | 170 + backend/.env | 4 + backend/1c-integration-options.md | 173 + backend/HOW_REPORTS_WORK.md | 135 + backend/INTEGRATION_SALARY_1C.md | 239 + backend/README_MIGRATION.md | 26 + backend/aiChatService.js | 189 + backend/aiToolsRegistry.js | 312 + backend/balanceSheet76Processor.js | 157 + backend/balanceSheetProcessor.js | 487 + backend/constants/roleAccess.js | 54 + backend/create_vacancies_table.sql | 33 + backend/dbInit.js | 3607 +++ backend/debtorReportProcessor.js | 115 + backend/fileProcessor.js | 969 + backend/migrate_add_deferred_status.sql | 19 + backend/migrate_add_performance_tracking.sql | 93 + backend/migrate_candidate_events.sql | 51 + backend/migrate_candidate_stage.sql | 17 + backend/migrate_company_settings.sql | 22 + backend/migrate_counterparty_checks.sql | 39 + backend/migrate_development_module.sql | 149 + backend/migrate_doma_mappings.sql | 40 + backend/migrate_legal_case_documents.sql | 18 + backend/migrate_legal_fssp_enforcement.sql | 12 + backend/migrate_legal_module.sql | 140 + backend/migrate_legal_penalties.sql | 9 + backend/migrate_nps_building_stats.sql | 25 + backend/migrate_nps_surveys.sql | 40 + backend/migrate_pipeline_automation.sql | 131 + backend/migrate_pr_nps.sql | 107 + backend/migrate_pre_trial_work.sql | 90 + backend/migrate_resident_report_data.sql | 60 + backend/migrate_salary_history.sql | 42 + backend/migrate_training_module.sql | 124 + .../add_applications_manual_fields.sql | 66 + ...closing_docs_files_to_payment_invoices.sql | 17 + .../add_event_to_payment_invoices.sql | 11 + ...d_expected_return_date_repair_requests.sql | 3 + .../add_image_to_scheduled_posts.sql | 4 + .../migrations/add_inventory_to_districts.sql | 8 + .../add_item_type_to_payment_invoices.sql | 62 + backend/migrations/add_outage_fields.sql | 7 + ...nt_ref_and_is_cash_to_payment_invoices.sql | 9 + .../add_plan_item_to_payment_invoices.sql | 6 + .../add_pr_events_location_place.sql | 8 + .../add_repair_request_statuses.sql | 5 + .../migrations/add_repair_status_fields.sql | 9 + backend/migrations/add_report_type.sql | 12 + .../add_report_type_balance_sheet_76.sql | 7 + .../add_scheduled_date_to_post_topics.sql | 10 + .../migrations/add_task_id_to_work_photos.sql | 5 + .../migrations/add_user_profile_fields.sql | 30 + .../create_application_comments_history.sql | 25 + .../create_building_personal_accounts.sql | 21 + backend/migrations/create_company_news.sql | 19 + .../migrations/create_debtor_report_data.sql | 18 + .../migrations/create_employee_districts.sql | 18 + .../create_employee_responsibility.sql | 14 + .../migrations/create_expense_directory.sql | 42 + .../migrations/create_finance_accounts.sql | 16 + .../create_office_equipment_history.sql | 21 + backend/migrations/create_outages.sql | 16 + .../create_payment_calendar_entries.sql | 28 + .../migrations/create_payment_categories.sql | 18 + .../migrations/create_payment_invoices.sql | 82 + backend/migrations/create_positions.sql | 12 + .../create_pr_attraction_actions.sql | 19 + backend/migrations/create_pr_events.sql | 52 + backend/migrations/create_pr_post_topics.sql | 21 + .../migrations/create_pr_scheduled_posts.sql | 23 + backend/migrations/create_pr_smm_channels.sql | 28 + .../migrations/create_report_76_tables.sql | 33 + backend/migrations/create_user_roles.sql | 24 + ...velopment_audits_status_and_inspection.sql | 27 + .../development_oss_agenda_and_votes.sql | 7 + .../development_pipeline_add_analysis.sql | 17 + .../development_pipeline_new_stages.sql | 31 + .../update_building_financial_data_unique.sql | 15 + backend/notificationService.js | 201 + backend/package-lock.json | 3494 +++ backend/package.json | 25 + backend/paymentInvoiceWorkflow.js | 153 + backend/pipelineAutomation.js | 561 + backend/read_act_xlsx.js | 30 + backend/reviewApiService.js | 139 + backend/reviewParser.js | 1088 + backend/routes/buildings.js | 450 + backend/salaryProcessor.js | 410 + backend/schema.sql | 919 + backend/server.js | 24107 ++++++++++++++++ backend/templates/accounts.csv | 4 + backend/templates/buildings.csv | 3 + backend/templates/districts.csv | 3 + backend/templates/employees.csv | 3 + ...довая_ведомость_по_счету_20_за_2025_г_ООО_Дружба.csv | 4094 +++ ...довая_ведомость_по_счету_20_за_2025_г_ООО_Дружба.csv | 4094 +++ ...довая_ведомость_по_счету_20_за_2025_г_ООО_Дружба.csv | 4094 +++ ...Новый текстовый документ.txt | 33 + ...овый текстовый документ (2).txt | 12 + ...Новый текстовый документ.txt | 33 + ...овый текстовый документ (2).txt | 12 + ...овый текстовый документ (2).txt | 12 + ...-3e58c14ac6cc-________________________.txt | 33 + ...Новый текстовый документ.txt | 33 + .../507a34a2-6179-4dea-b090-19313535ed93.png | Bin 0 -> 9845 bytes ...довая_ведомость_по_счету_20_за_2025_г_ООО_Дружба.csv | 4094 +++ .../b9769c82-0763-4dd6-bcf4-a1bfa274fcd0.xlsx | Bin 0 -> 145283 bytes ...cf2a6-4562-4135-a460-3c8587e575d5-logo.png | Bin 0 -> 3677 bytes ...emini_Generated_Image_ajhe46ajhe46ajhe.png | Bin 0 -> 8570025 bytes ...6-f956-45f0-af68-ce3861d1939d-Jnxtn_v.xlsx | Bin 0 -> 33501 bytes .../2281dbd5-90fd-4f19-a48e-10b23a37f0bb.jpg | Bin 0 -> 24421 bytes .../675b8e76-af85-41e3-aa57-86e0ddf19014.jpg | Bin 0 -> 12002 bytes .../7566561b-b493-4c9b-b8c0-badecc0939a4.jpg | Bin 0 -> 75360 bytes .../d3492424-b7ae-41e7-8fd1-798dc616155f.jpg | Bin 0 -> 296313 bytes .../186ccc5a-f276-47a9-8caf-eb1676f35712.png | Bin 0 -> 19031 bytes .../4143c13a-d3e1-4337-a38a-0c86e136eb03.png | Bin 0 -> 451 bytes .../6e5c40fc-65ae-42a8-abc9-c3eb23105adc.png | Bin 0 -> 19031 bytes .../b0944c18-d19e-43bb-9640-8aecdfd356ba.png | Bin 0 -> 1604232 bytes components/AdminModule.tsx | 113 + components/ApplicationsModule.tsx | 192 + components/BuildingCharacteristics.tsx | 298 + components/ConnectionIndicator.tsx | 94 + components/DashboardNavigation.tsx | 587 + components/DashboardTaskModal.tsx | 338 + components/DevelopmentModule.tsx | 103 + components/FinanceModule.tsx | 520 + components/HRModule.tsx | 245 + components/LegalModule.tsx | 102 + components/LoginPage.tsx | 173 + components/Navigation.tsx | 347 + components/NotificationPanel.tsx | 144 + components/OfficeModule.tsx | 138 + components/PRModule.tsx | 117 + components/ProfileSettingsModal.tsx | 533 + components/Sidebar.tsx | 92 + components/SummaryDashboard.tsx | 1827 ++ components/admin/AISection.tsx | 120 + components/admin/BackupsSection.tsx | 127 + components/admin/CompanySection.tsx | 240 + components/admin/DataCleanupSection.tsx | 674 + components/admin/DataImportSection.tsx | 144 + components/admin/IntegrationsSection.tsx | 434 + components/admin/PermissionsSection.tsx | 473 + components/admin/PositionsSection.tsx | 248 + .../admin/ResponsibilityZonesSection.tsx | 203 + components/admin/SecuritySection.tsx | 462 + components/admin/UsersSection.tsx | 746 + components/applications/AppSummary.tsx | 196 + .../applications/ApplicationCardDetail.tsx | 451 + .../applications/ApplicationsRegistry.tsx | 147 + .../applications/CreateApplicationCard.tsx | 490 + components/applications/CreateOutageCard.tsx | 365 + components/applications/DispatcherControl.tsx | 76 + .../applications/DomaPendingMappings.tsx | 260 + .../applications/EditApplicationModal.tsx | 332 + components/applications/OutageCardDetail.tsx | 173 + components/applications/OutagesJournal.tsx | 203 + components/applications/QualityControl.tsx | 343 + components/applications/StatusChangeModal.tsx | 198 + components/building/Accounts.tsx | 3 + components/building/AccountsView.tsx | 2652 ++ components/building/AddCustomFieldModal.tsx | 161 + components/building/BudgetPlanView.tsx | 1183 + .../building/DynamicInspectionSections.tsx | 795 + components/building/EditableField.tsx | 78 + components/building/Finance.tsx | 3 + components/building/FinanceView.tsx | 296 + components/building/Inspections.tsx | 3 + components/building/InspectionsView.tsx | 894 + components/building/InventoryView.tsx | 941 + components/building/MeterCheck.tsx | 3 + components/building/MeterCheckView.tsx | 1171 + components/building/Overview.tsx | 293 + components/building/OverviewView.tsx | 147 + components/building/Passport.tsx | 84 + components/building/PassportView.tsx | 1871 ++ components/building/ResidentsView.tsx | 630 + components/building/Supply.tsx | 3 + components/building/TaskModal.tsx | 716 + components/building/WorkPlan.tsx | 3 + components/development/AddBallotModal.tsx | 212 + .../development/AddPipelineObjectModal.tsx | 307 + components/development/AuditCardModal.tsx | 293 + components/development/BulkBallotModal.tsx | 289 + components/development/CreateOSSModal.tsx | 442 + components/development/DevSummary.tsx | 232 + components/development/EditOSSModal.tsx | 227 + .../development/EditPipelineObjectModal.tsx | 288 + .../development/MarketingActivityModal.tsx | 376 + components/development/MarketingCampaigns.tsx | 225 + components/development/OSSMaster.tsx | 289 + components/development/OSSRegistryModal.tsx | 339 + components/development/PipelineRegistry.tsx | 275 + components/development/TechnicalAudit.tsx | 231 + .../development/auditInspectionSchema.ts | 148 + components/finance/AggregatedReportView.tsx | 233 + .../finance/BuildingFinancialSummary.tsx | 245 + components/finance/DebtorReportDetailView.tsx | 232 + components/finance/DebtorsList.tsx | 42 + components/finance/ExpenseDirectory.tsx | 369 + components/finance/FinanceReports.tsx | 32 + components/finance/FinanceSummary.tsx | 61 + components/finance/InvoiceDistribution.tsx | 244 + components/finance/InvoiceRegistry.tsx | 101 + components/finance/PaymentCalendar.tsx | 515 + .../finance/PaymentCalendarEntryForm.tsx | 317 + components/finance/PaymentInvoiceDetail.tsx | 729 + components/finance/PaymentInvoiceForm.tsx | 965 + components/finance/PaymentInvoiceList.tsx | 293 + components/finance/PaymentSchedule.tsx | 101 + components/finance/PaymentStatusModal.tsx | 169 + components/finance/ReportDetailView.tsx | 859 + components/finance/ReportProcessing.tsx | 282 + components/finance/ReportTypesGrid.tsx | 141 + components/finance/ReportUploader.tsx | 343 + components/finance/ReportsGrid.tsx | 292 + components/hr/AssignTrainingModal.tsx | 133 + components/hr/CandidateEventsTimeline.tsx | 620 + components/hr/CandidateFormModal.tsx | 466 + components/hr/CandidatesRegistry.tsx | 499 + components/hr/EmployeeCardModal.tsx | 1251 + components/hr/EmployeeFormModal.tsx | 1188 + components/hr/EmployeeRegistry.tsx | 413 + components/hr/HRSummary.tsx | 445 + components/hr/HiringPipeline.tsx | 65 + components/hr/OrganizationalStructure.tsx | 357 + components/hr/PayrollModule.tsx | 69 + components/hr/ProgramModal.tsx | 400 + components/hr/SafetyBriefing.tsx | 80 + components/hr/TrainingManagementModal.tsx | 264 + components/hr/TrainingModule.tsx | 778 + components/hr/VacanciesRegistry.tsx | 432 + components/hr/VacancyFormModal.tsx | 291 + components/hr/WorkCalendarModal.tsx | 564 + components/hr/WorkCalendarView.tsx | 1074 + components/legal/CaseDetailsModal.tsx | 630 + components/legal/ComplianceCheck.tsx | 852 + components/legal/ContractsRegistry.tsx | 584 + components/legal/CourtCases.tsx | 595 + components/legal/DebtRecoveryPipeline.tsx | 915 + components/legal/LegalSummary.tsx | 392 + components/legal/PreTrialWork.tsx | 1326 + components/objects/BuildingsRegistry.tsx | 55 + components/objects/DeleteConfirmModal.tsx | 108 + components/objects/DistrictStaffModal.tsx | 117 + components/objects/DistrictsSummary.tsx | 202 + components/objects/EditDistrictModal.tsx | 101 + components/objects/MoveBuildingsModal.tsx | 214 + components/objects/PerformanceCard.tsx | 107 + components/objects/StaffRegistry.tsx | 39 + components/office/BookMeetingRoomModal.tsx | 197 + components/office/CompanyNewsRegistry.tsx | 408 + components/office/DocumentFlow.tsx | 1022 + components/office/FacilityManagement.tsx | 1068 + components/office/KnowledgeBase.css | 109 + components/office/KnowledgeBase.tsx | 1097 + components/office/MeetingsAndRooms.tsx | 1423 + components/office/OfficeSummary.tsx | 831 + components/office/RepairRequests.tsx | 1156 + components/office/SupplyRegistry.tsx | 3097 ++ components/pr/BuildingReportPage.tsx | 1452 + components/pr/EventsRegistry.tsx | 918 + components/pr/NPSSurveyPage.tsx | 367 + components/pr/NPSSurveyStatsPage.tsx | 403 + components/pr/NPSSurveysManager.tsx | 457 + components/pr/NegativeResolution.tsx | 497 + components/pr/PRFeedbackFeed.tsx | 812 + components/pr/PRSummary.tsx | 200 + components/pr/PostTopicsManager.tsx | 393 + components/pr/PublicationSchedule.tsx | 456 + components/pr/ResidentReportPage.tsx | 122 + components/pr/ResidentReportView.tsx | 410 + components/pr/ResidentReports.tsx | 699 + components/pr/SMMManager.tsx | 1015 + components/pr/WorkPhotosDirectory.tsx | 316 + components/pr/WorkPhotosManager.tsx | 358 + components/settings/CompanySettingsModal.tsx | 261 + constants.tsx | 526 + constants/permissions.ts | 230 + constants/refreshEvents.ts | 25 + constants/roleAccess.ts | 60 + contexts/PermissionsContext.tsx | 34 + docs/DEVELOPMENT_PIPELINE_AUTOMATION.md | 224 + docs/DOMA_AI_INTEGRATION.md | 209 + docs/PIPELINE_AUTOMATION_FLOW.md | 244 + docs/REPORT_01_ROLES_AND_RESTRICTIONS.md | 104 + ...REPORT_02_ACCESS_TABLE_AND_REWRITE_PLAN.md | 204 + hooks/useCachedFetch.ts | 17 + index.html | 113 + index.tsx | 15 + inspectionElementTemplates.ts | 89 + js/tailwind.js | 64 + metadata.json | 5 + package-lock.json | 2121 ++ package.json | 26 + services/apiClient.ts | 1455 + services/connectionService.ts | 206 + services/domaGraphQLClient.ts | 626 + services/domaService.ts | 190 + services/geminiService.ts | 55 + services/offlineCacheService.ts | 141 + services/settingsService.ts | 77 + services/storageService.ts | 272 + tsconfig.json | 29 + types.ts | 2049 ++ vite.config.ts | 51 + план.md | 147 + 312 files changed, 138754 insertions(+) create mode 100755 .env.example create mode 100755 .env.production create mode 100755 .gitignore create mode 100755 App.tsx create mode 100755 README.md create mode 100755 backend/.env create mode 100755 backend/1c-integration-options.md create mode 100755 backend/HOW_REPORTS_WORK.md create mode 100755 backend/INTEGRATION_SALARY_1C.md create mode 100755 backend/README_MIGRATION.md create mode 100755 backend/aiChatService.js create mode 100755 backend/aiToolsRegistry.js create mode 100755 backend/balanceSheet76Processor.js create mode 100755 backend/balanceSheetProcessor.js create mode 100755 backend/constants/roleAccess.js create mode 100755 backend/create_vacancies_table.sql create mode 100755 backend/dbInit.js create mode 100755 backend/debtorReportProcessor.js create mode 100755 backend/fileProcessor.js create mode 100755 backend/migrate_add_deferred_status.sql create mode 100755 backend/migrate_add_performance_tracking.sql create mode 100755 backend/migrate_candidate_events.sql create mode 100755 backend/migrate_candidate_stage.sql create mode 100755 backend/migrate_company_settings.sql create mode 100755 backend/migrate_counterparty_checks.sql create mode 100755 backend/migrate_development_module.sql create mode 100755 backend/migrate_doma_mappings.sql create mode 100755 backend/migrate_legal_case_documents.sql create mode 100755 backend/migrate_legal_fssp_enforcement.sql create mode 100755 backend/migrate_legal_module.sql create mode 100755 backend/migrate_legal_penalties.sql create mode 100755 backend/migrate_nps_building_stats.sql create mode 100755 backend/migrate_nps_surveys.sql create mode 100755 backend/migrate_pipeline_automation.sql create mode 100755 backend/migrate_pr_nps.sql create mode 100755 backend/migrate_pre_trial_work.sql create mode 100755 backend/migrate_resident_report_data.sql create mode 100755 backend/migrate_salary_history.sql create mode 100755 backend/migrate_training_module.sql create mode 100755 backend/migrations/add_applications_manual_fields.sql create mode 100755 backend/migrations/add_closing_docs_files_to_payment_invoices.sql create mode 100755 backend/migrations/add_event_to_payment_invoices.sql create mode 100755 backend/migrations/add_expected_return_date_repair_requests.sql create mode 100755 backend/migrations/add_image_to_scheduled_posts.sql create mode 100755 backend/migrations/add_inventory_to_districts.sql create mode 100755 backend/migrations/add_item_type_to_payment_invoices.sql create mode 100755 backend/migrations/add_outage_fields.sql create mode 100755 backend/migrations/add_payment_ref_and_is_cash_to_payment_invoices.sql create mode 100755 backend/migrations/add_plan_item_to_payment_invoices.sql create mode 100755 backend/migrations/add_pr_events_location_place.sql create mode 100755 backend/migrations/add_repair_request_statuses.sql create mode 100755 backend/migrations/add_repair_status_fields.sql create mode 100755 backend/migrations/add_report_type.sql create mode 100755 backend/migrations/add_report_type_balance_sheet_76.sql create mode 100755 backend/migrations/add_scheduled_date_to_post_topics.sql create mode 100755 backend/migrations/add_task_id_to_work_photos.sql create mode 100755 backend/migrations/add_user_profile_fields.sql create mode 100755 backend/migrations/create_application_comments_history.sql create mode 100755 backend/migrations/create_building_personal_accounts.sql create mode 100755 backend/migrations/create_company_news.sql create mode 100755 backend/migrations/create_debtor_report_data.sql create mode 100755 backend/migrations/create_employee_districts.sql create mode 100755 backend/migrations/create_employee_responsibility.sql create mode 100755 backend/migrations/create_expense_directory.sql create mode 100755 backend/migrations/create_finance_accounts.sql create mode 100755 backend/migrations/create_office_equipment_history.sql create mode 100755 backend/migrations/create_outages.sql create mode 100755 backend/migrations/create_payment_calendar_entries.sql create mode 100755 backend/migrations/create_payment_categories.sql create mode 100755 backend/migrations/create_payment_invoices.sql create mode 100755 backend/migrations/create_positions.sql create mode 100755 backend/migrations/create_pr_attraction_actions.sql create mode 100755 backend/migrations/create_pr_events.sql create mode 100755 backend/migrations/create_pr_post_topics.sql create mode 100755 backend/migrations/create_pr_scheduled_posts.sql create mode 100755 backend/migrations/create_pr_smm_channels.sql create mode 100755 backend/migrations/create_report_76_tables.sql create mode 100755 backend/migrations/create_user_roles.sql create mode 100755 backend/migrations/development_audits_status_and_inspection.sql create mode 100755 backend/migrations/development_oss_agenda_and_votes.sql create mode 100755 backend/migrations/development_pipeline_add_analysis.sql create mode 100755 backend/migrations/development_pipeline_new_stages.sql create mode 100755 backend/migrations/update_building_financial_data_unique.sql create mode 100755 backend/notificationService.js create mode 100755 backend/package-lock.json create mode 100755 backend/package.json create mode 100755 backend/paymentInvoiceWorkflow.js create mode 100755 backend/pipelineAutomation.js create mode 100755 backend/read_act_xlsx.js create mode 100755 backend/reviewApiService.js create mode 100755 backend/reviewParser.js create mode 100755 backend/routes/buildings.js create mode 100755 backend/salaryProcessor.js create mode 100755 backend/schema.sql create mode 100755 backend/server.js create mode 100755 backend/templates/accounts.csv create mode 100755 backend/templates/buildings.csv create mode 100755 backend/templates/districts.csv create mode 100755 backend/templates/employees.csv create mode 100755 backend/uploads/5badb14a-9095-4446-81c4-b359eb3d0222-Оборотно_сальдовая_ведомость_по_счету_20_за_2025_г_ООО_Дружба.csv create mode 100755 backend/uploads/b2b80283-bb8e-44a6-b53e-1b19cfd87902-Оборотно_сальдовая_ведомость_по_счету_20_за_2025_г_ООО_Дружба.csv create mode 100755 backend/uploads/b95cb956-55df-4f2f-ac72-97f7e0189c4f-Оборотно_сальдовая_ведомость_по_счету_20_за_2025_г_ООО_Дружба.csv create mode 100755 backend/uploads/documents/2149a352-4d58-40d0-b97a-7d3459ead696-Новый текстовый документ.txt create mode 100755 backend/uploads/documents/2877dd10-e5b2-48b5-bac0-a70e4a7afe67-Новый текстовый документ (2).txt create mode 100755 backend/uploads/documents/3de4efd1-0646-408d-98b3-4229d5206711-Новый текстовый документ.txt create mode 100755 backend/uploads/documents/78d4d492-5470-4486-ae4c-078a5a71e13b-Новый текстовый документ (2).txt create mode 100755 backend/uploads/documents/8d86223e-433e-44da-be29-ce88d846dbe2-Новый текстовый документ (2).txt create mode 100755 backend/uploads/documents/a4a62964-4d55-4624-907c-3e58c14ac6cc-________________________.txt create mode 100755 backend/uploads/documents/f9b389e0-3332-467d-8126-e7d461b130e3-Новый текстовый документ.txt create mode 100755 backend/uploads/event-photos/507a34a2-6179-4dea-b090-19313535ed93.png create mode 100755 backend/uploads/fe91d28f-4f94-4ccc-9a9e-a4ee4f57bcc3-Оборотно_сальдовая_ведомость_по_счету_20_за_2025_г_ООО_Дружба.csv create mode 100755 backend/uploads/hr-templates/b9769c82-0763-4dd6-bcf4-a1bfa274fcd0.xlsx create mode 100755 backend/uploads/knowledge-base/288cf2a6-4562-4135-a460-3c8587e575d5-logo.png create mode 100755 backend/uploads/knowledge-base/8b5b607c-3116-4632-8303-44d5e98a0583-Gemini_Generated_Image_ajhe46ajhe46ajhe.png create mode 100755 backend/uploads/payment-invoices/ea8de0b6-f956-45f0-af68-ce3861d1939d-Jnxtn_v.xlsx create mode 100755 backend/uploads/photos/2281dbd5-90fd-4f19-a48e-10b23a37f0bb.jpg create mode 100755 backend/uploads/photos/675b8e76-af85-41e3-aa57-86e0ddf19014.jpg create mode 100755 backend/uploads/photos/7566561b-b493-4c9b-b8c0-badecc0939a4.jpg create mode 100755 backend/uploads/photos/d3492424-b7ae-41e7-8fd1-798dc616155f.jpg create mode 100755 backend/uploads/work-photos/186ccc5a-f276-47a9-8caf-eb1676f35712.png create mode 100755 backend/uploads/work-photos/4143c13a-d3e1-4337-a38a-0c86e136eb03.png create mode 100755 backend/uploads/work-photos/6e5c40fc-65ae-42a8-abc9-c3eb23105adc.png create mode 100755 backend/uploads/work-photos/b0944c18-d19e-43bb-9640-8aecdfd356ba.png create mode 100755 components/AdminModule.tsx create mode 100755 components/ApplicationsModule.tsx create mode 100755 components/BuildingCharacteristics.tsx create mode 100755 components/ConnectionIndicator.tsx create mode 100755 components/DashboardNavigation.tsx create mode 100755 components/DashboardTaskModal.tsx create mode 100755 components/DevelopmentModule.tsx create mode 100755 components/FinanceModule.tsx create mode 100755 components/HRModule.tsx create mode 100755 components/LegalModule.tsx create mode 100755 components/LoginPage.tsx create mode 100755 components/Navigation.tsx create mode 100755 components/NotificationPanel.tsx create mode 100755 components/OfficeModule.tsx create mode 100755 components/PRModule.tsx create mode 100755 components/ProfileSettingsModal.tsx create mode 100755 components/Sidebar.tsx create mode 100755 components/SummaryDashboard.tsx create mode 100755 components/admin/AISection.tsx create mode 100755 components/admin/BackupsSection.tsx create mode 100755 components/admin/CompanySection.tsx create mode 100755 components/admin/DataCleanupSection.tsx create mode 100755 components/admin/DataImportSection.tsx create mode 100755 components/admin/IntegrationsSection.tsx create mode 100755 components/admin/PermissionsSection.tsx create mode 100755 components/admin/PositionsSection.tsx create mode 100755 components/admin/ResponsibilityZonesSection.tsx create mode 100755 components/admin/SecuritySection.tsx create mode 100755 components/admin/UsersSection.tsx create mode 100755 components/applications/AppSummary.tsx create mode 100755 components/applications/ApplicationCardDetail.tsx create mode 100755 components/applications/ApplicationsRegistry.tsx create mode 100755 components/applications/CreateApplicationCard.tsx create mode 100755 components/applications/CreateOutageCard.tsx create mode 100755 components/applications/DispatcherControl.tsx create mode 100755 components/applications/DomaPendingMappings.tsx create mode 100755 components/applications/EditApplicationModal.tsx create mode 100755 components/applications/OutageCardDetail.tsx create mode 100755 components/applications/OutagesJournal.tsx create mode 100755 components/applications/QualityControl.tsx create mode 100755 components/applications/StatusChangeModal.tsx create mode 100755 components/building/Accounts.tsx create mode 100755 components/building/AccountsView.tsx create mode 100755 components/building/AddCustomFieldModal.tsx create mode 100755 components/building/BudgetPlanView.tsx create mode 100755 components/building/DynamicInspectionSections.tsx create mode 100755 components/building/EditableField.tsx create mode 100755 components/building/Finance.tsx create mode 100755 components/building/FinanceView.tsx create mode 100755 components/building/Inspections.tsx create mode 100755 components/building/InspectionsView.tsx create mode 100755 components/building/InventoryView.tsx create mode 100755 components/building/MeterCheck.tsx create mode 100755 components/building/MeterCheckView.tsx create mode 100755 components/building/Overview.tsx create mode 100755 components/building/OverviewView.tsx create mode 100755 components/building/Passport.tsx create mode 100755 components/building/PassportView.tsx create mode 100755 components/building/ResidentsView.tsx create mode 100755 components/building/Supply.tsx create mode 100755 components/building/TaskModal.tsx create mode 100755 components/building/WorkPlan.tsx create mode 100755 components/development/AddBallotModal.tsx create mode 100755 components/development/AddPipelineObjectModal.tsx create mode 100755 components/development/AuditCardModal.tsx create mode 100755 components/development/BulkBallotModal.tsx create mode 100755 components/development/CreateOSSModal.tsx create mode 100755 components/development/DevSummary.tsx create mode 100755 components/development/EditOSSModal.tsx create mode 100755 components/development/EditPipelineObjectModal.tsx create mode 100755 components/development/MarketingActivityModal.tsx create mode 100755 components/development/MarketingCampaigns.tsx create mode 100755 components/development/OSSMaster.tsx create mode 100755 components/development/OSSRegistryModal.tsx create mode 100755 components/development/PipelineRegistry.tsx create mode 100755 components/development/TechnicalAudit.tsx create mode 100755 components/development/auditInspectionSchema.ts create mode 100755 components/finance/AggregatedReportView.tsx create mode 100755 components/finance/BuildingFinancialSummary.tsx create mode 100755 components/finance/DebtorReportDetailView.tsx create mode 100755 components/finance/DebtorsList.tsx create mode 100755 components/finance/ExpenseDirectory.tsx create mode 100755 components/finance/FinanceReports.tsx create mode 100755 components/finance/FinanceSummary.tsx create mode 100755 components/finance/InvoiceDistribution.tsx create mode 100755 components/finance/InvoiceRegistry.tsx create mode 100755 components/finance/PaymentCalendar.tsx create mode 100755 components/finance/PaymentCalendarEntryForm.tsx create mode 100755 components/finance/PaymentInvoiceDetail.tsx create mode 100755 components/finance/PaymentInvoiceForm.tsx create mode 100755 components/finance/PaymentInvoiceList.tsx create mode 100755 components/finance/PaymentSchedule.tsx create mode 100755 components/finance/PaymentStatusModal.tsx create mode 100755 components/finance/ReportDetailView.tsx create mode 100755 components/finance/ReportProcessing.tsx create mode 100755 components/finance/ReportTypesGrid.tsx create mode 100755 components/finance/ReportUploader.tsx create mode 100755 components/finance/ReportsGrid.tsx create mode 100755 components/hr/AssignTrainingModal.tsx create mode 100755 components/hr/CandidateEventsTimeline.tsx create mode 100755 components/hr/CandidateFormModal.tsx create mode 100755 components/hr/CandidatesRegistry.tsx create mode 100755 components/hr/EmployeeCardModal.tsx create mode 100755 components/hr/EmployeeFormModal.tsx create mode 100755 components/hr/EmployeeRegistry.tsx create mode 100755 components/hr/HRSummary.tsx create mode 100755 components/hr/HiringPipeline.tsx create mode 100755 components/hr/OrganizationalStructure.tsx create mode 100755 components/hr/PayrollModule.tsx create mode 100755 components/hr/ProgramModal.tsx create mode 100755 components/hr/SafetyBriefing.tsx create mode 100755 components/hr/TrainingManagementModal.tsx create mode 100755 components/hr/TrainingModule.tsx create mode 100755 components/hr/VacanciesRegistry.tsx create mode 100755 components/hr/VacancyFormModal.tsx create mode 100755 components/hr/WorkCalendarModal.tsx create mode 100755 components/hr/WorkCalendarView.tsx create mode 100755 components/legal/CaseDetailsModal.tsx create mode 100755 components/legal/ComplianceCheck.tsx create mode 100755 components/legal/ContractsRegistry.tsx create mode 100755 components/legal/CourtCases.tsx create mode 100755 components/legal/DebtRecoveryPipeline.tsx create mode 100755 components/legal/LegalSummary.tsx create mode 100755 components/legal/PreTrialWork.tsx create mode 100755 components/objects/BuildingsRegistry.tsx create mode 100755 components/objects/DeleteConfirmModal.tsx create mode 100755 components/objects/DistrictStaffModal.tsx create mode 100755 components/objects/DistrictsSummary.tsx create mode 100755 components/objects/EditDistrictModal.tsx create mode 100755 components/objects/MoveBuildingsModal.tsx create mode 100755 components/objects/PerformanceCard.tsx create mode 100755 components/objects/StaffRegistry.tsx create mode 100755 components/office/BookMeetingRoomModal.tsx create mode 100755 components/office/CompanyNewsRegistry.tsx create mode 100755 components/office/DocumentFlow.tsx create mode 100755 components/office/FacilityManagement.tsx create mode 100755 components/office/KnowledgeBase.css create mode 100755 components/office/KnowledgeBase.tsx create mode 100755 components/office/MeetingsAndRooms.tsx create mode 100755 components/office/OfficeSummary.tsx create mode 100755 components/office/RepairRequests.tsx create mode 100755 components/office/SupplyRegistry.tsx create mode 100755 components/pr/BuildingReportPage.tsx create mode 100755 components/pr/EventsRegistry.tsx create mode 100755 components/pr/NPSSurveyPage.tsx create mode 100755 components/pr/NPSSurveyStatsPage.tsx create mode 100755 components/pr/NPSSurveysManager.tsx create mode 100755 components/pr/NegativeResolution.tsx create mode 100755 components/pr/PRFeedbackFeed.tsx create mode 100755 components/pr/PRSummary.tsx create mode 100755 components/pr/PostTopicsManager.tsx create mode 100755 components/pr/PublicationSchedule.tsx create mode 100755 components/pr/ResidentReportPage.tsx create mode 100755 components/pr/ResidentReportView.tsx create mode 100755 components/pr/ResidentReports.tsx create mode 100755 components/pr/SMMManager.tsx create mode 100755 components/pr/WorkPhotosDirectory.tsx create mode 100755 components/pr/WorkPhotosManager.tsx create mode 100755 components/settings/CompanySettingsModal.tsx create mode 100755 constants.tsx create mode 100755 constants/permissions.ts create mode 100755 constants/refreshEvents.ts create mode 100755 constants/roleAccess.ts create mode 100755 contexts/PermissionsContext.tsx create mode 100755 docs/DEVELOPMENT_PIPELINE_AUTOMATION.md create mode 100755 docs/DOMA_AI_INTEGRATION.md create mode 100755 docs/PIPELINE_AUTOMATION_FLOW.md create mode 100755 docs/REPORT_01_ROLES_AND_RESTRICTIONS.md create mode 100755 docs/REPORT_02_ACCESS_TABLE_AND_REWRITE_PLAN.md create mode 100755 hooks/useCachedFetch.ts create mode 100755 index.html create mode 100755 index.tsx create mode 100755 inspectionElementTemplates.ts create mode 100755 js/tailwind.js create mode 100755 metadata.json create mode 100755 package-lock.json create mode 100755 package.json create mode 100755 services/apiClient.ts create mode 100755 services/connectionService.ts create mode 100755 services/domaGraphQLClient.ts create mode 100755 services/domaService.ts create mode 100755 services/geminiService.ts create mode 100755 services/offlineCacheService.ts create mode 100755 services/settingsService.ts create mode 100755 services/storageService.ts create mode 100755 tsconfig.json create mode 100755 types.ts create mode 100755 vite.config.ts create mode 100755 план.md diff --git a/.env.example b/.env.example new file mode 100755 index 0000000..dfbc36a --- /dev/null +++ b/.env.example @@ -0,0 +1,27 @@ +# API Configuration — ваш Node/Express бэкенд (НЕ n8n). +# Локально: http://localhost:4000/api или /api (если включён proxy в vite.config). +# В dev при /api запросы идут через Vite proxy на localhost:4000. +VITE_API_BASE_URL=http://localhost:4000/api + +# Auth (backend) +JWT_SECRET=your-secret-change-in-production +JWT_EXPIRES_IN=7d + +# Turnstile captcha (optional): https://dash.cloudflare.com/?to=/:account/turnstile +TURNSTILE_SECRET_KEY= +VITE_TURNSTILE_SITE_KEY= + +# Doma AI API Configuration (PRODUCTION) +# ВАЖНО: Укажите URL вашего продакшн инстанса Doma AI +# Пример для продакшена: https://your-domain.doma.ai/admin/api +# Для тестирования можно использовать: https://condo.d.doma.ai/admin/api +VITE_DOMA_AI_API_URL=https://your-domain.doma.ai/admin/api + +# Учетные данные для авторизации в Doma AI +# Используйте email и пароль ИЛИ телефон и пароль +# Для продакшена используйте учетные данные вашей организации в Doma AI +VITE_DOMA_AI_EMAIL=your-email@example.com +VITE_DOMA_AI_PASSWORD=your-password +# ИЛИ используйте телефон: +# VITE_DOMA_AI_PHONE=+79991234567 +# VITE_DOMA_AI_PASSWORD=your-password diff --git a/.env.production b/.env.production new file mode 100755 index 0000000..8c30a39 --- /dev/null +++ b/.env.production @@ -0,0 +1,13 @@ +VITE_API_BASE_URL=http://localhost:4000/api + +# Doma AI API Configuration (PRODUCTION) +# ВАЖНО: Укажите URL вашего продакшн инстанса Doma AI +# Пример: https://your-domain.doma.ai/admin/api +# Для тестирования можно использовать: https://condo.d.doma.ai/admin/api +VITE_DOMA_AI_API_URL= + +# Учетные данные для авторизации в Doma AI +# Используйте email и пароль ИЛИ телефон и пароль +VITE_DOMA_AI_EMAIL= +VITE_DOMA_AI_PASSWORD= +VITE_DOMA_AI_PHONE= \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100755 index 0000000..a547bf3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/App.tsx b/App.tsx new file mode 100755 index 0000000..52c0b6e --- /dev/null +++ b/App.tsx @@ -0,0 +1,1218 @@ + +import React, { useState, useEffect, lazy, Suspense } from 'react'; +import { Navigation } from './components/Navigation'; +import { Bell, ChevronDown, Settings, User as UserIcon, LogOut, X, Plug, UserCog, Building2 } from 'lucide-react'; +import { Building, User, UserRole } from './types'; +import { CURRENT_USER_MOCK, NAV_ITEMS } from './constants'; +import { storageService } from './services/storageService'; +import { backendApi, syncCachedRequests, getAuthToken, setAuthToken, clearAuth, fetchGuestToken } from './services/apiClient'; +import { ConnectionIndicator } from './components/ConnectionIndicator'; +import { NotificationPanel } from './components/NotificationPanel'; +import { connectionService } from './services/connectionService'; +import { settingsService, DomaAISettings } from './services/settingsService'; +import { apiClient } from './services/apiClient'; +import { PermissionsProvider } from './contexts/PermissionsContext'; +import { allowedSubsForSection } from './constants/permissions'; +import { ROLE_NAMES, ROLE_ACCESS } from './constants/roleAccess'; + +// Lazy-loaded modules (reduce initial bundle size) +const BuildingCharacteristics = lazy(() => import('./components/BuildingCharacteristics').then(m => ({ default: m.BuildingCharacteristics }))); +const DashboardNavigation = lazy(() => import('./components/DashboardNavigation').then(m => ({ default: m.DashboardNavigation }))); +const SummaryDashboard = lazy(() => import('./components/SummaryDashboard').then(m => ({ default: m.SummaryDashboard }))); +const HRModule = lazy(() => import('./components/HRModule').then(m => ({ default: m.HRModule }))); +const ApplicationsModule = lazy(() => import('./components/ApplicationsModule').then(m => ({ default: m.ApplicationsModule }))); +const PRModule = lazy(() => import('./components/PRModule').then(m => ({ default: m.PRModule }))); +const FinanceModule = lazy(() => import('./components/FinanceModule').then(m => ({ default: m.FinanceModule }))); +const OfficeModule = lazy(() => import('./components/OfficeModule').then(m => ({ default: m.OfficeModule }))); +const LegalModule = lazy(() => import('./components/LegalModule').then(m => ({ default: m.LegalModule }))); +const DevelopmentModule = lazy(() => import('./components/DevelopmentModule').then(m => ({ default: m.DevelopmentModule }))); +const AdminModule = lazy(() => import('./components/AdminModule').then(m => ({ default: m.AdminModule }))); +const BuildingReportPage = lazy(() => import('./components/pr/BuildingReportPage').then(m => ({ default: m.BuildingReportPage }))); +const NPSSurveyPage = lazy(() => import('./components/pr/NPSSurveyPage').then(m => ({ default: m.NPSSurveyPage }))); +const LoginPage = lazy(() => import('./components/LoginPage').then(m => ({ default: m.LoginPage }))); +const ProfileSettingsModal = lazy(() => import('./components/ProfileSettingsModal').then(m => ({ default: m.ProfileSettingsModal }))); + +const LazyFallback = () => ( +
+
Загрузка...
+
+); + +const CURRENT_USER_STORAGE_KEY = 'mkd_currentUser'; +const INTEGRATION_ENABLED_KEY = 'mkd_integrationEnabled'; +const PORTAL_LOGIN_KEY = 'mkd_portalLogin'; + +export default function App() { + // ====== Публичные страницы отчетов (/reports/:id) ====== + const [pathname, setPathname] = useState(() => window.location.pathname); + const [isAuthenticated, setIsAuthenticated] = useState(null); + const [allowedSections, setAllowedSections] = useState([]); + /** Детальные права (разделы и подразделы/отчёты). null = по роли, все подразделы разрешены */ + const [userPermissions, setUserPermissions] = useState(null); + const [authError, setAuthError] = useState(null); + const [authLoading, setAuthLoading] = useState(false); + + const [activeTab, setActiveTab] = useState(() => { + const saved = localStorage.getItem('mkd_activeTab'); + return saved || 'dashboard'; + }); + const [currentUser, setCurrentUser] = useState(() => { + try { + const saved = localStorage.getItem(CURRENT_USER_STORAGE_KEY); + if (saved) { + return JSON.parse(saved) as User; + } + } catch (e) { + console.warn('[App] Не удалось прочитать сохранённый профиль пользователя, используем значения по умолчанию'); + } + return CURRENT_USER_MOCK; + }); + const [selectedBuilding, setSelectedBuilding] = useState(null); + const [isUserMenuOpen, setIsUserMenuOpen] = useState(false); + const [isSettingsOpen, setIsSettingsOpen] = useState(false); + const [isProfileOpen, setIsProfileOpen] = useState(false); + const [isRoleAdminOpen, setIsRoleAdminOpen] = useState(false); + const [activeSettingsTab, setActiveSettingsTab] = useState<'role' | 'integrations'>('role'); + const [editableUser, setEditableUser] = useState(null); + const [integrationEnabled, setIntegrationEnabled] = useState(() => { + const saved = localStorage.getItem(INTEGRATION_ENABLED_KEY); + if (saved === 'false') return false; + return true; + }); + const [financeOpenInvoiceId, setFinanceOpenInvoiceId] = useState(null); + const [notificationUnreadCount, setNotificationUnreadCount] = useState(0); + const [isNotificationPanelOpen, setIsNotificationPanelOpen] = useState(false); + const [financeInvoicePrefill, setFinanceInvoicePrefill] = useState<{ + purposeType: 'event'; + purposeEventId?: string; + purposeDescription?: string; + totalAmount?: number; + } | null>(null); + const [pendingQuickAction, setPendingQuickAction] = useState(null); + const [dashboardOpenNewsId, setDashboardOpenNewsId] = useState(null); + + useEffect(() => { + const onPopState = () => setPathname(window.location.pathname); + window.addEventListener('popstate', onPopState); + return () => window.removeEventListener('popstate', onPopState); + }, []); + + // Восстановление сессии по токену; параллельно при необходимости загружаем список зданий для savedBuildingId + useEffect(() => { + if (pathname.startsWith('/reports/') || pathname.startsWith('/nps/')) { + return; + } + const token = getAuthToken(); + if (!token) { + setIsAuthenticated(false); + return; + } + const savedBuildingId = localStorage.getItem('mkd_selectedBuildingId'); + const needBuildings = !!savedBuildingId && !storageService.getBuildingById(savedBuildingId); + let cancelled = false; + const mePromise = backendApi.getMe(); + const buildingsPromise = needBuildings ? backendApi.getBuildings() : Promise.resolve(null); + Promise.all([mePromise, buildingsPromise]) + .then(([me, buildings]) => { + if (cancelled) return; + setCurrentUser({ + ...me, + id: me.id, + name: me.name, + role: me.role as UserRole, + avatar: me.avatar || 'https://picsum.photos/id/1005/64/64', + }); + setAllowedSections(me.allowedSections || []); + setUserPermissions(me.permissions ?? null); + setIsAuthenticated(true); + setAuthError(null); + if (needBuildings && buildings) { + const found = buildings.find((b: Building) => b.id === savedBuildingId); + if (found) setSelectedBuilding(found); + else localStorage.removeItem('mkd_selectedBuildingId'); + } + }) + .catch(() => { + if (!cancelled) { + setIsAuthenticated(false); + setAuthError(null); + if (needBuildings) localStorage.removeItem('mkd_selectedBuildingId'); + } + }); + return () => { cancelled = true; }; + }, [pathname]); + + // Установка selectedBuilding из localStorage, если здание уже есть в storage (без запроса к API) + useEffect(() => { + const savedBuildingId = localStorage.getItem('mkd_selectedBuildingId'); + if (savedBuildingId) { + const building = storageService.getBuildingById(savedBuildingId); + if (building) setSelectedBuilding(building); + } + }, []); + + useEffect(() => { + const onLogout = () => setIsAuthenticated(false); + window.addEventListener('mkd-auth-logout', onLogout); + return () => window.removeEventListener('mkd-auth-logout', onLogout); + }, []); + + // --- All hooks must run before any conditional return (Rules of Hooks) --- + type FinanceApproverRole = 'manager' | 'finance_manager' | 'financier' | 'finance_director' | 'director' | 'top_management'; + type FinanceUserRoleRow = { id: number; userId: string; role: FinanceApproverRole; createdAt?: string; updatedAt?: string }; + + const [roleAdminUserId, setRoleAdminUserId] = useState('user-1'); + const [roleAdminRoles, setRoleAdminRoles] = useState([]); + const [roleAdminLoading, setRoleAdminLoading] = useState(false); + const [roleAdminSelectedRole, setRoleAdminSelectedRole] = useState('manager'); + + const [domaAISettings, setDomaAISettings] = useState(() => { + const saved = settingsService.getDomaAISettings(); + return saved || { + apiUrl: '', + token: '', + }; + }); + + // --- All useEffects must run before any conditional return (Rules of Hooks) --- + useEffect(() => { + if (isSettingsOpen) { + backendApi.getDomaSettings() + .then((data) => { + setDomaAISettings({ apiUrl: data.apiUrl || '', token: data.token || '' }); + }) + .catch(() => { + const saved = settingsService.getDomaAISettings(); + if (saved) setDomaAISettings(saved); + }); + } + }, [isSettingsOpen]); + + useEffect(() => { + if (isProfileOpen) { + setEditableUser(currentUser); + } + }, [isProfileOpen, currentUser]); + + useEffect(() => { + try { + localStorage.setItem(CURRENT_USER_STORAGE_KEY, JSON.stringify(currentUser)); + } catch (e) { + console.warn('[App] Не удалось сохранить профиль пользователя в localStorage'); + } + }, [currentUser]); + + useEffect(() => { + localStorage.setItem('mkd_activeTab', activeTab); + }, [activeTab]); + + const fetchNotificationUnreadCount = React.useCallback(async () => { + if (!getAuthToken()) return; + try { + const { count } = await backendApi.getUnreadCount(); + setNotificationUnreadCount(count); + } catch { + setNotificationUnreadCount(0); + } + }, []); + + useEffect(() => { + fetchNotificationUnreadCount(); + const interval = setInterval(fetchNotificationUnreadCount, 60000); + const onFocus = () => fetchNotificationUnreadCount(); + window.addEventListener('focus', onFocus); + return () => { + clearInterval(interval); + window.removeEventListener('focus', onFocus); + }; + }, [fetchNotificationUnreadCount]); + + useEffect(() => { + const allowed = allowedSections.length > 0 + ? (allowedSections.includes('all') ? ['dashboard', 'objects', 'requests', 'pr', 'finance', 'legal', 'development', 'hr', 'office', 'admin'] : allowedSections) + : ROLE_ACCESS[currentUser.role]; + if (allowed && !allowed.includes('all') && !allowed.includes(activeTab)) { + const firstAllowed = NAV_ITEMS.find(item => allowed.includes(item.id))?.id || 'dashboard'; + if (firstAllowed && firstAllowed !== activeTab) { + setActiveTab(firstAllowed); + } + } + }, [currentUser.role, activeTab, allowedSections]); + + useEffect(() => { + localStorage.setItem(INTEGRATION_ENABLED_KEY, String(integrationEnabled)); + }, [integrationEnabled]); + + useEffect(() => { + let lastStatus = connectionService.getStatus(); + const unsubscribe = connectionService.subscribe((status) => { + if (lastStatus === 'disconnected' && status === 'connected') { + syncCachedRequests().then((result) => { + if (result.success > 0) { + console.log(`Синхронизировано ${result.success} запросов из кэша`); + } + }).catch((error) => { + console.error('Ошибка синхронизации кэша:', error); + }); + } + lastStatus = status; + }); + return () => { unsubscribe(); }; + }, []); + + useEffect(() => { + const handler = (event: any) => { + const d = event?.detail; + if (d?.invoiceId) { + setFinanceOpenInvoiceId(d.invoiceId); + setFinanceInvoicePrefill(null); + setActiveTab('finance'); + return; + } + if (d?.purposeType === 'event' || d?.purposeEventId) { + setFinanceOpenInvoiceId(null); + setFinanceInvoicePrefill({ + purposeType: 'event', + purposeEventId: d?.purposeEventId, + purposeDescription: d?.purposeDescription ?? '', + totalAmount: d?.totalAmount + }); + setActiveTab('finance'); + } + }; + window.addEventListener('mkd-open-finance-invoice', handler as EventListener); + return () => window.removeEventListener('mkd-open-finance-invoice', handler as EventListener); + }, []); + + // Если открыли публичную ссылку на отчет - рендерим сразу опубликованную версию без портала + if (pathname.startsWith('/reports/')) { + const parts = pathname.split('/').filter(Boolean); + const reportId = parts[1] || 'demo'; + + // Компонент для загрузки отчета и отображения + const PublishedReportLoader = () => { + const [reportInfo, setReportInfo] = React.useState<{ address: string; buildingId?: string; month?: string } | null>(null); + const [isLoading, setIsLoading] = React.useState(true); + + React.useEffect(() => { + const loadReport = async () => { + if (reportId === 'demo') { + setReportInfo({ address: 'Кавказская, 12' }); + setIsLoading(false); + return; + } + + try { + // Гость без учётки: если нет токена — получаем гостевой (доступ только к отчётам) + if (!getAuthToken()) { + const guestToken = await fetchGuestToken(); + setAuthToken(guestToken); + } + const report = await apiClient.get(`/pr/reports/${reportId}`); + console.log('[PublishedReportLoader] Загружен отчет:', { + reportId, + hasAddress: !!report.address, + hasContent: !!report.content, + hasBuildingData: !!report.building_data, + contentBuildingAddress: report.content?.building?.address + }); + + // Извлекаем адрес из разных возможных мест (приоритет: content.building.address > address > building_data) + let address = report.content?.building?.address || + report.address || + (report.building_data?.passport?.address) || + (report.building_data?.passport?.general?.address); + + // Если адрес все еще не найден, используем fallback + if (!address) { + address = `Отчет ${reportId}`; + console.warn('[PublishedReportLoader] Адрес не найден, используем fallback'); + } + + setReportInfo({ + address: address, + buildingId: report.buildingId || report.building_id, + month: report.month + }); + } catch (err) { + console.error('Error loading report:', err); + setReportInfo({ address: `Отчет ${reportId}` }); + } finally { + setIsLoading(false); + } + }; + + loadReport(); + }, [reportId]); + + if (isLoading) { + return ; + } + + return ( + + ); + }; + + return ( + }> + + + ); + } + + // Если открыли публичную ссылку на NPS опрос - рендерим страницу опроса без портала + if (pathname.startsWith('/nps/')) { + const parts = pathname.split('/').filter(Boolean); + const surveyId = parts[1] || null; + + if (surveyId) { + // Получаем номер квартиры из URL параметров + const urlParams = new URLSearchParams(window.location.search); + const apartment = urlParams.get('apartment'); + + return ( + }> + + + ); + } + } + + // Экран входа: нет токена или сессия не восстановлена + if (isAuthenticated === false) { + return ( + }> + { + setCurrentUser({ + id: user.id, + name: user.name, + role: user.role as UserRole, + avatar: user.avatar || 'https://picsum.photos/id/1005/64/64', + }); + backendApi.getMe().then((me) => { + setAllowedSections(me.allowedSections || []); + setUserPermissions(me.permissions ?? null); + }).catch(() => {}); + setIsAuthenticated(true); + setAuthError(null); + }} + /> + + ); + } + + // Загрузка сессии (проверка токена) + if (isAuthenticated === null) { + return ( +
+
Загрузка...
+
+ ); + } + + // ====== Админка ролей согласования счетов (скрыто в меню пользователя) ====== + const loadUserApproverRoles = async (userId: string) => { + try { + setRoleAdminLoading(true); + const roles = await apiClient.get(`/finance/user-roles?userId=${encodeURIComponent(userId)}`); + setRoleAdminRoles(Array.isArray(roles) ? roles : []); + } catch (err: any) { + console.error('Error loading user roles:', err); + setRoleAdminRoles([]); + alert(err?.message || 'Ошибка загрузки ролей пользователя'); + } finally { + setRoleAdminLoading(false); + } + }; + + const addUserApproverRole = async (userId: string, role: FinanceApproverRole) => { + try { + setRoleAdminLoading(true); + await apiClient.post(`/finance/user-roles`, { userId, role }); + await loadUserApproverRoles(userId); + } catch (err: any) { + console.error('Error adding user role:', err); + alert(err?.message || 'Ошибка добавления роли'); + } finally { + setRoleAdminLoading(false); + } + }; + + const removeUserApproverRole = async (id: number) => { + try { + setRoleAdminLoading(true); + await apiClient.delete(`/finance/user-roles/${id}`); + await loadUserApproverRoles(roleAdminUserId); + } catch (err: any) { + console.error('Error removing user role:', err); + alert(err?.message || 'Ошибка удаления роли'); + } finally { + setRoleAdminLoading(false); + } + }; + + const handleSelectBuilding = (building: Building) => { + setSelectedBuilding(building); + // Сохраняем ID выбранного здания в localStorage + localStorage.setItem('mkd_selectedBuildingId', building.id); + }; + + const handleBackToDashboard = () => { + setSelectedBuilding(null); + // Удаляем сохраненное здание из localStorage + localStorage.removeItem('mkd_selectedBuildingId'); + }; + + // Demo function to switch roles + const toggleRole = () => { + const roles: UserRole[] = ['DIRECTOR', 'ENGINEER', 'MASTER', 'LAWYER', 'FINANCIER', 'HR_MANAGER', 'PR_MANAGER']; + const nextIndex = (roles.indexOf(currentUser.role) + 1) % roles.length; + const newRole = roles[nextIndex]; + + // Update user role + setCurrentUser({ ...currentUser, role: newRole }); + }; + + const handleLogout = () => { + if (confirm('Вы уверены, что хотите выйти?')) { + clearAuth(); + setCurrentUser(CURRENT_USER_MOCK); + setAllowedSections([]); + setIsAuthenticated(false); + setIsUserMenuOpen(false); + } + }; + + const setModuleSubtab = (key: string, subtab: string) => { + try { + localStorage.setItem(key, subtab); + } catch (_) {} + }; + + const handleQuickAction = (action: string) => { + if (action === 'dashboard_task') { + setPendingQuickAction('dashboard_task'); + setActiveTab('dashboard'); + return; + } + if (action === 'inspection') { + setActiveTab('objects'); + if (!selectedBuilding) { + setTimeout(() => alert('Выберите дом для начала осмотра'), 100); + } + return; + } + if (action === 'task') { + setActiveTab('objects'); + if (!selectedBuilding) { + setTimeout(() => alert('Выберите дом для постановки задачи'), 100); + } + return; + } + if (action === 'request') { + setActiveTab('requests'); + return; + } + if (action === 'office_invoice') { + setActiveTab('office'); + setModuleSubtab('mkd_subTab_office', 'dashboard'); + return; + } + if (action === 'office_repair') { + setActiveTab('office'); + setModuleSubtab('mkd_subTab_office', 'repair'); + return; + } + if (action === 'office_article') { + setActiveTab('office'); + setModuleSubtab('mkd_subTab_office', 'knowledge'); + return; + } + if (action === 'office_document') { + setActiveTab('office'); + setModuleSubtab('mkd_subTab_office', 'docs'); + return; + } + if (action === 'office_equipment') { + setActiveTab('office'); + setModuleSubtab('mkd_subTab_office', 'facility'); + return; + } + if (action === 'office_order') { + setActiveTab('office'); + setModuleSubtab('mkd_subTab_office', 'supply'); + return; + } + if (action === 'office_meeting') { + setActiveTab('office'); + setModuleSubtab('mkd_subTab_office', 'meetings'); + return; + } + if (action === 'pr_feedback' || action === 'pr_incident') { + setActiveTab('pr'); + setModuleSubtab('mkd_subTab_pr', 'feedback'); + return; + } + if (action === 'pr_photo') { + setActiveTab('pr'); + setModuleSubtab('mkd_subTab_pr', 'photos'); + return; + } + if (action === 'pr_nps') { + setActiveTab('pr'); + setModuleSubtab('mkd_subTab_pr', 'nps'); + return; + } + if (action === 'invoice') { + setActiveTab('finance'); + setModuleSubtab('mkd_subTab_finance', 'invoices'); + return; + } + if (action === 'finance_calendar') { + setActiveTab('finance'); + setModuleSubtab('mkd_subTab_finance', 'calendar'); + return; + } + if (action === 'legal_contract') { + setActiveTab('legal'); + setModuleSubtab('mkd_subTab_legal', 'contracts'); + return; + } + if (action === 'legal_court') { + setActiveTab('legal'); + setModuleSubtab('mkd_subTab_legal', 'courts'); + return; + } + if (action === 'legal_debtor' || action === 'legal_action') { + setActiveTab('legal'); + setModuleSubtab('mkd_subTab_legal', 'preTrial'); + return; + } + if (action === 'legal_petition') { + setActiveTab('legal'); + setModuleSubtab('mkd_subTab_legal', 'debt'); + return; + } + if (action === 'development_oss') { + setActiveTab('development'); + setModuleSubtab('mkd_subTab_development', 'oss'); + return; + } + if (action === 'development_pipeline') { + setActiveTab('development'); + setModuleSubtab('mkd_subTab_development', 'pipeline'); + return; + } + if (action === 'development_marketing') { + setActiveTab('development'); + setModuleSubtab('mkd_subTab_development', 'marketing'); + return; + } + if (action === 'hire') { + setActiveTab('hr'); + setModuleSubtab('mkd_subTab_hr', 'hiring'); + return; + } + if (action === 'hr_vacancy') { + setActiveTab('hr'); + setModuleSubtab('mkd_subTab_hr', 'vacancies'); + return; + } + if (action === 'hr_employee') { + setActiveTab('hr'); + setModuleSubtab('mkd_subTab_hr', 'employees'); + return; + } + if (action === 'hr_calendar') { + setActiveTab('hr'); + setModuleSubtab('mkd_subTab_hr', 'calendar'); + return; + } + }; + + const renderContent = () => { + if (activeTab === 'objects') { + if (selectedBuilding) { + return ( + + ); + } + return ; + } + + if (activeTab === 'dashboard') { + const allowedDashboardBlocks = allowedSubsForSection(userPermissions ?? [], 'dashboard'); + return ( + setPendingQuickAction(null)} + openNewsId={dashboardOpenNewsId} + onCloseNews={() => setDashboardOpenNewsId(null)} + onNavigateToModule={(tabId) => setActiveTab(tabId)} + /> + ); + } + + if (activeTab === 'requests') { + return ; + } + + if (activeTab === 'pr') { + return ; + } + + if (activeTab === 'finance') { + return ( + { + setFinanceOpenInvoiceId(null); + setFinanceInvoicePrefill(null); + }} + allowedPermissions={userPermissions} + /> + ); + } + + if (activeTab === 'hr') { + return ; + } + + if (activeTab === 'office') { + return ; + } + + if (activeTab === 'legal') { + return ; + } + + if (activeTab === 'development') { + return ; + } + + if (activeTab === 'admin') { + return ; + } + + return ( +
+
+ 🏗️ +
+

Раздел в разработке

+

Функционал "{activeTab}" скоро появится в центре управления.

+
+ ); + }; + + return ( + +
+ + {/* Top Header */} +
+
+ + {/* Logo & Brand */} +
+
+ ЦУ +
+
+

Центр управления

+

жилым фондом

+
+
+ + {/* User & Actions */} +
+ +
+ + setIsNotificationPanelOpen(false)} + unreadCount={notificationUnreadCount} + onUnreadCountChange={setNotificationUnreadCount} + onNavigate={(entityType, entityId) => { + if (entityType === 'application') { + setActiveTab('requests'); + } else if (entityType === 'payment_invoice') { + setActiveTab('finance'); + setFinanceOpenInvoiceId(parseInt(entityId, 10)); + } else if (entityType === 'repair_request') { + setActiveTab('office'); + try { + localStorage.setItem('mkd_subTab_office', 'repair'); + } catch (_) {} + } else if (entityType === 'incident') { + setActiveTab('pr'); + try { + localStorage.setItem('mkd_subTab_pr', 'feedback'); + } catch (_) {} + } else if (entityType === 'pipeline') { + setActiveTab('development'); + } else if (entityType === 'pre_trial_work') { + setActiveTab('legal'); + try { + localStorage.setItem('mkd_subTab_legal', 'pre_trial'); + } catch (_) {} + } else if (entityType === 'training') { + setActiveTab('hr'); + try { + localStorage.setItem('mkd_subTab_hr', 'training'); + } catch (_) {} + } else if (entityType === 'company_news') { + setActiveTab('dashboard'); + const id = parseInt(entityId, 10); + if (!Number.isNaN(id)) setDashboardOpenNewsId(id); + } else if (entityType === 'outage') { + setActiveTab('requests'); + try { + localStorage.setItem('mkd_subTab_requests', 'outages'); + } catch (_) {} + } else if (entityType === 'oss') { + setActiveTab('development'); + try { + localStorage.setItem('mkd_subTab_development', 'oss'); + } catch (_) {} + } else if (entityType === 'legal_debtor') { + setActiveTab('legal'); + try { + localStorage.setItem('mkd_subTab_legal', 'debt'); + } catch (_) {} + } else if (entityType === 'pr_event') { + setActiveTab('pr'); + try { + localStorage.setItem('mkd_subTab_pr', 'events'); + } catch (_) {} + } else if (entityType === 'nps_survey') { + setActiveTab('pr'); + try { + localStorage.setItem('mkd_subTab_pr', 'nps'); + } catch (_) {} + } + }} + /> +
+ + {/* User Menu */} +
+ + + {/* Dropdown Menu */} + {isUserMenuOpen && ( + <> +
setIsUserMenuOpen(false)} + /> +
+ {/* User Info */} +
+
+ Profile +
+

{currentUser.name}

+

{ROLE_NAMES[currentUser.role]}

+
+
+
+ + {/* Профиль */} +
+ +
+ + {/* Logout */} +
+ +
+
+ + )} +
+
+
+
+ + {/* Main Content */} +
+ }> + {renderContent()} + +
+ + {/* Settings Modal */} + {isSettingsOpen && ( +
setIsSettingsOpen(false)}> +
e.stopPropagation()}> + {/* Header */} +
+
+
+

Настройки

+

Управление параметрами системы

+
+ +
+
+ + {/* Content: tabs on mobile, two columns on desktop */} +
+ {/* Mobile: horizontal tabs */} +
+ + +
+ {/* Desktop: left sidebar */} +
+
+ + +
+
+ {/* Right Content */} +
+
+ {activeSettingsTab === 'role' && ( +
+

Роль пользователя

+

Текущая роль определяет доступные разделы системы

+ +
+
+ + +
+
+
+ )} + + {activeSettingsTab === 'integrations' && ( +
+

Интеграции

+

Настройка подключений к внешним сервисам

+ + {/* Настройки Дома.АИ */} +
+
+
+ Д.АИ +
+
+
Дома.АИ
+

Интеграция с системой управления заявками

+
+
+ +
+
+ + setDomaAISettings({ ...domaAISettings, apiUrl: e.target.value })} + placeholder="https://your-domain.doma.ai/admin/api" + className="w-full px-4 py-2.5 bg-white border border-slate-200 rounded-lg text-sm text-slate-800 focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-transparent" + /> +
+ +
+ +