Repository: krayin/laravel-crm Branch: 2.2 Commit: ba25b7e7f8e1 Files: 1083 Total size: 6.0 MB Directory structure: gitextract_tgh1bov9/ ├── .editorconfig ├── .gitattributes ├── .github/ │ ├── FUNDING.yml │ ├── ISSUE_TEMPLATE/ │ │ ├── 1_Bug_report.md │ │ ├── 2_Feature_request.md │ │ ├── 3_Support_question.md │ │ └── 4_Security_vulnerabilities.md │ ├── PULL_REQUEST_TEMPLATE.md │ └── workflows/ │ ├── admin_playwright_tests.yml │ ├── auto_commits.yml │ └── ci.yml ├── .gitignore ├── .styleci.yml ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md ├── UPGRADE.md ├── app/ │ ├── Http/ │ │ └── Controllers/ │ │ └── Controller.php │ ├── Models/ │ │ └── User.php │ └── Providers/ │ └── AppServiceProvider.php ├── artisan ├── bootstrap/ │ ├── app.php │ ├── cache/ │ │ └── .gitignore │ └── providers.php ├── composer.json ├── config/ │ ├── app.php │ ├── auth.php │ ├── breadcrumbs.php │ ├── broadcasting.php │ ├── cache.php │ ├── concord.php │ ├── cors.php │ ├── database.php │ ├── filesystems.php │ ├── hashing.php │ ├── imap.php │ ├── krayin-vite.php │ ├── logging.php │ ├── mail-receiver.php │ ├── mail.php │ ├── queue.php │ ├── repository.php │ ├── sanctum.php │ ├── services.php │ ├── session.php │ ├── tinker.php │ └── view.php ├── database/ │ ├── .gitignore │ ├── factories/ │ │ └── UserFactory.php │ ├── migrations/ │ │ ├── 2019_08_19_000000_create_failed_jobs_table.php │ │ ├── 2019_12_14_000001_create_personal_access_tokens_table.php │ │ ├── 2024_09_09_094040_create_job_batches_table.php │ │ └── 2024_09_09_094042_create_jobs_table.php │ └── seeders/ │ └── DatabaseSeeder.php ├── lang/ │ └── en/ │ ├── auth.php │ ├── pagination.php │ ├── passwords.php │ └── validation.php ├── package.json ├── packages/ │ └── Webkul/ │ ├── Activity/ │ │ ├── composer.json │ │ └── src/ │ │ ├── Contracts/ │ │ │ ├── Activity.php │ │ │ ├── File.php │ │ │ └── Participant.php │ │ ├── Database/ │ │ │ └── Migrations/ │ │ │ ├── 2021_05_12_150329_create_activities_table.php │ │ │ ├── 2021_05_15_151855_create_activity_files_table.php │ │ │ ├── 2021_07_28_142453_create_activity_participants_table.php │ │ │ ├── 2021_11_17_190943_add_location_column_in_activities_table.php │ │ │ └── 2025_01_17_151632_alter_activities_table.php │ │ ├── Models/ │ │ │ ├── Activity.php │ │ │ ├── ActivityProxy.php │ │ │ ├── File.php │ │ │ ├── FileProxy.php │ │ │ ├── Participant.php │ │ │ └── ParticipantProxy.php │ │ ├── Providers/ │ │ │ ├── ActivityServiceProvider.php │ │ │ └── ModuleServiceProvider.php │ │ ├── Repositories/ │ │ │ ├── ActivityRepository.php │ │ │ ├── FileRepository.php │ │ │ └── ParticipantRepository.php │ │ └── Traits/ │ │ └── LogsActivity.php │ ├── Admin/ │ │ ├── .gitignore │ │ ├── composer.json │ │ ├── package.json │ │ ├── postcss.config.cjs │ │ ├── src/ │ │ │ ├── Bouncer.php │ │ │ ├── Config/ │ │ │ │ ├── acl.php │ │ │ │ ├── attribute_entity_types.php │ │ │ │ ├── attribute_lookups.php │ │ │ │ ├── core_config.php │ │ │ │ └── menu.php │ │ │ ├── DataGrids/ │ │ │ │ ├── Activity/ │ │ │ │ │ └── ActivityDataGrid.php │ │ │ │ ├── Contact/ │ │ │ │ │ ├── OrganizationDataGrid.php │ │ │ │ │ └── PersonDataGrid.php │ │ │ │ ├── Lead/ │ │ │ │ │ └── LeadDataGrid.php │ │ │ │ ├── Mail/ │ │ │ │ │ └── EmailDataGrid.php │ │ │ │ ├── Product/ │ │ │ │ │ └── ProductDataGrid.php │ │ │ │ ├── Quote/ │ │ │ │ │ └── QuoteDataGrid.php │ │ │ │ └── Settings/ │ │ │ │ ├── AttributeDataGrid.php │ │ │ │ ├── DataTransfer/ │ │ │ │ │ └── ImportDataGrid.php │ │ │ │ ├── EmailTemplateDataGrid.php │ │ │ │ ├── GroupDataGrid.php │ │ │ │ ├── Marketing/ │ │ │ │ │ ├── CampaignDatagrid.php │ │ │ │ │ └── EventDataGrid.php │ │ │ │ ├── PipelineDataGrid.php │ │ │ │ ├── RoleDataGrid.php │ │ │ │ ├── SourceDataGrid.php │ │ │ │ ├── TagDataGrid.php │ │ │ │ ├── TypeDataGrid.php │ │ │ │ ├── UserDataGrid.php │ │ │ │ ├── WarehouseDataGrid.php │ │ │ │ ├── WebhookDataGrid.php │ │ │ │ └── WorkflowDataGrid.php │ │ │ ├── Database/ │ │ │ │ └── Migrations/ │ │ │ │ ├── 2021_06_07_162808_add_lead_view_permission_column_in_users_table.php │ │ │ │ └── 2021_10_02_170105_insert_expected_closed_date_column_in_attributes_table.php │ │ │ ├── Exceptions/ │ │ │ │ └── Handler.php │ │ │ ├── Facades/ │ │ │ │ └── Bouncer.php │ │ │ ├── Helpers/ │ │ │ │ ├── Dashboard.php │ │ │ │ └── Reporting/ │ │ │ │ ├── AbstractReporting.php │ │ │ │ ├── Activity.php │ │ │ │ ├── Lead.php │ │ │ │ ├── Organization.php │ │ │ │ ├── Person.php │ │ │ │ ├── Product.php │ │ │ │ └── Quote.php │ │ │ ├── Http/ │ │ │ │ ├── Controllers/ │ │ │ │ │ ├── Activity/ │ │ │ │ │ │ └── ActivityController.php │ │ │ │ │ ├── Configuration/ │ │ │ │ │ │ └── ConfigurationController.php │ │ │ │ │ ├── Contact/ │ │ │ │ │ │ ├── OrganizationController.php │ │ │ │ │ │ └── Persons/ │ │ │ │ │ │ ├── ActivityController.php │ │ │ │ │ │ ├── PersonController.php │ │ │ │ │ │ └── TagController.php │ │ │ │ │ ├── Controller.php │ │ │ │ │ ├── DashboardController.php │ │ │ │ │ ├── DataGrid/ │ │ │ │ │ │ └── SavedFilterController.php │ │ │ │ │ ├── DataGridController.php │ │ │ │ │ ├── Lead/ │ │ │ │ │ │ ├── ActivityController.php │ │ │ │ │ │ ├── EmailController.php │ │ │ │ │ │ ├── LeadController.php │ │ │ │ │ │ ├── QuoteController.php │ │ │ │ │ │ └── TagController.php │ │ │ │ │ ├── Mail/ │ │ │ │ │ │ ├── EmailController.php │ │ │ │ │ │ └── TagController.php │ │ │ │ │ ├── Products/ │ │ │ │ │ │ ├── ActivityController.php │ │ │ │ │ │ ├── ProductController.php │ │ │ │ │ │ └── TagController.php │ │ │ │ │ ├── Quote/ │ │ │ │ │ │ └── QuoteController.php │ │ │ │ │ ├── Settings/ │ │ │ │ │ │ ├── AttributeController.php │ │ │ │ │ │ ├── DataTransfer/ │ │ │ │ │ │ │ └── ImportController.php │ │ │ │ │ │ ├── EmailTemplateController.php │ │ │ │ │ │ ├── GroupController.php │ │ │ │ │ │ ├── LocationController.php │ │ │ │ │ │ ├── Marketing/ │ │ │ │ │ │ │ ├── CampaignsController.php │ │ │ │ │ │ │ └── EventController.php │ │ │ │ │ │ ├── PipelineController.php │ │ │ │ │ │ ├── RoleController.php │ │ │ │ │ │ ├── SettingController.php │ │ │ │ │ │ ├── SourceController.php │ │ │ │ │ │ ├── TagController.php │ │ │ │ │ │ ├── TypeController.php │ │ │ │ │ │ ├── UserController.php │ │ │ │ │ │ ├── Warehouse/ │ │ │ │ │ │ │ ├── ActivityController.php │ │ │ │ │ │ │ ├── TagController.php │ │ │ │ │ │ │ └── WarehouseController.php │ │ │ │ │ │ ├── WebFormController.php │ │ │ │ │ │ ├── WebhookController.php │ │ │ │ │ │ └── WorkflowController.php │ │ │ │ │ ├── TinyMCEController.php │ │ │ │ │ └── User/ │ │ │ │ │ ├── AccountController.php │ │ │ │ │ ├── ForgotPasswordController.php │ │ │ │ │ ├── ResetPasswordController.php │ │ │ │ │ └── SessionController.php │ │ │ │ ├── Middleware/ │ │ │ │ │ ├── Bouncer.php │ │ │ │ │ ├── Locale.php │ │ │ │ │ └── SanitizeUrl.php │ │ │ │ ├── Requests/ │ │ │ │ │ ├── AttributeForm.php │ │ │ │ │ ├── ConfigurationForm.php │ │ │ │ │ ├── LeadForm.php │ │ │ │ │ ├── MassDestroyRequest.php │ │ │ │ │ ├── MassUpdateRequest.php │ │ │ │ │ ├── PipelineForm.php │ │ │ │ │ └── UserForm.php │ │ │ │ ├── Resources/ │ │ │ │ │ ├── ActivityFileResource.php │ │ │ │ │ ├── ActivityParticipantResource.php │ │ │ │ │ ├── ActivityResource.php │ │ │ │ │ ├── EmailAttachmentResource.php │ │ │ │ │ ├── EmailResource.php │ │ │ │ │ ├── LeadResource.php │ │ │ │ │ ├── OrganizationResource.php │ │ │ │ │ ├── PersonResource.php │ │ │ │ │ ├── PipelineResource.php │ │ │ │ │ ├── ProductResource.php │ │ │ │ │ ├── QuoteResource.php │ │ │ │ │ ├── SourceResource.php │ │ │ │ │ ├── StageResource.php │ │ │ │ │ ├── TagResource.php │ │ │ │ │ ├── TypeResource.php │ │ │ │ │ └── UserResource.php │ │ │ │ └── helpers.php │ │ │ ├── Listeners/ │ │ │ │ ├── Activity.php │ │ │ │ ├── Lead.php │ │ │ │ └── Person.php │ │ │ ├── Notifications/ │ │ │ │ ├── Common.php │ │ │ │ └── User/ │ │ │ │ ├── Create.php │ │ │ │ ├── UserResetPassword.php │ │ │ │ └── UserUpdatePassword.php │ │ │ ├── Providers/ │ │ │ │ ├── AdminServiceProvider.php │ │ │ │ ├── EventServiceProvider.php │ │ │ │ └── ModuleServiceProvider.php │ │ │ ├── Requests/ │ │ │ │ └── WebhookRequest.php │ │ │ ├── Resources/ │ │ │ │ ├── assets/ │ │ │ │ │ ├── css/ │ │ │ │ │ │ └── app.css │ │ │ │ │ ├── js/ │ │ │ │ │ │ ├── app.js │ │ │ │ │ │ ├── chart.js │ │ │ │ │ │ ├── directives/ │ │ │ │ │ │ │ ├── debounce.js │ │ │ │ │ │ │ ├── dompurify.js │ │ │ │ │ │ │ └── tooltip.js │ │ │ │ │ │ └── plugins/ │ │ │ │ │ │ ├── admin.js │ │ │ │ │ │ ├── axios.js │ │ │ │ │ │ ├── createElement.js │ │ │ │ │ │ ├── draggable.js │ │ │ │ │ │ ├── emitter.js │ │ │ │ │ │ ├── flatpickr.js │ │ │ │ │ │ ├── vee-validate.js │ │ │ │ │ │ └── vue-cal.js │ │ │ │ │ └── locales/ │ │ │ │ │ ├── hi_IN.json │ │ │ │ │ └── sin.json │ │ │ │ ├── lang/ │ │ │ │ │ ├── ar/ │ │ │ │ │ │ └── app.php │ │ │ │ │ ├── en/ │ │ │ │ │ │ └── app.php │ │ │ │ │ ├── es/ │ │ │ │ │ │ └── app.php │ │ │ │ │ ├── fa/ │ │ │ │ │ │ └── app.php │ │ │ │ │ ├── pt_BR/ │ │ │ │ │ │ └── app.php │ │ │ │ │ ├── tr/ │ │ │ │ │ │ └── app.php │ │ │ │ │ └── vi/ │ │ │ │ │ └── app.php │ │ │ │ └── views/ │ │ │ │ ├── activities/ │ │ │ │ │ ├── datagrid/ │ │ │ │ │ │ └── is-done.blade.php │ │ │ │ │ ├── edit.blade.php │ │ │ │ │ └── index.blade.php │ │ │ │ ├── components/ │ │ │ │ │ ├── accordion/ │ │ │ │ │ │ └── index.blade.php │ │ │ │ │ ├── activities/ │ │ │ │ │ │ ├── actions/ │ │ │ │ │ │ │ ├── activity/ │ │ │ │ │ │ │ │ └── participants.blade.php │ │ │ │ │ │ │ ├── activity.blade.php │ │ │ │ │ │ │ ├── file.blade.php │ │ │ │ │ │ │ ├── mail.blade.php │ │ │ │ │ │ │ └── note.blade.php │ │ │ │ │ │ └── index.blade.php │ │ │ │ │ ├── attachments/ │ │ │ │ │ │ └── index.blade.php │ │ │ │ │ ├── attributes/ │ │ │ │ │ │ ├── edit/ │ │ │ │ │ │ │ ├── address.blade.php │ │ │ │ │ │ │ ├── boolean.blade.php │ │ │ │ │ │ │ ├── checkbox.blade.php │ │ │ │ │ │ │ ├── date.blade.php │ │ │ │ │ │ │ ├── datetime.blade.php │ │ │ │ │ │ │ ├── email.blade.php │ │ │ │ │ │ │ ├── file.blade.php │ │ │ │ │ │ │ ├── image.blade.php │ │ │ │ │ │ │ ├── index.blade.php │ │ │ │ │ │ │ ├── lookup.blade.php │ │ │ │ │ │ │ ├── multiselect.blade.php │ │ │ │ │ │ │ ├── phone.blade.php │ │ │ │ │ │ │ ├── price.blade.php │ │ │ │ │ │ │ ├── select.blade.php │ │ │ │ │ │ │ ├── text.blade.php │ │ │ │ │ │ │ └── textarea.blade.php │ │ │ │ │ │ ├── index.blade.php │ │ │ │ │ │ ├── view/ │ │ │ │ │ │ │ ├── address.blade.php │ │ │ │ │ │ │ ├── boolean.blade.php │ │ │ │ │ │ │ ├── checkbox.blade.php │ │ │ │ │ │ │ ├── date.blade.php │ │ │ │ │ │ │ ├── datetime.blade.php │ │ │ │ │ │ │ ├── email.blade.php │ │ │ │ │ │ │ ├── file.blade.php │ │ │ │ │ │ │ ├── image.blade.php │ │ │ │ │ │ │ ├── lookup.blade.php │ │ │ │ │ │ │ ├── multiselect.blade.php │ │ │ │ │ │ │ ├── phone.blade.php │ │ │ │ │ │ │ ├── price.blade.php │ │ │ │ │ │ │ ├── select.blade.php │ │ │ │ │ │ │ ├── text.blade.php │ │ │ │ │ │ │ └── textarea.blade.php │ │ │ │ │ │ └── view.blade.php │ │ │ │ │ ├── avatar/ │ │ │ │ │ │ └── index.blade.php │ │ │ │ │ ├── breadcrumbs/ │ │ │ │ │ │ └── index.blade.php │ │ │ │ │ ├── button/ │ │ │ │ │ │ └── index.blade.php │ │ │ │ │ ├── charts/ │ │ │ │ │ │ ├── bar.blade.php │ │ │ │ │ │ ├── doughnut.blade.php │ │ │ │ │ │ └── line.blade.php │ │ │ │ │ ├── datagrid/ │ │ │ │ │ │ ├── export/ │ │ │ │ │ │ │ ├── index.blade.php │ │ │ │ │ │ │ └── temp.blade.php │ │ │ │ │ │ ├── index.blade.php │ │ │ │ │ │ ├── table.blade.php │ │ │ │ │ │ ├── toolbar/ │ │ │ │ │ │ │ ├── filter.blade.php │ │ │ │ │ │ │ ├── mass-action.blade.php │ │ │ │ │ │ │ ├── pagination.blade.php │ │ │ │ │ │ │ └── search.blade.php │ │ │ │ │ │ └── toolbar.blade.php │ │ │ │ │ ├── drawer/ │ │ │ │ │ │ └── index.blade.php │ │ │ │ │ ├── dropdown/ │ │ │ │ │ │ ├── index.blade.php │ │ │ │ │ │ └── menu/ │ │ │ │ │ │ └── item.blade.php │ │ │ │ │ ├── example.blade.php │ │ │ │ │ ├── flash-group/ │ │ │ │ │ │ ├── index.blade.php │ │ │ │ │ │ └── item.blade.php │ │ │ │ │ ├── flat-picker/ │ │ │ │ │ │ ├── date.blade.php │ │ │ │ │ │ └── datetime.blade.php │ │ │ │ │ ├── form/ │ │ │ │ │ │ ├── control-group/ │ │ │ │ │ │ │ ├── control.blade.php │ │ │ │ │ │ │ ├── controls/ │ │ │ │ │ │ │ │ ├── inline/ │ │ │ │ │ │ │ │ │ ├── address.blade.php │ │ │ │ │ │ │ │ │ ├── boolean.blade.php │ │ │ │ │ │ │ │ │ ├── date.blade.php │ │ │ │ │ │ │ │ │ ├── datetime.blade.php │ │ │ │ │ │ │ │ │ ├── email.blade.php │ │ │ │ │ │ │ │ │ ├── file.blade.php │ │ │ │ │ │ │ │ │ ├── image.blade.php │ │ │ │ │ │ │ │ │ ├── lookup.blade.php │ │ │ │ │ │ │ │ │ ├── multiselect.blade.php │ │ │ │ │ │ │ │ │ ├── phone.blade.php │ │ │ │ │ │ │ │ │ ├── select.blade.php │ │ │ │ │ │ │ │ │ └── text.blade.php │ │ │ │ │ │ │ │ └── tags.blade.php │ │ │ │ │ │ │ ├── error.blade.php │ │ │ │ │ │ │ ├── index.blade.php │ │ │ │ │ │ │ └── label.blade.php │ │ │ │ │ │ └── index.blade.php │ │ │ │ │ ├── layouts/ │ │ │ │ │ │ ├── anonymous.blade.php │ │ │ │ │ │ ├── header/ │ │ │ │ │ │ │ ├── desktop/ │ │ │ │ │ │ │ │ └── mega-search.blade.php │ │ │ │ │ │ │ ├── index.blade.php │ │ │ │ │ │ │ ├── mobile/ │ │ │ │ │ │ │ │ └── mega-search.blade.php │ │ │ │ │ │ │ └── quick-creation.blade.php │ │ │ │ │ │ ├── index.blade.php │ │ │ │ │ │ ├── sidebar/ │ │ │ │ │ │ │ ├── desktop/ │ │ │ │ │ │ │ │ └── index.blade.php │ │ │ │ │ │ │ └── mobile/ │ │ │ │ │ │ │ └── index.blade.php │ │ │ │ │ │ └── tabs.blade.php │ │ │ │ │ ├── lookup/ │ │ │ │ │ │ └── index.blade.php │ │ │ │ │ ├── media/ │ │ │ │ │ │ ├── images.blade.php │ │ │ │ │ │ └── videos.blade.php │ │ │ │ │ ├── modal/ │ │ │ │ │ │ ├── confirm.blade.php │ │ │ │ │ │ └── index.blade.php │ │ │ │ │ ├── shimmer/ │ │ │ │ │ │ ├── accordion/ │ │ │ │ │ │ │ └── index.blade.php │ │ │ │ │ │ ├── activities/ │ │ │ │ │ │ │ └── index.blade.php │ │ │ │ │ │ ├── charts/ │ │ │ │ │ │ │ └── bar.blade.php │ │ │ │ │ │ ├── common/ │ │ │ │ │ │ │ └── address.blade.php │ │ │ │ │ │ ├── dashboard/ │ │ │ │ │ │ │ └── index/ │ │ │ │ │ │ │ ├── open-leads-by-states.blade.php │ │ │ │ │ │ │ ├── over-all.blade.php │ │ │ │ │ │ │ ├── revenue-by-sources.blade.php │ │ │ │ │ │ │ ├── revenue-by-types.blade.php │ │ │ │ │ │ │ ├── revenue.blade.php │ │ │ │ │ │ │ ├── top-persons.blade.php │ │ │ │ │ │ │ ├── top-selling-products.blade.php │ │ │ │ │ │ │ └── total-leads.blade.php │ │ │ │ │ │ ├── datagrid/ │ │ │ │ │ │ │ ├── index.blade.php │ │ │ │ │ │ │ ├── table/ │ │ │ │ │ │ │ │ ├── body.blade.php │ │ │ │ │ │ │ │ └── head.blade.php │ │ │ │ │ │ │ ├── toolbar/ │ │ │ │ │ │ │ │ ├── filter.blade.php │ │ │ │ │ │ │ │ ├── pagination.blade.php │ │ │ │ │ │ │ │ └── search.blade.php │ │ │ │ │ │ │ └── toolbar.blade.php │ │ │ │ │ │ ├── header/ │ │ │ │ │ │ │ └── mega-search/ │ │ │ │ │ │ │ ├── configurations.blade.php │ │ │ │ │ │ │ ├── leads.blade.php │ │ │ │ │ │ │ ├── persons.blade.php │ │ │ │ │ │ │ ├── products.blade.php │ │ │ │ │ │ │ ├── quotes.blade.php │ │ │ │ │ │ │ └── settings.blade.php │ │ │ │ │ │ ├── image/ │ │ │ │ │ │ │ └── index.blade.php │ │ │ │ │ │ ├── leads/ │ │ │ │ │ │ │ ├── datagrid.blade.php │ │ │ │ │ │ │ ├── index/ │ │ │ │ │ │ │ │ ├── kanban/ │ │ │ │ │ │ │ │ │ └── toolbar.blade.php │ │ │ │ │ │ │ │ └── kanban.blade.php │ │ │ │ │ │ │ └── view/ │ │ │ │ │ │ │ ├── mail/ │ │ │ │ │ │ │ │ └── index.blade.php │ │ │ │ │ │ │ └── stages.blade.php │ │ │ │ │ │ ├── mail/ │ │ │ │ │ │ │ └── datagrid/ │ │ │ │ │ │ │ ├── index.blade.php │ │ │ │ │ │ │ └── table/ │ │ │ │ │ │ │ ├── body.blade.php │ │ │ │ │ │ │ └── head.blade.php │ │ │ │ │ │ ├── person/ │ │ │ │ │ │ │ └── view/ │ │ │ │ │ │ │ ├── activities/ │ │ │ │ │ │ │ │ └── index.blade.php │ │ │ │ │ │ │ ├── stages.blade.php │ │ │ │ │ │ │ └── tags.blade.php │ │ │ │ │ │ ├── pipelines/ │ │ │ │ │ │ │ └── kanban.blade.php │ │ │ │ │ │ ├── quotes/ │ │ │ │ │ │ │ └── index.blade.php │ │ │ │ │ │ ├── settings/ │ │ │ │ │ │ │ ├── attributes.blade.php │ │ │ │ │ │ │ └── web-forms/ │ │ │ │ │ │ │ ├── body.blade.php │ │ │ │ │ │ │ ├── head.blade.php │ │ │ │ │ │ │ └── index.blade.php │ │ │ │ │ │ ├── tabs/ │ │ │ │ │ │ │ └── index.blade.php │ │ │ │ │ │ ├── tags/ │ │ │ │ │ │ │ └── index.blade.php │ │ │ │ │ │ ├── tinymce/ │ │ │ │ │ │ │ └── index.blade.php │ │ │ │ │ │ └── tree/ │ │ │ │ │ │ └── index.blade.php │ │ │ │ │ ├── spinner/ │ │ │ │ │ │ └── index.blade.php │ │ │ │ │ ├── table/ │ │ │ │ │ │ ├── index.blade.php │ │ │ │ │ │ ├── tbody/ │ │ │ │ │ │ │ ├── index.blade.php │ │ │ │ │ │ │ └── tr.blade.php │ │ │ │ │ │ ├── td.blade.php │ │ │ │ │ │ ├── th.blade.php │ │ │ │ │ │ └── thead/ │ │ │ │ │ │ ├── index.blade.php │ │ │ │ │ │ └── tr.blade.php │ │ │ │ │ ├── tabs/ │ │ │ │ │ │ ├── index.blade.php │ │ │ │ │ │ └── item.blade.php │ │ │ │ │ ├── tags/ │ │ │ │ │ │ └── index.blade.php │ │ │ │ │ ├── tinymce/ │ │ │ │ │ │ └── index.blade.php │ │ │ │ │ └── tree/ │ │ │ │ │ ├── checkbox.blade.php │ │ │ │ │ ├── radio.blade.php │ │ │ │ │ └── view.blade.php │ │ │ │ ├── configuration/ │ │ │ │ │ ├── edit.blade.php │ │ │ │ │ ├── field-type.blade.php │ │ │ │ │ └── index.blade.php │ │ │ │ ├── contacts/ │ │ │ │ │ ├── organizations/ │ │ │ │ │ │ ├── create.blade.php │ │ │ │ │ │ ├── edit.blade.php │ │ │ │ │ │ └── index.blade.php │ │ │ │ │ └── persons/ │ │ │ │ │ ├── create.blade.php │ │ │ │ │ ├── edit.blade.php │ │ │ │ │ ├── index.blade.php │ │ │ │ │ ├── view/ │ │ │ │ │ │ ├── attributes.blade.php │ │ │ │ │ │ └── organization.blade.php │ │ │ │ │ └── view.blade.php │ │ │ │ ├── dashboard/ │ │ │ │ │ ├── index/ │ │ │ │ │ │ ├── open-leads-by-states.blade.php │ │ │ │ │ │ ├── over-all.blade.php │ │ │ │ │ │ ├── revenue-by-sources.blade.php │ │ │ │ │ │ ├── revenue-by-types.blade.php │ │ │ │ │ │ ├── revenue.blade.php │ │ │ │ │ │ ├── top-persons.blade.php │ │ │ │ │ │ ├── top-selling-products.blade.php │ │ │ │ │ │ └── total-leads.blade.php │ │ │ │ │ └── index.blade.php │ │ │ │ ├── emails/ │ │ │ │ │ ├── common/ │ │ │ │ │ │ └── index.blade.php │ │ │ │ │ ├── layout.blade.php │ │ │ │ │ └── users/ │ │ │ │ │ ├── create.blade.php │ │ │ │ │ └── forget-password.blade.php │ │ │ │ ├── errors/ │ │ │ │ │ └── index.blade.php │ │ │ │ ├── leads/ │ │ │ │ │ ├── common/ │ │ │ │ │ │ ├── contact.blade.php │ │ │ │ │ │ └── products.blade.php │ │ │ │ │ ├── create.blade.php │ │ │ │ │ ├── edit.blade.php │ │ │ │ │ ├── index/ │ │ │ │ │ │ ├── kanban/ │ │ │ │ │ │ │ ├── filter.blade.php │ │ │ │ │ │ │ ├── search.blade.php │ │ │ │ │ │ │ └── toolbar.blade.php │ │ │ │ │ │ ├── kanban.blade.php │ │ │ │ │ │ ├── table.blade.php │ │ │ │ │ │ ├── upload.blade.php │ │ │ │ │ │ └── view-switcher.blade.php │ │ │ │ │ ├── index.blade.php │ │ │ │ │ ├── view/ │ │ │ │ │ │ ├── attributes.blade.php │ │ │ │ │ │ ├── person.blade.php │ │ │ │ │ │ ├── products.blade.php │ │ │ │ │ │ ├── quotes.blade.php │ │ │ │ │ │ └── stages.blade.php │ │ │ │ │ └── view.blade.php │ │ │ │ ├── mail/ │ │ │ │ │ ├── index.blade.php │ │ │ │ │ └── view.blade.php │ │ │ │ ├── partials/ │ │ │ │ │ └── breadcrumbs.blade.php │ │ │ │ ├── products/ │ │ │ │ │ ├── create.blade.php │ │ │ │ │ ├── edit.blade.php │ │ │ │ │ ├── index.blade.php │ │ │ │ │ ├── view/ │ │ │ │ │ │ ├── attributes.blade.php │ │ │ │ │ │ └── inventory.blade.php │ │ │ │ │ └── view.blade.php │ │ │ │ ├── quotes/ │ │ │ │ │ ├── create.blade.php │ │ │ │ │ ├── edit.blade.php │ │ │ │ │ ├── index.blade.php │ │ │ │ │ └── pdf.blade.php │ │ │ │ ├── sessions/ │ │ │ │ │ ├── forgot-password.blade.php │ │ │ │ │ ├── login.blade.php │ │ │ │ │ └── reset-password.blade.php │ │ │ │ ├── settings/ │ │ │ │ │ ├── attributes/ │ │ │ │ │ │ ├── create.blade.php │ │ │ │ │ │ ├── edit.blade.php │ │ │ │ │ │ └── index.blade.php │ │ │ │ │ ├── data-transfer/ │ │ │ │ │ │ └── imports/ │ │ │ │ │ │ ├── create.blade.php │ │ │ │ │ │ ├── edit.blade.php │ │ │ │ │ │ ├── import.blade.php │ │ │ │ │ │ └── index.blade.php │ │ │ │ │ ├── email-templates/ │ │ │ │ │ │ ├── create.blade.php │ │ │ │ │ │ ├── edit.blade.php │ │ │ │ │ │ └── index.blade.php │ │ │ │ │ ├── groups/ │ │ │ │ │ │ └── index.blade.php │ │ │ │ │ ├── index.blade.php │ │ │ │ │ ├── marketing/ │ │ │ │ │ │ ├── campaigns/ │ │ │ │ │ │ │ └── index.blade.php │ │ │ │ │ │ └── events/ │ │ │ │ │ │ └── index.blade.php │ │ │ │ │ ├── pipelines/ │ │ │ │ │ │ ├── create.blade.php │ │ │ │ │ │ ├── edit.blade.php │ │ │ │ │ │ └── index.blade.php │ │ │ │ │ ├── roles/ │ │ │ │ │ │ ├── create.blade.php │ │ │ │ │ │ ├── edit.blade.php │ │ │ │ │ │ └── index.blade.php │ │ │ │ │ ├── sources/ │ │ │ │ │ │ └── index.blade.php │ │ │ │ │ ├── tags/ │ │ │ │ │ │ └── index.blade.php │ │ │ │ │ ├── types/ │ │ │ │ │ │ └── index.blade.php │ │ │ │ │ ├── users/ │ │ │ │ │ │ └── index.blade.php │ │ │ │ │ ├── warehouses/ │ │ │ │ │ │ ├── create.blade.php │ │ │ │ │ │ ├── edit.blade.php │ │ │ │ │ │ ├── index.blade.php │ │ │ │ │ │ ├── view/ │ │ │ │ │ │ │ ├── contact-information.blade.php │ │ │ │ │ │ │ ├── general-information.blade.php │ │ │ │ │ │ │ └── locations.blade.php │ │ │ │ │ │ └── view.blade.php │ │ │ │ │ ├── web-forms/ │ │ │ │ │ │ ├── create.blade.php │ │ │ │ │ │ ├── edit.blade.php │ │ │ │ │ │ └── index.blade.php │ │ │ │ │ ├── webhook/ │ │ │ │ │ │ ├── create.blade.php │ │ │ │ │ │ ├── edit.blade.php │ │ │ │ │ │ └── index.blade.php │ │ │ │ │ └── workflows/ │ │ │ │ │ ├── create.blade.php │ │ │ │ │ ├── edit.blade.php │ │ │ │ │ └── index.blade.php │ │ │ │ └── user/ │ │ │ │ └── account/ │ │ │ │ └── edit.blade.php │ │ │ ├── Routes/ │ │ │ │ ├── Admin/ │ │ │ │ │ ├── activities-routes.php │ │ │ │ │ ├── auth-routes.php │ │ │ │ │ ├── configuration-routes.php │ │ │ │ │ ├── contacts-routes.php │ │ │ │ │ ├── leads-routes.php │ │ │ │ │ ├── mail-routes.php │ │ │ │ │ ├── products-routes.php │ │ │ │ │ ├── quote-routes.php │ │ │ │ │ ├── rest-routes.php │ │ │ │ │ ├── settings-routes.php │ │ │ │ │ └── web.php │ │ │ │ └── Front/ │ │ │ │ └── web.php │ │ │ └── Traits/ │ │ │ └── ProvideDropdownOptions.php │ │ ├── tailwind.config.js │ │ ├── tests/ │ │ │ └── e2e-pw/ │ │ │ ├── .gitignore │ │ │ ├── playwright.config.ts │ │ │ ├── setup.ts │ │ │ ├── tests/ │ │ │ │ ├── auth.spec.ts │ │ │ │ ├── contacts/ │ │ │ │ │ ├── organization.spec.ts │ │ │ │ │ └── person.spec.ts │ │ │ │ ├── lang/ │ │ │ │ │ └── lang.spec.ts │ │ │ │ ├── lead.spec.ts │ │ │ │ ├── mail/ │ │ │ │ │ ├── draft.spec.ts │ │ │ │ │ ├── inbox.spec.ts │ │ │ │ │ ├── outbox.spec.ts │ │ │ │ │ ├── sent.spec.ts │ │ │ │ │ └── trash.spec.ts │ │ │ │ ├── product.spec.ts │ │ │ │ ├── quotes.spec.ts │ │ │ │ └── settings/ │ │ │ │ ├── automation/ │ │ │ │ │ └── events.spec.ts │ │ │ │ ├── lead/ │ │ │ │ │ └── types.spec.ts │ │ │ │ ├── user/ │ │ │ │ │ ├── groups.spec.ts │ │ │ │ │ └── users.spec.ts │ │ │ │ └── warehouses/ │ │ │ │ └── warehouse.spec.ts │ │ │ └── utils/ │ │ │ ├── components.ts │ │ │ └── faker.ts │ │ └── vite.config.js │ ├── Attribute/ │ │ ├── composer.json │ │ └── src/ │ │ ├── Config/ │ │ │ └── attribute_lookups.php │ │ ├── Contracts/ │ │ │ ├── Attribute.php │ │ │ ├── AttributeOption.php │ │ │ └── AttributeValue.php │ │ ├── Database/ │ │ │ └── Migrations/ │ │ │ ├── 2021_04_02_080709_create_attributes_table.php │ │ │ ├── 2021_04_02_080837_create_attribute_options_table.php │ │ │ ├── 2021_04_06_122751_create_attribute_values_table.php │ │ │ └── 2025_07_02_191710_alter_attribute_values_table.php │ │ ├── Models/ │ │ │ ├── Attribute.php │ │ │ ├── AttributeOption.php │ │ │ ├── AttributeOptionProxy.php │ │ │ ├── AttributeProxy.php │ │ │ ├── AttributeValue.php │ │ │ └── AttributeValueProxy.php │ │ ├── Providers/ │ │ │ ├── AttributeServiceProvider.php │ │ │ └── ModuleServiceProvider.php │ │ ├── Repositories/ │ │ │ ├── AttributeOptionRepository.php │ │ │ ├── AttributeRepository.php │ │ │ └── AttributeValueRepository.php │ │ └── Traits/ │ │ └── CustomAttribute.php │ ├── Automation/ │ │ └── src/ │ │ ├── Config/ │ │ │ └── workflows.php │ │ ├── Contracts/ │ │ │ ├── Webhook.php │ │ │ └── Workflow.php │ │ ├── Database/ │ │ │ └── Migrations/ │ │ │ ├── 2021_08_26_133538_create_workflows_table.php │ │ │ └── 2024_07_24_150821_create_webhooks_table.php │ │ ├── Helpers/ │ │ │ ├── Entity/ │ │ │ │ ├── AbstractEntity.php │ │ │ │ ├── Activity.php │ │ │ │ ├── Lead.php │ │ │ │ ├── Person.php │ │ │ │ └── Quote.php │ │ │ ├── Entity.php │ │ │ └── Validator.php │ │ ├── Listeners/ │ │ │ └── Entity.php │ │ ├── Models/ │ │ │ ├── Webhook.php │ │ │ ├── WebhookProxy.php │ │ │ ├── Workflow.php │ │ │ └── WorkflowProxy.php │ │ ├── Providers/ │ │ │ ├── ModuleServiceProvider.php │ │ │ └── WorkflowServiceProvider.php │ │ ├── Repositories/ │ │ │ ├── WebhookRepository.php │ │ │ └── WorkflowRepository.php │ │ └── Services/ │ │ └── WebhookService.php │ ├── Contact/ │ │ ├── composer.json │ │ └── src/ │ │ ├── Contracts/ │ │ │ ├── Organization.php │ │ │ └── Person.php │ │ ├── Database/ │ │ │ ├── Factories/ │ │ │ │ └── PersonFactory.php │ │ │ └── Migrations/ │ │ │ ├── 2021_04_09_051326_create_organizations_table.php │ │ │ ├── 2021_04_09_065617_create_persons_table.php │ │ │ ├── 2021_09_22_194103_add_unique_index_to_name_in_organizations_table.php │ │ │ ├── 2024_07_31_092951_add_job_title_in_persons_table.php │ │ │ ├── 2024_08_06_145943_create_person_tags_table.php │ │ │ ├── 2024_08_06_161212_create_person_activities_table.php │ │ │ ├── 2024_08_14_102116_add_user_id_column_in_persons_table.php │ │ │ ├── 2024_08_14_102136_add_user_id_column_in_organizations_table.php │ │ │ ├── 2024_09_09_112201_add_unique_id_to_person_table.php │ │ │ └── 2025_03_19_132236_update_organization_id_column_in_persons_table.php │ │ ├── Models/ │ │ │ ├── Organization.php │ │ │ ├── OrganizationProxy.php │ │ │ ├── Person.php │ │ │ └── PersonProxy.php │ │ ├── Providers/ │ │ │ ├── ContactServiceProvider.php │ │ │ └── ModuleServiceProvider.php │ │ └── Repositories/ │ │ ├── OrganizationRepository.php │ │ └── PersonRepository.php │ ├── Core/ │ │ ├── composer.json │ │ └── src/ │ │ ├── Acl/ │ │ │ └── AclItem.php │ │ ├── Acl.php │ │ ├── Config/ │ │ │ ├── concord.php │ │ │ ├── cors.php │ │ │ └── sanctum.php │ │ ├── Console/ │ │ │ └── Commands/ │ │ │ └── Version.php │ │ ├── Contracts/ │ │ │ ├── CoreConfig.php │ │ │ ├── Country.php │ │ │ ├── CountryState.php │ │ │ └── Validations/ │ │ │ ├── Code.php │ │ │ └── Decimal.php │ │ ├── Core.php │ │ ├── Database/ │ │ │ └── Migrations/ │ │ │ ├── 2021_03_12_060658_create_core_config_table.php │ │ │ ├── 2021_04_12_173232_create_countries_table.php │ │ │ ├── 2021_04_12_173344_create_country_states_table.php │ │ │ └── 2025_01_29_133500_update_text_column_type_in_core_config_table.php │ │ ├── Eloquent/ │ │ │ ├── Repository.php │ │ │ └── TranslatableModel.php │ │ ├── Exceptions/ │ │ │ └── ViterNotFound.php │ │ ├── Facades/ │ │ │ ├── Acl.php │ │ │ ├── Core.php │ │ │ ├── Menu.php │ │ │ └── SystemConfig.php │ │ ├── Helpers/ │ │ │ └── Helper.php │ │ ├── Http/ │ │ │ └── helpers.php │ │ ├── Menu/ │ │ │ └── MenuItem.php │ │ ├── Menu.php │ │ ├── Models/ │ │ │ ├── CoreConfig.php │ │ │ ├── CoreConfigProxy.php │ │ │ ├── Country.php │ │ │ ├── CountryProxy.php │ │ │ ├── CountryState.php │ │ │ └── CountryStateProxy.php │ │ ├── Providers/ │ │ │ ├── BaseModuleServiceProvider.php │ │ │ ├── CoreServiceProvider.php │ │ │ └── ModuleServiceProvider.php │ │ ├── Repositories/ │ │ │ ├── CoreConfigRepository.php │ │ │ ├── CountryRepository.php │ │ │ └── CountryStateRepository.php │ │ ├── Resources/ │ │ │ └── lang/ │ │ │ ├── ar/ │ │ │ │ └── app.php │ │ │ ├── en/ │ │ │ │ └── app.php │ │ │ ├── es/ │ │ │ │ └── app.php │ │ │ ├── fa/ │ │ │ │ └── app.php │ │ │ ├── pt_BR/ │ │ │ │ └── app.php │ │ │ ├── tr/ │ │ │ │ └── app.php │ │ │ └── vi/ │ │ │ └── app.php │ │ ├── SystemConfig/ │ │ │ ├── Item.php │ │ │ └── ItemField.php │ │ ├── SystemConfig.php │ │ ├── Traits/ │ │ │ ├── PDFHandler.php │ │ │ └── Sanitizer.php │ │ ├── ViewRenderEventManager.php │ │ └── Vite.php │ ├── DataGrid/ │ │ └── src/ │ │ ├── Action.php │ │ ├── Column.php │ │ ├── ColumnTypes/ │ │ │ ├── Aggregate.php │ │ │ ├── Boolean.php │ │ │ ├── Date.php │ │ │ ├── Datetime.php │ │ │ ├── Decimal.php │ │ │ ├── Integer.php │ │ │ └── Text.php │ │ ├── Contracts/ │ │ │ └── SavedFilter.php │ │ ├── DataGrid.php │ │ ├── Database/ │ │ │ └── Migrations/ │ │ │ └── 2024_05_10_152848_create_saved_filters_table.php │ │ ├── Enums/ │ │ │ ├── ColumnTypeEnum.php │ │ │ ├── DateRangeOptionEnum.php │ │ │ └── FilterTypeEnum.php │ │ ├── Exceptions/ │ │ │ ├── InvalidColumnException.php │ │ │ ├── InvalidColumnTypeException.php │ │ │ └── InvalidDataGridException.php │ │ ├── Exports/ │ │ │ └── DataGridExport.php │ │ ├── Http/ │ │ │ └── helpers.php │ │ ├── MassAction.php │ │ ├── Models/ │ │ │ ├── SavedFilter.php │ │ │ └── SavedFilterProxy.php │ │ ├── Providers/ │ │ │ ├── DataGridServiceProvider.php │ │ │ └── ModuleServiceProvider.php │ │ └── Repositories/ │ │ └── SavedFilterRepository.php │ ├── DataTransfer/ │ │ └── src/ │ │ ├── Config/ │ │ │ └── importers.php │ │ ├── Contracts/ │ │ │ ├── Import.php │ │ │ └── ImportBatch.php │ │ ├── Database/ │ │ │ └── Migrations/ │ │ │ ├── 2024_01_11_154640_create_imports_table.php │ │ │ └── 2024_01_11_154741_create_import_batches_table.php │ │ ├── Helpers/ │ │ │ ├── Error.php │ │ │ ├── Import.php │ │ │ ├── Importers/ │ │ │ │ ├── AbstractImporter.php │ │ │ │ ├── Leads/ │ │ │ │ │ ├── Importer.php │ │ │ │ │ └── Storage.php │ │ │ │ ├── Persons/ │ │ │ │ │ ├── Importer.php │ │ │ │ │ └── Storage.php │ │ │ │ └── Products/ │ │ │ │ ├── Importer.php │ │ │ │ └── SKUStorage.php │ │ │ └── Sources/ │ │ │ ├── AbstractSource.php │ │ │ ├── CSV.php │ │ │ └── Excel.php │ │ ├── Jobs/ │ │ │ └── Import/ │ │ │ ├── Completed.php │ │ │ ├── ImportBatch.php │ │ │ ├── IndexBatch.php │ │ │ ├── Indexing.php │ │ │ ├── LinkBatch.php │ │ │ └── Linking.php │ │ ├── Models/ │ │ │ ├── Import.php │ │ │ ├── ImportBatch.php │ │ │ ├── ImportBatchProxy.php │ │ │ └── ImportProxy.php │ │ ├── Providers/ │ │ │ ├── DataTransferServiceProvider.php │ │ │ └── ModuleServiceProvider.php │ │ ├── Repositories/ │ │ │ ├── ImportBatchRepository.php │ │ │ └── ImportRepository.php │ │ └── Resources/ │ │ └── lang/ │ │ ├── ar/ │ │ │ └── ar.php │ │ ├── en/ │ │ │ └── app.php │ │ ├── es/ │ │ │ └── app.php │ │ ├── fa/ │ │ │ └── app.php │ │ ├── pt_BR/ │ │ │ └── app.php │ │ └── tr/ │ │ └── app.php │ ├── Email/ │ │ ├── composer.json │ │ └── src/ │ │ ├── Console/ │ │ │ └── Commands/ │ │ │ └── ProcessInboundEmails.php │ │ ├── Contracts/ │ │ │ ├── Attachment.php │ │ │ └── Email.php │ │ ├── Database/ │ │ │ └── Migrations/ │ │ │ ├── 2021_05_24_075618_create_emails_table.php │ │ │ ├── 2021_05_25_072700_create_email_attachments_table.php │ │ │ └── 2024_08_27_091619_create_email_tags_table.php │ │ ├── Enums/ │ │ │ └── SupportedFolderEnum.php │ │ ├── Helpers/ │ │ │ ├── Attachment.php │ │ │ ├── Charset.php │ │ │ ├── Contracts/ │ │ │ │ └── CharsetManager.php │ │ │ ├── HtmlFilter.php │ │ │ └── Parser.php │ │ ├── InboundEmailProcessor/ │ │ │ ├── Contracts/ │ │ │ │ └── InboundEmailProcessor.php │ │ │ ├── SendgridEmailProcessor.php │ │ │ └── WebklexImapEmailProcessor.php │ │ ├── Mails/ │ │ │ └── Email.php │ │ ├── Models/ │ │ │ ├── Attachment.php │ │ │ ├── AttachmentProxy.php │ │ │ ├── Email.php │ │ │ └── EmailProxy.php │ │ ├── Providers/ │ │ │ ├── EmailServiceProvider.php │ │ │ └── ModuleServiceProvider.php │ │ └── Repositories/ │ │ ├── AttachmentRepository.php │ │ └── EmailRepository.php │ ├── EmailTemplate/ │ │ └── src/ │ │ ├── Contracts/ │ │ │ └── EmailTemplate.php │ │ ├── Database/ │ │ │ └── Migrations/ │ │ │ ├── 2021_09_03_172713_create_email_templates_table.php │ │ │ └── 2025_07_09_133553_alter_email_templates_table.php │ │ ├── Models/ │ │ │ ├── EmailTemplate.php │ │ │ └── EmailTemplateProxy.php │ │ ├── Providers/ │ │ │ ├── EmailTemplateServiceProvider.php │ │ │ └── ModuleServiceProvider.php │ │ └── Repositories/ │ │ └── EmailTemplateRepository.php │ ├── Installer/ │ │ ├── .gitignore │ │ ├── composer.json │ │ ├── package.json │ │ ├── postcss.config.js │ │ ├── src/ │ │ │ ├── Console/ │ │ │ │ └── Commands/ │ │ │ │ └── Installer.php │ │ │ ├── Data/ │ │ │ │ ├── countries.json │ │ │ │ └── states.json │ │ │ ├── Database/ │ │ │ │ ├── Migrations/ │ │ │ │ │ ├── 2024_06_24_174241_insert_warehouse_attributes_in_attributes_table.php │ │ │ │ │ ├── 2024_07_31_093603_add_organization_sales_owner_attribute_in_attributes_table.php │ │ │ │ │ ├── 2024_07_31_093605_add_person_job_title_attribute_in_attributes_table.php │ │ │ │ │ ├── 2024_07_31_093605_add_person_sales_owner_attribute_in_attributes_table.php │ │ │ │ │ └── 2024_08_21_153011_add_leads_stage_and_pipeline_attributes.php │ │ │ │ └── Seeders/ │ │ │ │ ├── Attribute/ │ │ │ │ │ ├── AttributeSeeder.php │ │ │ │ │ └── DatabaseSeeder.php │ │ │ │ ├── Core/ │ │ │ │ │ ├── CountriesSeeder.php │ │ │ │ │ ├── DatabaseSeeder.php │ │ │ │ │ └── StatesSeeder.php │ │ │ │ ├── DatabaseSeeder.php │ │ │ │ ├── EmailTemplate/ │ │ │ │ │ ├── DatabaseSeeder.php │ │ │ │ │ └── EmailTemplateSeeder.php │ │ │ │ ├── Lead/ │ │ │ │ │ ├── DatabaseSeeder.php │ │ │ │ │ ├── PipelineSeeder.php │ │ │ │ │ ├── SourceSeeder.php │ │ │ │ │ └── TypeSeeder.php │ │ │ │ ├── User/ │ │ │ │ │ ├── DatabaseSeeder.php │ │ │ │ │ ├── RoleSeeder.php │ │ │ │ │ └── UserSeeder.php │ │ │ │ └── Workflow/ │ │ │ │ ├── DatabaseSeeder.php │ │ │ │ └── WorkflowSeeder.php │ │ │ ├── Events/ │ │ │ │ └── ComposerEvents.php │ │ │ ├── Helpers/ │ │ │ │ ├── DatabaseManager.php │ │ │ │ ├── EnvironmentManager.php │ │ │ │ └── ServerRequirements.php │ │ │ ├── Http/ │ │ │ │ ├── Controllers/ │ │ │ │ │ ├── Controller.php │ │ │ │ │ ├── ImageCacheController.php │ │ │ │ │ └── InstallerController.php │ │ │ │ └── Middleware/ │ │ │ │ ├── CanInstall.php │ │ │ │ └── Locale.php │ │ │ ├── Listeners/ │ │ │ │ └── Installer.php │ │ │ ├── Providers/ │ │ │ │ └── InstallerServiceProvider.php │ │ │ ├── Resources/ │ │ │ │ ├── assets/ │ │ │ │ │ ├── css/ │ │ │ │ │ │ └── app.css │ │ │ │ │ └── js/ │ │ │ │ │ ├── app.js │ │ │ │ │ └── plugins/ │ │ │ │ │ └── axios.js │ │ │ │ ├── lang/ │ │ │ │ │ ├── ar/ │ │ │ │ │ │ └── app.php │ │ │ │ │ ├── en/ │ │ │ │ │ │ └── app.php │ │ │ │ │ ├── es/ │ │ │ │ │ │ └── app.php │ │ │ │ │ ├── fa/ │ │ │ │ │ │ └── app.php │ │ │ │ │ ├── pt_BR/ │ │ │ │ │ │ └── app.php │ │ │ │ │ ├── tr/ │ │ │ │ │ │ └── app.php │ │ │ │ │ └── vi/ │ │ │ │ │ └── app.php │ │ │ │ └── views/ │ │ │ │ ├── components/ │ │ │ │ │ ├── button/ │ │ │ │ │ │ └── index.blade.php │ │ │ │ │ └── form/ │ │ │ │ │ ├── control-group/ │ │ │ │ │ │ ├── control.blade.php │ │ │ │ │ │ ├── error.blade.php │ │ │ │ │ │ ├── index.blade.php │ │ │ │ │ │ └── label.blade.php │ │ │ │ │ └── index.blade.php │ │ │ │ └── installer/ │ │ │ │ └── index.blade.php │ │ │ ├── Routes/ │ │ │ │ └── web.php │ │ │ └── Templates/ │ │ │ └── on-boarding.php │ │ ├── tailwind.config.js │ │ └── vite.config.js │ ├── Lead/ │ │ ├── composer.json │ │ └── src/ │ │ ├── Contracts/ │ │ │ ├── Lead.php │ │ │ ├── Pipeline.php │ │ │ ├── Product.php │ │ │ ├── Source.php │ │ │ ├── Stage.php │ │ │ └── Type.php │ │ ├── Database/ │ │ │ └── Migrations/ │ │ │ ├── 2021_04_21_172825_create_lead_sources_table.php │ │ │ ├── 2021_04_21_172847_create_lead_types_table.php │ │ │ ├── 2021_04_22_153258_create_lead_stages_table.php │ │ │ ├── 2021_04_22_155706_create_lead_pipelines_table.php │ │ │ ├── 2021_04_22_155838_create_lead_pipeline_stages_table.php │ │ │ ├── 2021_04_22_164215_create_leads_table.php │ │ │ ├── 2021_04_22_171805_create_lead_products_table.php │ │ │ ├── 2021_05_12_150329_create_lead_activities_table.php │ │ │ ├── 2021_05_20_141240_create_lead_tags_table.php │ │ │ ├── 2021_07_02_201822_create_lead_quotes_table.php │ │ │ ├── 2021_09_23_221138_add_column_expected_close_date_in_leads_table.php │ │ │ ├── 2021_09_30_135857_add_column_rotten_days_in_lead_pipelines_table.php │ │ │ ├── 2021_09_30_154222_alter_lead_pipeline_stages_table.php │ │ │ ├── 2021_09_30_161722_alter_leads_table.php │ │ │ ├── 2021_09_30_183825_change_user_id_to_nullable_in_leads_table.php │ │ │ ├── 2021_11_11_180804_change_lead_pipeline_stage_id_constraint_in_leads_table.php │ │ │ ├── 2024_11_29_120302_modify_foreign_keys_in_leads_table.php │ │ │ └── 2025_07_01_133612_alter_lead_pipelines_table.php │ │ ├── Helpers/ │ │ │ └── MagicAI.php │ │ ├── Models/ │ │ │ ├── Lead.php │ │ │ ├── LeadProxy.php │ │ │ ├── Pipeline.php │ │ │ ├── PipelineProxy.php │ │ │ ├── Product.php │ │ │ ├── ProductProxy.php │ │ │ ├── Source.php │ │ │ ├── SourceProxy.php │ │ │ ├── Stage.php │ │ │ ├── StageProxy.php │ │ │ ├── Type.php │ │ │ └── TypeProxy.php │ │ ├── Providers/ │ │ │ ├── LeadServiceProvider.php │ │ │ └── ModuleServiceProvider.php │ │ ├── Repositories/ │ │ │ ├── LeadRepository.php │ │ │ ├── PipelineRepository.php │ │ │ ├── ProductRepository.php │ │ │ ├── SourceRepository.php │ │ │ ├── StageRepository.php │ │ │ └── TypeRepository.php │ │ └── Services/ │ │ └── MagicAIService.php │ ├── Marketing/ │ │ └── src/ │ │ ├── Console/ │ │ │ └── Commands/ │ │ │ └── CampaignCommand.php │ │ ├── Contracts/ │ │ │ ├── Campaign.php │ │ │ └── Event.php │ │ ├── Database/ │ │ │ └── Migrations/ │ │ │ ├── 2024_10_29_044744_create_marketing_events_table.php │ │ │ └── 2024_11_04_122500_create_marketing_campaigns_table.php │ │ ├── Helpers/ │ │ │ └── Campaign.php │ │ ├── Mail/ │ │ │ └── CampaignMail.php │ │ ├── Models/ │ │ │ ├── Campaign.php │ │ │ ├── CampaignProxy.php │ │ │ ├── Event.php │ │ │ └── EventProxy.php │ │ ├── Providers/ │ │ │ ├── MarketingServiceProvider.php │ │ │ └── ModuleServiceProvider.php │ │ └── Repositories/ │ │ ├── CampaignRepository.php │ │ └── EventRepository.php │ ├── Product/ │ │ ├── composer.json │ │ └── src/ │ │ ├── Contracts/ │ │ │ ├── Product.php │ │ │ └── ProductInventory.php │ │ ├── Database/ │ │ │ └── Migrations/ │ │ │ ├── 2021_04_09_065617_create_products_table.php │ │ │ ├── 2024_06_28_154009_create_product_inventories_table.php │ │ │ ├── 2024_08_10_150329_create_product_activities_table.php │ │ │ ├── 2024_08_10_150340_create_product_tags_table.php │ │ │ └── 2024_09_06_065808_alter_product_inventories_table.php │ │ ├── Models/ │ │ │ ├── Product.php │ │ │ ├── ProductInventory.php │ │ │ ├── ProductInventoryProxy.php │ │ │ └── ProductProxy.php │ │ ├── Providers/ │ │ │ ├── ModuleServiceProvider.php │ │ │ └── ProductServiceProvider.php │ │ └── Repositories/ │ │ ├── ProductInventoryRepository.php │ │ └── ProductRepository.php │ ├── Quote/ │ │ └── src/ │ │ ├── Contracts/ │ │ │ ├── Quote.php │ │ │ └── QuoteItem.php │ │ ├── Database/ │ │ │ └── Migrations/ │ │ │ ├── 2021_07_01_230345_create_quotes_table.php │ │ │ └── 2021_07_01_231317_create_quote_items_table.php │ │ ├── Models/ │ │ │ ├── Quote.php │ │ │ ├── QuoteItem.php │ │ │ ├── QuoteItemProxy.php │ │ │ └── QuoteProxy.php │ │ ├── Providers/ │ │ │ ├── ModuleServiceProvider.php │ │ │ └── QuoteServiceProvider.php │ │ └── Repositories/ │ │ ├── QuoteItemRepository.php │ │ └── QuoteRepository.php │ ├── Tag/ │ │ ├── composer.json │ │ └── src/ │ │ ├── Contracts/ │ │ │ └── Tag.php │ │ ├── Database/ │ │ │ └── Migrations/ │ │ │ └── 2021_05_20_141230_create_tags_table.php │ │ ├── Models/ │ │ │ ├── Tag.php │ │ │ └── TagProxy.php │ │ ├── Providers/ │ │ │ ├── ModuleServiceProvider.php │ │ │ └── TagServiceProvider.php │ │ └── Repositories/ │ │ └── TagRepository.php │ ├── User/ │ │ ├── .gitignore │ │ ├── composer.json │ │ └── src/ │ │ ├── Contracts/ │ │ │ ├── Group.php │ │ │ ├── Role.php │ │ │ └── User.php │ │ ├── Database/ │ │ │ └── Migrations/ │ │ │ ├── .gitkeep │ │ │ ├── 2021_03_12_074578_create_groups_table.php │ │ │ ├── 2021_03_12_074597_create_roles_table.php │ │ │ ├── 2021_03_12_074857_create_users_table.php │ │ │ ├── 2021_03_12_074867_create_user_groups_table.php │ │ │ ├── 2021_03_12_074957_create_user_password_resets_table.php │ │ │ ├── 2021_09_22_194622_add_unique_index_to_name_in_groups_table.php │ │ │ └── 2021_11_12_171510_add_image_column_in_users_table.php │ │ ├── Models/ │ │ │ ├── Group.php │ │ │ ├── GroupProxy.php │ │ │ ├── Role.php │ │ │ ├── RoleProxy.php │ │ │ ├── User.php │ │ │ └── UserProxy.php │ │ ├── Providers/ │ │ │ ├── ModuleServiceProvider.php │ │ │ └── UserServiceProvider.php │ │ └── Repositories/ │ │ ├── GroupRepository.php │ │ ├── RoleRepository.php │ │ └── UserRepository.php │ ├── Warehouse/ │ │ └── src/ │ │ ├── Contracts/ │ │ │ ├── Location.php │ │ │ └── Warehouse.php │ │ ├── Database/ │ │ │ └── Migrations/ │ │ │ ├── 2024_06_21_160707_create_warehouses_table.php │ │ │ ├── 2024_06_21_160735_create_warehouse_locations_table.php │ │ │ ├── 2024_08_10_100329_create_warehouse_activities_table.php │ │ │ └── 2024_08_10_100340_create_warehouse_tags_table.php │ │ ├── Models/ │ │ │ ├── Location.php │ │ │ ├── LocationProxy.php │ │ │ ├── Warehouse.php │ │ │ └── WarehouseProxy.php │ │ ├── Providers/ │ │ │ ├── ModuleServiceProvider.php │ │ │ └── WarehouseServiceProvider.php │ │ └── Repositories/ │ │ ├── LocationRepository.php │ │ └── WarehouseRepository.php │ └── WebForm/ │ ├── .gitignore │ ├── composer.json │ ├── package.json │ ├── postcss.config.js │ ├── src/ │ │ ├── Config/ │ │ │ ├── acl.php │ │ │ └── menu.php │ │ ├── Contracts/ │ │ │ ├── WebForm.php │ │ │ └── WebFormAttribute.php │ │ ├── DataGrids/ │ │ │ └── WebFormDataGrid.php │ │ ├── Database/ │ │ │ └── Migrations/ │ │ │ ├── 2021_12_14_213049_create_web_forms_table.php │ │ │ └── 2021_12_14_214923_create_web_form_attributes_table.php │ │ ├── Http/ │ │ │ ├── Controllers/ │ │ │ │ ├── Controller.php │ │ │ │ └── WebFormController.php │ │ │ └── Requests/ │ │ │ └── WebForm.php │ │ ├── Models/ │ │ │ ├── WebForm.php │ │ │ ├── WebFormAttribute.php │ │ │ ├── WebFormAttributeProxy.php │ │ │ └── WebFormProxy.php │ │ ├── Providers/ │ │ │ ├── ModuleServiceProvider.php │ │ │ └── WebFormServiceProvider.php │ │ ├── Repositories/ │ │ │ ├── WebFormAttributeRepository.php │ │ │ └── WebFormRepository.php │ │ ├── Resources/ │ │ │ ├── assets/ │ │ │ │ ├── css/ │ │ │ │ │ └── app.css │ │ │ │ ├── js/ │ │ │ │ │ ├── app.js │ │ │ │ │ └── plugins/ │ │ │ │ │ ├── axios.js │ │ │ │ │ ├── emitter.js │ │ │ │ │ ├── flatpickr.js │ │ │ │ │ └── vee-validate.js │ │ │ │ └── locales/ │ │ │ │ ├── hi_IN.json │ │ │ │ └── sin.json │ │ │ ├── lang/ │ │ │ │ ├── ar/ │ │ │ │ │ └── app.php │ │ │ │ ├── en/ │ │ │ │ │ └── app.php │ │ │ │ ├── es/ │ │ │ │ │ └── app.php │ │ │ │ ├── fa/ │ │ │ │ │ └── app.php │ │ │ │ ├── pt_BR/ │ │ │ │ │ └── app.php │ │ │ │ ├── tr/ │ │ │ │ │ └── app.php │ │ │ │ └── vi/ │ │ │ │ └── app.php │ │ │ └── views/ │ │ │ ├── components/ │ │ │ │ ├── button/ │ │ │ │ │ └── index.blade.php │ │ │ │ ├── flash-group/ │ │ │ │ │ ├── index.blade.php │ │ │ │ │ └── item.blade.php │ │ │ │ ├── form/ │ │ │ │ │ ├── control-group/ │ │ │ │ │ │ ├── control.blade.php │ │ │ │ │ │ ├── error.blade.php │ │ │ │ │ │ ├── index.blade.php │ │ │ │ │ │ └── label.blade.php │ │ │ │ │ └── index.blade.php │ │ │ │ ├── layouts/ │ │ │ │ │ └── index.blade.php │ │ │ │ └── spinner/ │ │ │ │ └── index.blade.php │ │ │ └── settings/ │ │ │ └── web-forms/ │ │ │ ├── controls.blade.php │ │ │ ├── embed.blade.php │ │ │ └── preview.blade.php │ │ ├── Routes/ │ │ │ └── routes.php │ │ └── Rules/ │ │ └── PhoneNumber.php │ ├── tailwind.config.js │ └── vite.config.js ├── phpunit.xml ├── pint.json ├── public/ │ ├── .htaccess │ ├── admin/ │ │ └── build/ │ │ ├── assets/ │ │ │ ├── app-B1rBjssc.js │ │ │ ├── app-C2Wq9G4i.css │ │ │ ├── app-xcMAMgaV.css │ │ │ ├── ar.es-CfdTYgcp.js │ │ │ ├── bg.es-Ce0T19Qg.js │ │ │ ├── bn.es-iWyup8_3.js │ │ │ ├── bs.es-Cz58hpHx.js │ │ │ ├── ca.es-CujU75Im.js │ │ │ ├── chart-D1u1Dgzh.js │ │ │ ├── cs.es-BHhRbaip.js │ │ │ ├── da.es-DglD7fV2.js │ │ │ ├── de.es-D_4ZyLTN.js │ │ │ ├── drag-and-drop.es-JkAdgoaa.js │ │ │ ├── el.es-BC26X5xm.js │ │ │ ├── en.es-DDTuV2po.js │ │ │ ├── es.es-BvvfjN-O.js │ │ │ ├── et.es-C0rF9HtR.js │ │ │ ├── fa.es-CibKJjgz.js │ │ │ ├── fi.es-B0Iy6aas.js │ │ │ ├── fr.es-B7WXlprl.js │ │ │ ├── he.es-K9Fk8xhK.js │ │ │ ├── hr.es-ef5bxYFj.js │ │ │ ├── hu.es-nZ65MV0n.js │ │ │ ├── id.es-CzHAK-XV.js │ │ │ ├── is.es-Dz07gBgt.js │ │ │ ├── it.es-CquQA5xx.js │ │ │ ├── ja.es-D6pL26k_.js │ │ │ ├── ka.es-Cb9X-eNS.js │ │ │ ├── ko.es-CTpJlj0A.js │ │ │ ├── lt.es-vqgjGyMx.js │ │ │ ├── mn.es-YZet1as4.js │ │ │ ├── nl.es-COp8PWbT.js │ │ │ ├── no.es-_2m-F2FS.js │ │ │ ├── pl.es-jjNeJM5X.js │ │ │ ├── pt-br.es-D8ojES2d.js │ │ │ ├── pt-pt.es-D8ojES2d.js │ │ │ ├── ro.es-B6_ATXom.js │ │ │ ├── ru.es-BGEpmv_x.js │ │ │ ├── sk.es-V3h-1af8.js │ │ │ ├── sl.es-mZjm0YDT.js │ │ │ ├── sq.es-_6B4UQXy.js │ │ │ ├── sr.es-BmdOpTOG.js │ │ │ ├── sv.es-LwrPWbzy.js │ │ │ ├── tr.es-CS80t-Rq.js │ │ │ ├── uk.es-BVYlda65.js │ │ │ ├── vi.es-B5CIRCK5.js │ │ │ ├── zh-cn.es-DKz-yscG.js │ │ │ └── zh-hk.es-CFZP5Cvd.js │ │ └── manifest.json │ ├── fonts/ │ │ └── .gitignore │ ├── index.php │ ├── installer/ │ │ └── build/ │ │ ├── assets/ │ │ │ ├── app-aec2df31.js │ │ │ └── app-e0866a20.css │ │ └── manifest.json │ ├── robots.txt │ ├── web.config │ └── webform/ │ └── build/ │ ├── assets/ │ │ ├── app-499ae59b.css │ │ ├── app-8787c790.js │ │ └── app-c04ede37.css │ └── manifest.json ├── resources/ │ ├── css/ │ │ └── app.css │ ├── js/ │ │ ├── app.js │ │ └── bootstrap.js │ └── views/ │ └── .gitignore ├── routes/ │ ├── api.php │ ├── breadcrumbs.php │ ├── channels.php │ ├── console.php │ └── web.php ├── storage/ │ ├── .gitignore │ ├── app/ │ │ └── .gitignore │ ├── debugbar/ │ │ └── .gitignore │ ├── framework/ │ │ ├── .gitignore │ │ ├── cache/ │ │ │ └── .gitignore │ │ ├── sessions/ │ │ │ └── .gitignore │ │ ├── testing/ │ │ │ └── .gitignore │ │ └── views/ │ │ └── .gitignore │ └── logs/ │ └── .gitignore ├── tests/ │ ├── Feature/ │ │ └── AuthenticationTest.php │ ├── Pest.php │ ├── TestCase.php │ └── Unit/ │ └── BasicTest.php └── vite.config.js ================================================ FILE CONTENTS ================================================ ================================================ FILE: .editorconfig ================================================ root = true [*] charset = utf-8 end_of_line = lf insert_final_newline = true indent_style = space indent_size = 4 trim_trailing_whitespace = true [*.md] trim_trailing_whitespace = false [*.{yml,yaml}] indent_size = 2 ================================================ FILE: .gitattributes ================================================ * text=auto eol=lf *.blade.php diff=html *.css diff=css *.html diff=html *.md diff=markdown *.php diff=php /.github export-ignore CHANGELOG.md export-ignore .styleci.yml export-ignore ================================================ FILE: .github/FUNDING.yml ================================================ # These are supported funding model platforms open_collective: bagisto ================================================ FILE: .github/ISSUE_TEMPLATE/1_Bug_report.md ================================================ --- name: "🐛 Bug Report" about: 'Report a general library issue.' --- # Bug report ### Title **Just a quick sentence to brief your trouble with Krayin CRM or something associated with it.** **Please be calm, short and emphasize on points.** ### Issue Description **Description helps the developers to understand the bug. It describes the problem encountered or some after effect of some kind.** ### Preconditions **Please provide as detailed information about your environment as possible.** 1. framework Version. 2. Commit id. ### Steps to reproduce **It is important to provide a set of clear steps to reproduce this bug.If relevant please include code samples.** 1. step1 2. step2 ### Expected result **Tell us what should happen.** * [Screenshots, logs or description] ### Actual result > **Tell us what happens instead.** * [points....] ================================================ FILE: .github/ISSUE_TEMPLATE/2_Feature_request.md ================================================ --- name: "💡 Feature Request" about: 'For ideas or feature requests, please make a pull request, or open an issue' --- This repository is only for reporting bugs or issues. If you need support, please use other channels: 1. Write an email - mailto:support@krayincrm.com 2. Create support ticket on https://krayincrm.uvdesk.com 3. Visit forums to get support from our community (https://forums.krayincrm.com) Alternatively, you may use Facebook (https://www.facebook.com/groups/krayincrm/). We promise that more channels are coming soon!!! ================================================ FILE: .github/ISSUE_TEMPLATE/3_Support_question.md ================================================ --- name: "❔ Support Question" about: 'This repository is only for reporting bugs or problems. If you need help, see: https://github.com/krayin/laravel-crm#documentation' --- This repository is only for reporting bugs or issues. If you need support, please use: 1. Create support ticket on https://krayincrm.uvdesk.com Thanks! ================================================ FILE: .github/ISSUE_TEMPLATE/4_Security_vulnerabilities.md ================================================ --- name: "🔒 Security Vulnerabilities" about: 'For reporting security-related issues, see: https://github.com/krayin/laravel-crm#security-vulnerabilities' --- PLEASE DON'T DISCLOSE SECURITY-RELATED ISSUES PUBLICLY, SEE BELOW. If you have security vulnerability to address related to Krayin then please write a mail to us: **support@krayincrm.com** ================================================ FILE: .github/PULL_REQUEST_TEMPLATE.md ================================================ ## Issue Reference ## Description ## How To Test This? ## Documentation - [ ] My pull request requires an update on the documentation repository. ## Branch Selection - [ ] Target Branch: master ## Tailwind Reordering ================================================ FILE: .github/workflows/admin_playwright_tests.yml ================================================ name: Admin | Playwright Tests on: [push, pull_request] permissions: contents: read env: FORCE_COLOR: 1 jobs: admin_playwright_test: runs-on: ${{ matrix.operating-systems }} strategy: fail-fast: false matrix: operating-systems: [ubuntu-latest] php-versions: ['8.3'] node-version: ['22.13.1'] shard-index: [1,2,3,4,5,6] shard-total: [6] name: Admin | Playwright Tests | Shard ${{ matrix.shard-index }} Of ${{ matrix.shard-total }} services: mysql: image: mysql:8.0 env: MYSQL_ROOT_PASSWORD: root MYSQL_DATABASE: krayin ports: - 3306 options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=5 steps: - name: Checkout uses: actions/checkout@v4 - name: Setup PHP uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php-versions }} extensions: curl, fileinfo, gd, intl, mbstring, openssl, pdo, pdo_mysql, tokenizer, zip ini-values: error_reporting=E_ALL tools: composer:v2 - name: Set Up Node.js uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} - name: Install Node.js Dependencies run: npm install working-directory: packages/Webkul/Admin - name: Install Playwright Browsers run: npx playwright install --with-deps working-directory: packages/Webkul/Admin - name: Setting Environment run: | cp .env.example .env sed -i "s|^\(DB_HOST=\s*\).*$|\1127.0.0.1|" .env sed -i "s|^\(DB_PORT=\s*\).*$|\1${{ job.services.mysql.ports['3306'] }}|" .env sed -i "s|^\(DB_DATABASE=\s*\).*$|\1krayin|" .env sed -i "s|^\(DB_USERNAME=\s*\).*$|\1root|" .env sed -i "s|^\(DB_PASSWORD=\s*\).*$|\1root|" .env sed -i "s|^\(APP_DEBUG=\s*\).*$|\1true|" .env sed -i "s|^\(APP_URL=\s*\).*$|\1http://127.0.0.1:8000|" .env cat .env - name: Install Composer Dependencies run: composer install - name: Running Krayin Installer run: php artisan krayin-crm:install --skip-env-check --skip-admin-creation - name: Clear Laravel Caches run: php artisan optimize:clear - name: Verify Activity Binding run: | php artisan tinker --execute=" // 1) Laravel container binding (needed by repositories) if (! app()->bound(Webkul\\Activity\\Contracts\\Activity::class)) { throw new RuntimeException('Activity contract is NOT bound in Laravel container'); } // 2) Concord model-proxy registry (needed by ActivityProxy::modelClass()) \$concord = app('concord'); if (! \$concord->model(Webkul\\Activity\\Contracts\\Activity::class)) { throw new RuntimeException('Activity contract is NOT registered in Concord model registry'); } dump(app()->make(Webkul\\Activity\\Contracts\\Activity::class)::class); dump(\$concord->model(Webkul\\Activity\\Contracts\\Activity::class)); " # - name: Seed Product Table # run: php artisan db:seed --class="Webkul\\Installer\\Database\\Seeders\\ProductTableSeeder" - name: Start Laravel server run: | php artisan serve --host=0.0.0.0 --port=8000 > server.log 2>&1 & echo "Waiting for server to start..." timeout 30 bash -c 'until curl -s http://127.0.0.1:8000 > /dev/null; do sleep 1; done' - name: Run All Playwright Tests env: BASE_URL: 'http://127.0.0.1:8000' run: | npx playwright test --reporter=list --config=tests/e2e-pw/playwright.config.ts --shard=${{ matrix.shard-index }}/${{ matrix.shard-total }} working-directory: packages/Webkul/Admin - name: Upload Test Results uses: actions/upload-artifact@v4 if: always() with: name: test-results-${{ matrix.shard-index }}-${{ github.run_id }}-${{ github.run_number }} path: packages/Webkul/Admin/tests/e2e-pw/test-results retention-days: 1 ================================================ FILE: .github/workflows/auto_commits.yml ================================================ name: Linting Tests on: [push, pull_request] permissions: contents: write jobs: linting_tests: runs-on: ${{ matrix.operating-systems }} strategy: matrix: operating-systems: [ubuntu-latest] php-versions: ['8.3'] name: PHP ${{ matrix.php-versions }} test on ${{ matrix.operating-systems }} steps: - name: Checkout uses: actions/checkout@v4 - name: Setup PHP uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php-versions }} tools: composer:v2 - name: Install Pint run: composer global require laravel/pint env: COMPOSER_HOME: ${{ runner.temp }}/composer - name: Run Pint run: ${{ runner.temp }}/composer/vendor/bin/pint - name: Commit Linted Files uses: stefanzweifel/git-auto-commit-action@v5 with: commit_message: "chore: applied pint changes" ================================================ FILE: .github/workflows/ci.yml ================================================ name: CI on: [push, pull_request] jobs: tests: runs-on: ${{ matrix.operating-system }} strategy: matrix: operating-system: [ubuntu-latest] php-versions: ["8.3"] name: PHP ${{ matrix.php-versions }} test on ${{ matrix.operating-system }} services: mysql: image: mysql:8.0 env: MYSQL_ROOT_PASSWORD: root MYSQL_DATABASE: krayin ports: - 3306 options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=5 steps: - name: Checkout uses: actions/checkout@v2 - name: Setup PHP uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php-versions }} extensions: curl, gd, intl, mbstring, openssl, pdo, pdo_mysql, tokenizer, zip ini-values: error_reporting=E_ALL tools: composer:v2 - name: Running Composer Install run: composer install --no-interaction --prefer-dist --no-progress - name: Set Testing Environment run: | cp .env.example .env sed -i "s|^\(APP_ENV=\s*\).*$|\1testing|" .env sed -i "s|^\(DB_HOST=\s*\).*$|\1127.0.0.1|" .env sed -i "s|^\(DB_PORT=\s*\).*$|\1${{ job.services.mysql.ports['3306'] }}|" .env sed -i "s|^\(DB_DATABASE=\s*\).*$|\1krayin|" .env sed -i "s|^\(DB_USERNAME=\s*\).*$|\1root|" .env sed -i "s|^\(DB_PASSWORD=\s*\).*$|\1root|" .env - name: Running Krayin Installer run: php artisan krayin-crm:install --skip-env-check --skip-admin-creation - name: Running Pest Test run: vendor/bin/pest --parallel --colors=always ================================================ FILE: .gitignore ================================================ .env .env.testing .idea .php_cs.cache .phpunit.result.cache .vscode .vagrant *.hot /stubs /data /docker-compose-collection Homestead.json Homestead.yaml /ignorables/* /node_modules npm-debug.log package-lock.json /playwright-report /public/css /public/js /public/hot /public/storage /public/vendor /lang/vendor /storage/*.key /storage/dcc-data/ /vendor yarn.lock yarn-error.log # Playwright node_modules/ /test-results/ /blob-report/ /playwright/.cache/ ================================================ FILE: .styleci.yml ================================================ php: preset: laravel disabled: - no_unused_imports finder: not-name: - index.php js: true css: true ================================================ FILE: CHANGELOG.md ================================================ # CHANGELOG for 2.2 This changelog consists of the bug & security fixes and new features being included in the releases listed below. ## **v2.2.0 (17th of March 2026)** *Release* * **[Laravel 12 Upgrade]** Upgraded framework to Laravel 12 * #2480[enhancement] Codebase updates and refinements. * #2478[enhancement] Improved class instantiation handling. * #2472[enhancement] Upgrade to Laravel 12. * #2470[enhancement] Updated auto_commits.yml configuration. * #2469[enhancement] General enhancements and optimizations. * #2468[enhancement] Documentation updates (MD files). * #2450[fixed] Added ACL support for warehouses. * #2444[fixed] Improved global search functionality for organizations. ================================================ FILE: CODE_OF_CONDUCT.md ================================================ # Contributor Covenant Code of Conduct ## Our Pledge In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. ## Our Standards Examples of behavior that contributes to creating a positive environment include: * Using welcoming and inclusive language * Being respectful of differing viewpoints and experiences * Gracefully accepting constructive criticism * Focusing on what is best for the community * Showing empathy towards other community members Examples of unacceptable behavior by participants include: * The use of sexualized language or imagery and unwelcome sexual attention or advances * Trolling, insulting/derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or electronic address, without explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Our Responsibilities Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. ## Scope This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at support@krayincrm.com. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html [homepage]: https://www.contributor-covenant.org For answers to common questions about this code of conduct, see https://www.contributor-covenant.org/faq ================================================ FILE: LICENSE ================================================ MIT License Copyright 2010-2025, Webkul Software Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================

Krayin CRM

Total Downloads Latest Stable Version License

![enter image description here](https://raw.githubusercontent.com/krayin/temp-media/master/dashboard.png) ## Topics 1. [Introduction](#introduction) 2. [Documentation](#documentation) 3. [Requirements](#requirements) 4. [Installation & Configuration](#installation-and-configuration) 4. [Docker Installation](https://devdocs.krayincrm.com/2.0/introduction/docker.html) 5. [Krayin Cloud System](#krayin-cloud-system) 6. [License](#license) 7. [Security Vulnerabilities](#security-vulnerabilities) ### Introduction [Krayin CRM](https://krayincrm.com) is a hand tailored CRM framework built on some of the hottest opensource technologies such as [Laravel](https://laravel.com) (a [PHP](https://secure.php.net/) framework) and [Vue.js](https://vuejs.org) a progressive Javascript framework. **Free & Opensource Laravel CRM solution for SMEs and Enterprises for complete customer lifecycle management.** **Read our documentation: [Krayin CRM Docs](https://devdocs.krayincrm.com/)** **We also have a forum for any type of concerns, feature requests, or discussions. Please visit: [Krayin CRM Forums](https://forums.krayincrm.com/)** # Visit our live [Demo](https://demo.krayincrm.com) Chinese It packs in lots of features that will allow your E-Commerce business to scale in no time: - Descriptive and Simple Admin Panel. - Admin Dashboard. - Custom Attributes. - Built on Modular Approach. - Email parsing via Sendgrid. - Check out [these features and more](https://krayincrm.com/features/). **For Developers**: Take advantage of two of the hottest frameworks used in this project -- Laravel and Vue.js -- both of which have been used in Krayin CRM. ### Documentation #### Krayin Documentation [https://devdocs.krayincrm.com](https://devdocs.krayincrm.com) ### Requirements - **SERVER**: Apache 2 or NGINX. - **RAM**: 3 GB or higher. - **PHP**: 8.3 or higher - **Composer**: 2.5 or higher - **For MySQL users**: 8.0.32 or higher. ### Installation and Configuration ##### Execute these commands below, in order ``` composer create-project ``` - Find **.env** file in root directory and change the **APP_URL** param to your **domain**. - Also, Configure the **Mail** and **Database** parameters inside **.env** file. ``` php artisan krayin-crm:install ``` **To execute Krayin**: ##### On server: Warning: Before going into production mode we recommend you uninstall developer dependencies. In order to do that, run the command below: > composer install --no-dev ``` Open the specified entry point in your hosts file in your browser or make an entry in hosts file if not done. ``` ##### On local: ``` php artisan route:clear php artisan serve ``` **How to log in as admin:** > _http(s)://example.com/admin/login_ ``` email:admin@example.com password:admin123 ``` ### Krayin Cloud Hosting [Krayin CRM Cloud Hosting](https://krayincrm.com/crm-cloud-hosting) is a fully managed hosting solution where our team sets up, secures, and configures your Krayin CRM on reliable infrastructure. Get a ready-to-use CRM on your own domain, without manual installation or infrastructure complexity, and focus on growing your business while we handle the technology. ![Krayin CRM Cloud Hosting](https://raw.githubusercontent.com/krayin/temp-media/master/cloud_hosting.png) ### Krayin CRM Multi Tenant SaaS [Krayin CRM Multi Tenant SaaS](https://krayincrm.com/extensions/krayin-crm-multi-tenant-saas-extension/) Krayin Multitenant SaaS is a Laravel-based CRM solution that allows multiple businesses (tenants) to use a single application instance while keeping their data isolated and secure. ![enter image description here](https://raw.githubusercontent.com/krayin/temp-media/master/krayin-saas.png) ### WhatsApp CRM Integration [Krayin CRM WhatsApp](https://krayincrm.com/extensions/krayin-crm-whatsapp-extension/) Extension enables the store administrator to generate leads via their WhatsApp number. ![enter image description here](https://raw.githubusercontent.com/krayin/temp-media/master/krayin-crm-whatsapp-integration.png) ### VoIP CRM Integration [Krayin CRM VoIP](https://krayincrm.com/extensions/krayin-crm-voip/) extension allows the user to make Trunk calls over a broadband Internet connection and the user can also perform Inbound routes. ![enter image description here](https://raw.githubusercontent.com/krayin/temp-media/master/krayin-voip.png) ### License Krayin CRM is a fully open-source CRM framework which will always be free under the [MIT License](https://github.com/krayin/laravel-crm/blob/2.1/LICENSE). ### Security Vulnerabilities Please don't disclose security vulnerabilities publicly. If you find any security vulnerability in Krayin CRM then please email us: sales@krayincrm.com. ================================================ FILE: UPGRADE.md ================================================ # UPGRADE Guide - [Upgrading To v2.2 From v2.1](#upgrading-to-v22-from-v21) ## High Impact Changes - [Laravel 12 Upgrade](#laravel-12-upgrade) ## Upgrading To v2.2 From v2.1 > [!NOTE] > We strive to document every potential breaking change. However, as some of these alterations occur in lesser-known sections of Krayin, only a fraction of them may impact your application. ### Updating Dependencies **Impact Probability: High** #### PHP 8.3 Required Krayin CRM v2.2 now requires PHP 8.3 or greater. ### Laravel 12 Upgrade **Impact Probability: High** Krayin CRM v2.2 has been upgraded to Laravel 12, which introduces stricter type checking and modernized date/time handling. #### Key Changes - **Bootstrap**: `bootstrap/app.php` now uses the new `Application::configure()` builder pattern. Service providers are listed in `bootstrap/providers.php`. - **Kernels Removed**: `app/Http/Kernel.php` and `app/Console/Kernel.php` are removed. Middleware and scheduling are configured in `bootstrap/app.php` and `routes/console.php` respectively. - **Exception Handler Removed**: `app/Exceptions/Handler.php` is removed. Exception handling is configured in `bootstrap/app.php`. - **Middleware Classes Removed**: Built-in middleware wrappers (EncryptCookies, VerifyCsrfToken, TrimStrings, TrustProxies, etc.) are removed. Customizations are now done via `bootstrap/app.php`. - **Service Providers Simplified**: `AuthServiceProvider`, `EventServiceProvider`, `RouteServiceProvider`, and `BroadcastServiceProvider` are removed. Their logic is handled in `bootstrap/app.php` or `AppServiceProvider`. - **Config**: `config/app.php` no longer contains `providers` or `aliases` arrays. - **doctrine/dbal** dependency has been removed (native column modification in Laravel 12). ================================================ FILE: app/Http/Controllers/Controller.php ================================================ */ use HasFactory, Notifiable; /** * The attributes that are mass assignable. * * @var list */ protected $fillable = [ 'name', 'email', 'password', ]; /** * The attributes that should be hidden for serialization. * * @var list */ protected $hidden = [ 'password', 'remember_token', ]; /** * Get the attributes that should be cast. * * @return array */ protected function casts(): array { return [ 'email_verified_at' => 'datetime', 'password' => 'hashed', ]; } } ================================================ FILE: app/Providers/AppServiceProvider.php ================================================ handleCommand(new ArgvInput); exit($status); ================================================ FILE: bootstrap/app.php ================================================ withRouting( web: __DIR__.'/../routes/web.php', api: __DIR__.'/../routes/api.php', commands: __DIR__.'/../routes/console.php', channels: __DIR__.'/../routes/channels.php', health: '/up', ) ->withMiddleware(function (Middleware $middleware) { $middleware->append(CanInstall::class); $middleware->encryptCookies(except: [ 'dark_mode', ]); $middleware->validateCsrfTokens(except: [ 'admin/mail/inbound-parse', 'admin/web-forms/forms/*', ]); $middleware->api(prepend: [ EnsureFrontendRequestsAreStateful::class, ]); }) ->withExceptions(function (Exceptions $exceptions) { // })->create(); ================================================ FILE: bootstrap/cache/.gitignore ================================================ * !.gitignore ================================================ FILE: bootstrap/providers.php ================================================ env('APP_NAME', 'Laravel'), /* |-------------------------------------------------------------------------- | Application Environment |-------------------------------------------------------------------------- | | This value determines the "environment" your application is currently | running in. This may determine how you prefer to configure various | services the application utilizes. Set this in your ".env" file. | */ 'env' => env('APP_ENV', 'production'), /* |-------------------------------------------------------------------------- | Application Debug Mode |-------------------------------------------------------------------------- | | When your application is in debug mode, detailed error messages with | stack traces will be shown on every error that occurs within your | application. If disabled, a simple generic error page is shown. | */ 'debug' => (bool) env('APP_DEBUG', false), /* |-------------------------------------------------------------------------- | Application URL |-------------------------------------------------------------------------- | | This URL is used by the console to properly generate URLs when using | the Artisan command line tool. You should set this to the root of | your application so that it is used when running Artisan tasks. | */ 'url' => env('APP_URL', 'http://localhost'), /* |-------------------------------------------------------------------------- | Application Admin URL |-------------------------------------------------------------------------- | | This URL suffix is used to define the admin url for example | admin/ or backend/ | */ 'admin_path' => env('APP_ADMIN_PATH', 'admin'), 'asset_url' => env('ASSET_URL'), /* |-------------------------------------------------------------------------- | Application Timezone |-------------------------------------------------------------------------- | | Here you may specify the default timezone for your application, which | will be used by the PHP date and date-time functions. We have gone | ahead and set this to a sensible default for you out of the box. | */ 'timezone' => env('APP_TIMEZONE', 'Asia/Kolkata'), /* |-------------------------------------------------------------------------- | Application Locale Configuration |-------------------------------------------------------------------------- | | The application locale determines the default locale that will be used | by the translation service provider. You are free to set this value | to any of the locales which will be supported by the application. | */ 'locale' => env('APP_LOCALE', 'en'), /* |-------------------------------------------------------------------------- | Available Locales Configuration |-------------------------------------------------------------------------- | | The application available locale determines the supported locales | by application | */ 'available_locales' => [ 'ar' => 'Arabic', 'en' => 'English', 'es' => 'Español', 'fa' => 'Persian', 'pt_BR' => 'Portuguese', 'tr' => 'Türkçe', 'vi' => 'Vietnamese', ], /* |-------------------------------------------------------------------------- | Application Fallback Locale |-------------------------------------------------------------------------- | | The fallback locale determines the locale to use when the current one | is not available. You may change the value to correspond to any of | the language folders that are provided through your application. | */ 'fallback_locale' => 'en', /* |-------------------------------------------------------------------------- | Faker Locale |-------------------------------------------------------------------------- | | This locale will be used by the Faker PHP library when generating fake | data for your database seeds. For example, this will be used to get | localized telephone numbers, street address information and more. | */ 'faker_locale' => 'en_US', /* |-------------------------------------------------------------------------- | Base Currency Code |-------------------------------------------------------------------------- | | Here you may specify the base currency code for your application. | */ 'currency' => env('APP_CURRENCY', 'USD'), /* |-------------------------------------------------------------------------- | Encryption Key |-------------------------------------------------------------------------- | | This key is used by the Illuminate encrypter service and should be set | to a random, 32 character string, otherwise these encrypted strings | will not be safe. Please do this before deploying an application! | */ 'key' => env('APP_KEY'), 'cipher' => 'AES-256-CBC', /* |-------------------------------------------------------------------------- | Maintenance Mode Driver |-------------------------------------------------------------------------- | | These configuration options determine the driver used to determine and | manage Laravel's "maintenance mode" status. The "cache" driver will | allow maintenance mode to be controlled across multiple machines. | | Supported drivers: "file", "cache" | */ 'maintenance' => [ 'driver' => env('APP_MAINTENANCE_DRIVER', 'file'), 'store' => env('APP_MAINTENANCE_STORE', 'database'), ], ]; ================================================ FILE: config/auth.php ================================================ [ 'guard' => 'user', 'passwords' => 'users', ], /* |-------------------------------------------------------------------------- | Authentication Guards |-------------------------------------------------------------------------- | | Next, you may define every authentication guard for your application. | Of course, a great default configuration has been defined for you | here which uses session storage and the Eloquent user provider. | | All authentication drivers have a user provider. This defines how the | users are actually retrieved out of your database or other storage | mechanisms used by this application to persist your user's data. | | Supported: "session", "token" | */ 'guards' => [ 'user' => [ 'driver' => 'session', 'provider' => 'users', ], ], /* |-------------------------------------------------------------------------- | User Providers |-------------------------------------------------------------------------- | | All authentication drivers have a user provider. This defines how the | users are actually retrieved out of your database or other storage | mechanisms used by this application to persist your user's data. | | If you have multiple user tables or models you may configure multiple | sources which represent each model / table. These sources may then | be assigned to any extra authentication guards you have defined. | | Supported: "database", "eloquent" | */ 'providers' => [ 'users' => [ 'driver' => 'eloquent', 'model' => User::class, ], ], /* |-------------------------------------------------------------------------- | Resetting Passwords |-------------------------------------------------------------------------- | | You may specify multiple password reset configurations if you have more | than one user table or model in the application and you want to have | separate password reset settings based on the specific user types. | | The expire time is the number of minutes that the reset token should be | considered valid. This security feature keeps tokens short-lived so | they have less time to be guessed. You may change this as needed. | */ 'passwords' => [ 'users' => [ 'provider' => 'users', 'table' => 'user_password_resets', 'expire' => 60, ], ], /* |-------------------------------------------------------------------------- | Password Confirmation Timeout |-------------------------------------------------------------------------- | | Here you may define the amount of seconds before a password confirmation | times out and the user is prompted to re-enter their password via the | confirmation screen. By default, the timeout lasts for three hours. | */ 'password_timeout' => 10800, ]; ================================================ FILE: config/breadcrumbs.php ================================================ 'breadcrumbs::bootstrap5', /* |-------------------------------------------------------------------------- | Breadcrumbs File(s) |-------------------------------------------------------------------------- | | The file(s) where breadcrumbs are defined. e.g. | | - base_path('routes/breadcrumbs.php') | - glob(base_path('breadcrumbs/*.php')) | */ 'files' => base_path('routes/breadcrumbs.php'), /* |-------------------------------------------------------------------------- | Exceptions |-------------------------------------------------------------------------- | | Determine when to throw an exception. | */ // When route-bound breadcrumbs are used but the current route doesn't have a name (UnnamedRouteException) 'unnamed-route-exception' => true, // When route-bound breadcrumbs are used and the matching breadcrumb doesn't exist (InvalidBreadcrumbException) 'missing-route-bound-breadcrumb-exception' => true, // When a named breadcrumb is used but doesn't exist (InvalidBreadcrumbException) 'invalid-named-breadcrumb-exception' => true, /* |-------------------------------------------------------------------------- | Classes |-------------------------------------------------------------------------- | | Subclass the default classes for more advanced customisations. | */ // Manager 'manager-class' => Manager::class, // Generator 'generator-class' => Generator::class, ]; ================================================ FILE: config/broadcasting.php ================================================ env('BROADCAST_DRIVER', 'null'), /* |-------------------------------------------------------------------------- | Broadcast Connections |-------------------------------------------------------------------------- | | Here you may define all of the broadcast connections that will be used | to broadcast events to other systems or over websockets. Samples of | each available type of connection are provided inside this array. | */ 'connections' => [ 'pusher' => [ 'driver' => 'pusher', 'key' => env('PUSHER_APP_KEY'), 'secret' => env('PUSHER_APP_SECRET'), 'app_id' => env('PUSHER_APP_ID'), 'options' => [ 'cluster' => env('PUSHER_APP_CLUSTER'), 'useTLS' => true, ], ], 'ably' => [ 'driver' => 'ably', 'key' => env('ABLY_KEY'), ], 'redis' => [ 'driver' => 'redis', 'connection' => 'default', ], 'log' => [ 'driver' => 'log', ], 'null' => [ 'driver' => 'null', ], ], ]; ================================================ FILE: config/cache.php ================================================ env('CACHE_DRIVER', 'file'), /* |-------------------------------------------------------------------------- | Cache Stores |-------------------------------------------------------------------------- | | Here you may define all of the cache "stores" for your application as | well as their drivers. You may even define multiple stores for the | same cache driver to group types of items stored in your caches. | | Supported drivers: "apc", "array", "database", "file", | "memcached", "redis", "dynamodb", "null" | */ 'stores' => [ 'apc' => [ 'driver' => 'apc', ], 'array' => [ 'driver' => 'array', 'serialize' => false, ], 'database' => [ 'driver' => 'database', 'table' => 'cache', 'connection' => null, 'lock_connection' => null, ], 'file' => [ 'driver' => 'file', 'path' => storage_path('framework/cache/data'), ], 'memcached' => [ 'driver' => 'memcached', 'persistent_id' => env('MEMCACHED_PERSISTENT_ID'), 'sasl' => [ env('MEMCACHED_USERNAME'), env('MEMCACHED_PASSWORD'), ], 'options' => [ // Memcached::OPT_CONNECT_TIMEOUT => 2000, ], 'servers' => [ [ 'host' => env('MEMCACHED_HOST', '127.0.0.1'), 'port' => env('MEMCACHED_PORT', 11211), 'weight' => 100, ], ], ], 'redis' => [ 'driver' => 'redis', 'connection' => 'cache', 'lock_connection' => 'default', ], 'dynamodb' => [ 'driver' => 'dynamodb', 'key' => env('AWS_ACCESS_KEY_ID'), 'secret' => env('AWS_SECRET_ACCESS_KEY'), 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'), 'table' => env('DYNAMODB_CACHE_TABLE', 'cache'), 'endpoint' => env('DYNAMODB_ENDPOINT'), ], ], /* |-------------------------------------------------------------------------- | Cache Key Prefix |-------------------------------------------------------------------------- | | When utilizing a RAM based store such as APC or Memcached, there might | be other applications utilizing the same cache. So, we'll specify a | value to get prefixed to all our keys so we can avoid collisions. | */ 'prefix' => env('CACHE_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_cache'), ]; ================================================ FILE: config/concord.php ================================================ [ ModuleServiceProvider::class, Webkul\Admin\Providers\ModuleServiceProvider::class, Webkul\Attribute\Providers\ModuleServiceProvider::class, Webkul\Automation\Providers\ModuleServiceProvider::class, Webkul\Contact\Providers\ModuleServiceProvider::class, Webkul\Core\Providers\ModuleServiceProvider::class, Webkul\DataGrid\Providers\ModuleServiceProvider::class, Webkul\EmailTemplate\Providers\ModuleServiceProvider::class, Webkul\Email\Providers\ModuleServiceProvider::class, Webkul\Lead\Providers\ModuleServiceProvider::class, Webkul\Product\Providers\ModuleServiceProvider::class, Webkul\Quote\Providers\ModuleServiceProvider::class, Webkul\Tag\Providers\ModuleServiceProvider::class, Webkul\User\Providers\ModuleServiceProvider::class, Webkul\Warehouse\Providers\ModuleServiceProvider::class, Webkul\WebForm\Providers\ModuleServiceProvider::class, Webkul\DataTransfer\Providers\ModuleServiceProvider::class, ], 'register_route_models' => true, ]; ================================================ FILE: config/cors.php ================================================ [ 'admin/web-forms/forms/*', ], /* * Matches the request method. `['*']` allows all methods. */ 'allowed_methods' => ['*'], /* * Matches the request origin. `['*']` allows all origins. Wildcards can be used, eg `*.mydomain.com` */ 'allowed_origins' => ['*'], /* * Patterns that can be used with `preg_match` to match the origin. */ 'allowed_origins_patterns' => [], /* * Sets the Access-Control-Allow-Headers response header. `['*']` allows all headers. */ 'allowed_headers' => ['*'], /* * Sets the Access-Control-Expose-Headers response header with these headers. */ 'exposed_headers' => [], /* * Sets the Access-Control-Max-Age response header when > 0. */ 'max_age' => 0, /* * Sets the Access-Control-Allow-Credentials header. */ 'supports_credentials' => false, ]; ================================================ FILE: config/database.php ================================================ env('DB_CONNECTION', 'mysql'), /* |-------------------------------------------------------------------------- | Database Connections |-------------------------------------------------------------------------- | | Here are each of the database connections setup for your application. | Of course, examples of configuring each database platform that is | supported by Laravel is shown below to make development simple. | | | All database work in Laravel is done through the PHP PDO facilities | so make sure you have the driver for your particular database of | choice installed on your machine before you begin development. | */ 'connections' => [ 'sqlite' => [ 'driver' => 'sqlite', 'url' => env('DATABASE_URL'), 'database' => env('DB_DATABASE', database_path('database.sqlite')), 'prefix' => env('DB_PREFIX', ''), 'foreign_key_constraints' => env('DB_FOREIGN_KEYS', true), ], 'mysql' => [ 'driver' => 'mysql', 'url' => env('DATABASE_URL'), 'host' => env('DB_HOST', '127.0.0.1'), 'port' => env('DB_PORT', '3306'), 'database' => env('DB_DATABASE', 'forge'), 'username' => env('DB_USERNAME', 'forge'), 'password' => env('DB_PASSWORD', ''), 'unix_socket' => env('DB_SOCKET', ''), 'charset' => 'utf8mb4', 'collation' => 'utf8mb4_unicode_ci', 'prefix' => env('DB_PREFIX', ''), 'prefix_indexes' => true, 'strict' => false, 'engine' => null, 'options' => extension_loaded('pdo_mysql') ? array_filter([ PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'), ]) : [], ], 'pgsql' => [ 'driver' => 'pgsql', 'url' => env('DATABASE_URL'), 'host' => env('DB_HOST', '127.0.0.1'), 'port' => env('DB_PORT', '5432'), 'database' => env('DB_DATABASE', 'forge'), 'username' => env('DB_USERNAME', 'forge'), 'password' => env('DB_PASSWORD', ''), 'charset' => 'utf8', 'prefix' => env('DB_PREFIX', ''), 'prefix_indexes' => true, 'schema' => 'public', 'sslmode' => 'prefer', ], 'sqlsrv' => [ 'driver' => 'sqlsrv', 'url' => env('DATABASE_URL'), 'host' => env('DB_HOST', 'localhost'), 'port' => env('DB_PORT', '1433'), 'database' => env('DB_DATABASE', 'forge'), 'username' => env('DB_USERNAME', 'forge'), 'password' => env('DB_PASSWORD', ''), 'charset' => 'utf8', 'prefix' => env('DB_PREFIX', ''), 'prefix_indexes' => true, ], ], /* |-------------------------------------------------------------------------- | Migration Repository Table |-------------------------------------------------------------------------- | | This table keeps track of all the migrations that have already run for | your application. Using this information, we can determine which of | the migrations on disk haven't actually been run in the database. | */ 'migrations' => 'migrations', /* |-------------------------------------------------------------------------- | Redis Databases |-------------------------------------------------------------------------- | | Redis is an open source, fast, and advanced key-value store that also | provides a richer body of commands than a typical key-value system | such as APC or Memcached. Laravel makes it easy to dig right in. | */ 'redis' => [ 'client' => env('REDIS_CLIENT', 'phpredis'), 'options' => [ 'cluster' => env('REDIS_CLUSTER', 'redis'), 'prefix' => env('REDIS_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_database_'), ], 'default' => [ 'url' => env('REDIS_URL'), 'host' => env('REDIS_HOST', '127.0.0.1'), 'password' => env('REDIS_PASSWORD', null), 'port' => env('REDIS_PORT', '6379'), 'database' => env('REDIS_DB', '0'), ], 'cache' => [ 'url' => env('REDIS_URL'), 'host' => env('REDIS_HOST', '127.0.0.1'), 'password' => env('REDIS_PASSWORD', null), 'port' => env('REDIS_PORT', '6379'), 'database' => env('REDIS_CACHE_DB', '1'), ], ], ]; ================================================ FILE: config/filesystems.php ================================================ env('FILESYSTEM_DISK', 'public'), /* |-------------------------------------------------------------------------- | Filesystem Disks |-------------------------------------------------------------------------- | | Here you may configure as many filesystem "disks" as you wish, and you | may even configure multiple disks of the same driver. Defaults have | been setup for each driver as an example of the required options. | | Supported Drivers: "local", "ftp", "sftp", "s3" | */ 'disks' => [ 'local' => [ 'driver' => 'local', 'root' => storage_path('app'), ], 'public' => [ 'driver' => 'local', 'root' => storage_path('app/public'), 'url' => env('APP_URL').'/storage', 'visibility' => 'public', ], 's3' => [ 'driver' => 's3', 'key' => env('AWS_ACCESS_KEY_ID'), 'secret' => env('AWS_SECRET_ACCESS_KEY'), 'region' => env('AWS_DEFAULT_REGION'), 'bucket' => env('AWS_BUCKET'), 'url' => env('AWS_URL'), 'endpoint' => env('AWS_ENDPOINT'), ], ], /* |-------------------------------------------------------------------------- | Symbolic Links |-------------------------------------------------------------------------- | | Here you may configure the symbolic links that will be created when the | `storage:link` Artisan command is executed. The array keys should be | the locations of the links and the values should be their targets. | */ 'links' => [ public_path('storage') => storage_path('app/public'), ], ]; ================================================ FILE: config/hashing.php ================================================ 'bcrypt', /* |-------------------------------------------------------------------------- | Bcrypt Options |-------------------------------------------------------------------------- | | Here you may specify the configuration options that should be used when | passwords are hashed using the Bcrypt algorithm. This will allow you | to control the amount of time it takes to hash the given password. | */ 'bcrypt' => [ 'rounds' => env('BCRYPT_ROUNDS', 10), ], /* |-------------------------------------------------------------------------- | Argon Options |-------------------------------------------------------------------------- | | Here you may specify the configuration options that should be used when | passwords are hashed using the Argon algorithm. These will allow you | to control the amount of time it takes to hash the given password. | */ 'argon' => [ 'memory' => 1024, 'threads' => 2, 'time' => 2, ], ]; ================================================ FILE: config/imap.php ================================================ env('IMAP_DEFAULT_ACCOUNT', 'default'), /* |-------------------------------------------------------------------------- | Default date format |-------------------------------------------------------------------------- | | The default date format is used to convert any given Carbon::class object into a valid date string. | These are currently known working formats: "d-M-Y", "d-M-y", "d M y" | */ 'date_format' => 'd-M-Y', /* |-------------------------------------------------------------------------- | Available IMAP accounts |-------------------------------------------------------------------------- | | Please list all IMAP accounts which you are planning to use within the | array below. | */ 'accounts' => [ 'default' => [ 'host' => env('IMAP_HOST', 'localhost'), 'port' => env('IMAP_PORT', 993), 'protocol' => env('IMAP_PROTOCOL', 'imap'), // might also use imap, [pop3 or nntp (untested)] 'encryption' => env('IMAP_ENCRYPTION', 'ssl'), // Supported: false, 'ssl', 'tls', 'notls', 'starttls' 'validate_cert' => env('IMAP_VALIDATE_CERT', true), 'username' => env('IMAP_USERNAME', 'root@example.com'), 'password' => env('IMAP_PASSWORD', ''), 'authentication' => env('IMAP_AUTHENTICATION', null), 'proxy' => [ 'socket' => null, 'request_fulluri' => false, 'username' => null, 'password' => null, ], 'timeout' => 30, 'extensions' => [], ], ], /* |-------------------------------------------------------------------------- | Available IMAP options |-------------------------------------------------------------------------- | | Available php imap config parameters are listed below | -Delimiter (optional): | This option is only used when calling $oClient-> | You can use any supported char such as ".", "/", (...) | -Fetch option: | IMAP::FT_UID - Message marked as read by fetching the body message | IMAP::FT_PEEK - Fetch the message without setting the "seen" flag | -Fetch sequence id: | IMAP::ST_UID - Fetch message components using the message uid | IMAP::ST_MSGN - Fetch message components using the message number | -Body download option | Default TRUE | -Flag download option | Default TRUE | -Soft fail | Default FALSE - Set to TRUE if you want to ignore certain exception while fetching bulk messages | -RFC822 | Default TRUE - Set to FALSE to prevent the usage of \imap_rfc822_parse_headers(). | See https://github.com/Webklex/php-imap/issues/115 for more information. | -Debug enable to trace communication traffic | -UID cache enable the UID cache | -Fallback date is used if the given message date could not be parsed | -Boundary regex used to detect message boundaries. If you are having problems with empty messages, missing | attachments or anything like this. Be advised that it likes to break which causes new problems.. | -Message key identifier option | You can choose between the following: | 'id' - Use the MessageID as array key (default, might cause hickups with yahoo mail) | 'number' - Use the message number as array key (isn't always unique and can cause some interesting behavior) | 'list' - Use the message list number as array key (incrementing integer (does not always start at 0 or 1) | 'uid' - Use the message uid as array key (isn't always unique and can cause some interesting behavior) | -Fetch order | 'asc' - Order all messages ascending (probably results in oldest first) | 'desc' - Order all messages descending (probably results in newest first) | -Disposition types potentially considered an attachment | Default ['attachment', 'inline'] | -Common folders | Default folder locations and paths assumed if none is provided | -Open IMAP options: | DISABLE_AUTHENTICATOR - Disable authentication properties. | Use 'GSSAPI' if you encounter the following | error: "Kerberos error: No credentials cache | file found (try running kinit) (...)" | or ['GSSAPI','PLAIN'] if you are using outlook mail | -Decoder options (currently only the message subject and attachment name decoder can be set) | 'utf-8' - Uses imap_utf8($string) to decode a string | 'mimeheader' - Uses mb_decode_mimeheader($string) to decode a string | */ 'options' => [ 'delimiter' => '/', 'fetch' => IMAP::FT_PEEK, 'sequence' => IMAP::ST_UID, 'fetch_body' => true, 'fetch_flags' => true, 'soft_fail' => false, 'rfc822' => true, 'debug' => false, 'uid_cache' => true, // 'fallback_date' => "01.01.1970 00:00:00", 'boundary' => '/boundary=(.*?(?=;)|(.*))/i', 'message_key' => 'list', 'fetch_order' => 'asc', 'dispositions' => ['attachment', 'inline'], 'common_folders' => [ 'root' => 'INBOX', 'junk' => 'INBOX/Junk', 'draft' => 'INBOX/Drafts', 'sent' => 'INBOX/Sent', 'trash' => 'INBOX/Trash', ], 'decoder' => [ 'message' => 'utf-8', // mimeheader 'attachment' => 'utf-8', // mimeheader ], 'open' => [ // 'DISABLE_AUTHENTICATOR' => 'GSSAPI' ], ], /* |-------------------------------------------------------------------------- | Available flags |-------------------------------------------------------------------------- | | List all available / supported flags. Set to null to accept all given flags. */ 'flags' => ['recent', 'flagged', 'answered', 'deleted', 'seen', 'draft'], /* |-------------------------------------------------------------------------- | Available events |-------------------------------------------------------------------------- | */ 'events' => [ 'message' => [ 'new' => MessageNewEvent::class, 'moved' => MessageMovedEvent::class, 'copied' => MessageCopiedEvent::class, 'deleted' => MessageDeletedEvent::class, 'restored' => MessageRestoredEvent::class, ], 'folder' => [ 'new' => FolderNewEvent::class, 'moved' => FolderMovedEvent::class, 'deleted' => FolderDeletedEvent::class, ], 'flag' => [ 'new' => FlagNewEvent::class, 'deleted' => FlagDeletedEvent::class, ], ], /* |-------------------------------------------------------------------------- | Available masking options |-------------------------------------------------------------------------- | | By using your own custom masks you can implement your own methods for | a better and faster access and less code to write. | | Checkout the two examples custom_attachment_mask and custom_message_mask | for a quick start. | | The provided masks below are used as the default masks. */ 'masks' => [ 'message' => MessageMask::class, 'attachment' => AttachmentMask::class, ], ]; ================================================ FILE: config/krayin-vite.php ================================================ [ 'admin' => [ 'hot_file' => 'admin-vite.hot', 'build_directory' => 'admin/build', 'package_assets_directory' => 'src/Resources/assets', ], 'installer' => [ 'hot_file' => 'installer-vite.hot', 'build_directory' => 'installer/build', 'package_assets_directory' => 'src/Resources/assets', ], 'webform' => [ 'hot_file' => 'webform-vite.hot', 'build_directory' => 'webform/build', 'package_assets_directory' => 'src/Resources/assets', ], ], ]; ================================================ FILE: config/logging.php ================================================ env('LOG_CHANNEL', 'stack'), /* |-------------------------------------------------------------------------- | Log Channels |-------------------------------------------------------------------------- | | Here you may configure the log channels for your application. Out of | the box, Laravel uses the Monolog PHP logging library. This gives | you a variety of powerful log handlers / formatters to utilize. | | Available Drivers: "single", "daily", "slack", "syslog", | "errorlog", "monolog", | "custom", "stack" | */ 'channels' => [ 'stack' => [ 'driver' => 'stack', 'channels' => ['single'], 'ignore_exceptions' => false, ], 'single' => [ 'driver' => 'single', 'path' => storage_path('logs/laravel.log'), 'level' => env('LOG_LEVEL', 'debug'), ], 'daily' => [ 'driver' => 'daily', 'path' => storage_path('logs/laravel.log'), 'level' => env('LOG_LEVEL', 'debug'), 'days' => 14, ], 'slack' => [ 'driver' => 'slack', 'url' => env('LOG_SLACK_WEBHOOK_URL'), 'username' => 'Laravel Log', 'emoji' => ':boom:', 'level' => env('LOG_LEVEL', 'critical'), ], 'papertrail' => [ 'driver' => 'monolog', 'level' => env('LOG_LEVEL', 'debug'), 'handler' => SyslogUdpHandler::class, 'handler_with' => [ 'host' => env('PAPERTRAIL_URL'), 'port' => env('PAPERTRAIL_PORT'), ], ], 'stderr' => [ 'driver' => 'monolog', 'handler' => StreamHandler::class, 'formatter' => env('LOG_STDERR_FORMATTER'), 'with' => [ 'stream' => 'php://stderr', ], ], 'syslog' => [ 'driver' => 'syslog', 'level' => env('LOG_LEVEL', 'debug'), ], 'errorlog' => [ 'driver' => 'errorlog', 'level' => env('LOG_LEVEL', 'debug'), ], 'null' => [ 'driver' => 'monolog', 'handler' => NullHandler::class, ], 'emergency' => [ 'path' => storage_path('logs/laravel.log'), ], ], ]; ================================================ FILE: config/mail-receiver.php ================================================ env('MAIL_RECEIVER_DRIVER', 'sendgrid'), ]; ================================================ FILE: config/mail.php ================================================ env('MAIL_MAILER', 'smtp'), /* |-------------------------------------------------------------------------- | Mailer Configurations |-------------------------------------------------------------------------- | | Here you may configure all of the mailers used by your application plus | their respective settings. Several examples have been configured for | you and you are free to add your own as your application requires. | | Laravel supports a variety of mail "transport" drivers to be used while | sending an e-mail. You will specify which one you are using for your | mailers below. You are free to add additional mailers as required. | | Supported: "smtp", "sendmail", "mailgun", "ses", | "postmark", "log", "array", "failover" | */ 'mailers' => [ 'smtp' => [ 'transport' => 'smtp', 'host' => env('MAIL_HOST', 'smtp.mailgun.org'), 'port' => env('MAIL_PORT', 587), 'encryption' => env('MAIL_ENCRYPTION', 'tls'), 'username' => env('MAIL_USERNAME'), 'password' => env('MAIL_PASSWORD'), 'timeout' => null, 'verify_peer' => false, ], 'ses' => [ 'transport' => 'ses', ], 'mailgun' => [ 'transport' => 'mailgun', ], 'postmark' => [ 'transport' => 'postmark', ], 'sendmail' => [ 'transport' => 'sendmail', 'path' => env('MAIL_SENDMAIL_PATH', '/usr/sbin/sendmail -bs -i'), ], 'log' => [ 'transport' => 'log', 'channel' => env('MAIL_LOG_CHANNEL'), ], 'array' => [ 'transport' => 'array', ], 'failover' => [ 'transport' => 'failover', 'mailers' => [ 'smtp', 'log', ], ], ], /* |-------------------------------------------------------------------------- | Global "From" Address |-------------------------------------------------------------------------- | | You may wish for all e-mails sent by your application to be sent from | the same address. Here, you may specify a name and address that is | used globally for all e-mails that are sent by your application. | */ 'from' => [ 'address' => env('MAIL_FROM_ADDRESS'), 'name' => env('MAIL_FROM_NAME'), ], /* |-------------------------------------------------------------------------- | Default Mailer Domain |-------------------------------------------------------------------------- | | This option controls the domain for email message_id that is used to send email | messages sent by your application. | */ 'domain' => env('MAIL_DOMAIN', 'webkul.com'), /* |-------------------------------------------------------------------------- | Markdown Mail Settings |-------------------------------------------------------------------------- | | If you are using Markdown based email rendering, you may configure your | theme and component paths here, allowing you to customize the design | of the emails. Or, you may simply stick with the Laravel defaults! | */ 'markdown' => [ 'theme' => 'default', 'paths' => [ resource_path('views/vendor/mail'), ], ], ]; ================================================ FILE: config/queue.php ================================================ env('QUEUE_CONNECTION', 'sync'), /* |-------------------------------------------------------------------------- | Queue Connections |-------------------------------------------------------------------------- | | Here you may configure the connection information for each server that | is used by your application. A default configuration has been added | for each back-end shipped with Laravel. You are free to add more. | | Drivers: "sync", "database", "beanstalkd", "sqs", "redis", "null" | */ 'connections' => [ 'sync' => [ 'driver' => 'sync', ], 'database' => [ 'driver' => 'database', 'table' => 'jobs', 'queue' => 'default', 'retry_after' => 90, ], 'beanstalkd' => [ 'driver' => 'beanstalkd', 'host' => 'localhost', 'queue' => 'default', 'retry_after' => 90, 'block_for' => 0, ], 'sqs' => [ 'driver' => 'sqs', 'key' => env('AWS_ACCESS_KEY_ID'), 'secret' => env('AWS_SECRET_ACCESS_KEY'), 'prefix' => env('SQS_PREFIX', 'https://sqs.us-east-1.amazonaws.com/your-account-id'), 'queue' => env('SQS_QUEUE', 'your-queue-name'), 'suffix' => env('SQS_SUFFIX'), 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'), ], 'redis' => [ 'driver' => 'redis', 'connection' => 'default', 'queue' => env('REDIS_QUEUE', 'default'), 'retry_after' => 90, 'block_for' => null, ], ], /* |-------------------------------------------------------------------------- | Failed Queue Jobs |-------------------------------------------------------------------------- | | These options configure the behavior of failed queue job logging so you | can control which database and table are used to store the jobs that | have failed. You may change them to any database / table you wish. | */ 'failed' => [ 'driver' => env('QUEUE_FAILED_DRIVER', 'database-uuids'), 'database' => env('DB_CONNECTION', 'mysql'), 'table' => 'failed_jobs', ], ]; ================================================ FILE: config/repository.php ================================================ [ 'limit' => 15, ], /* |-------------------------------------------------------------------------- | Fractal Presenter Config |-------------------------------------------------------------------------- | Available serializers: ArraySerializer DataArraySerializer JsonApiSerializer */ 'fractal' => [ 'params' => [ 'include' => 'include', ], 'serializer' => DataArraySerializer::class, ], /* |-------------------------------------------------------------------------- | Cache Config |-------------------------------------------------------------------------- | */ 'cache' => [ /* |-------------------------------------------------------------------------- | Cache Status |-------------------------------------------------------------------------- | | Enable or disable cache | */ 'enabled' => false, /* |-------------------------------------------------------------------------- | Cache Minutes |-------------------------------------------------------------------------- | | Time of expiration cache | */ 'minutes' => 30, /* |-------------------------------------------------------------------------- | Cache Repository |-------------------------------------------------------------------------- | | Instance of Illuminate\Contracts\Cache\Repository | */ 'repository' => 'cache', /* |-------------------------------------------------------------------------- | Cache Clean Listener |-------------------------------------------------------------------------- | | | */ 'clean' => [ /* |-------------------------------------------------------------------------- | Enable clear cache on repository changes |-------------------------------------------------------------------------- | */ 'enabled' => true, /* |-------------------------------------------------------------------------- | Actions in Repository |-------------------------------------------------------------------------- | | create : Clear Cache on create Entry in repository | update : Clear Cache on update Entry in repository | delete : Clear Cache on delete Entry in repository | */ 'on' => [ 'create' => true, 'update' => true, 'delete' => true, ], ], 'params' => [ /* |-------------------------------------------------------------------------- | Skip Cache Params |-------------------------------------------------------------------------- | | | Ex: http://prettus.local/?search=lorem&skipCache=true | */ 'skipCache' => 'skipCache', ], /* |-------------------------------------------------------------------------- | Methods Allowed |-------------------------------------------------------------------------- | | methods cacheable : all, paginate, find, findByField, findWhere, getByCriteria | | Ex: | | 'only' =>['all','paginate'], | | or | | 'except' =>['find'], */ 'allowed' => [ 'only' => null, 'except' => null, ], ], /* |-------------------------------------------------------------------------- | Criteria Config |-------------------------------------------------------------------------- | | Settings of request parameters names that will be used by Criteria | */ 'criteria' => [ /* |-------------------------------------------------------------------------- | Accepted Conditions |-------------------------------------------------------------------------- | | Conditions accepted in consultations where the Criteria | | Ex: | | 'acceptedConditions'=>['=','like'] | | $query->where('foo','=','bar') | $query->where('foo','like','bar') | */ 'acceptedConditions' => [ '=', 'like', 'in', ], /* |-------------------------------------------------------------------------- | Request Params |-------------------------------------------------------------------------- | | Request parameters that will be used to filter the query in the repository | | Params : | | - search : Searched value | Ex: http://prettus.local/?search=lorem | | - searchFields : Fields in which research should be carried out | Ex: | http://prettus.local/?search=lorem&searchFields=name;email | http://prettus.local/?search=lorem&searchFields=name:like;email | http://prettus.local/?search=lorem&searchFields=name:like | | - filter : Fields that must be returned to the response object | Ex: | http://prettus.local/?search=lorem&filter=id,name | | - orderBy : Order By | Ex: | http://prettus.local/?search=lorem&orderBy=id | | - sortedBy : Sort | Ex: | http://prettus.local/?search=lorem&orderBy=id&sortedBy=asc | http://prettus.local/?search=lorem&orderBy=id&sortedBy=desc | | - searchJoin: Specifies the search method (AND / OR), by default the | application searches each parameter with OR | EX: | http://prettus.local/?search=lorem&searchJoin=and | http://prettus.local/?search=lorem&searchJoin=or | */ 'params' => [ 'search' => 'search', 'searchFields' => 'searchFields', 'filter' => 'filter', 'orderBy' => 'orderBy', 'sortedBy' => 'sortedBy', 'with' => 'with', 'searchJoin' => 'searchJoin', 'withCount' => 'withCount', ], ], /* |-------------------------------------------------------------------------- | Generator Config |-------------------------------------------------------------------------- | */ 'generator' => [ 'basePath' => app()->path(), 'rootNamespace' => 'App\\', 'stubsOverridePath' => app()->path(), 'paths' => [ 'models' => 'Entities', 'repositories' => 'Repositories', 'interfaces' => 'Repositories', 'transformers' => 'Transformers', 'presenters' => 'Presenters', 'validators' => 'Validators', 'controllers' => 'Http/Controllers', 'provider' => 'RepositoryServiceProvider', 'criteria' => 'Criteria', ], ], ]; ================================================ FILE: config/sanctum.php ================================================ explode(',', env('SANCTUM_STATEFUL_DOMAINS', sprintf( '%s%s', 'localhost,localhost:3000,127.0.0.1,127.0.0.1:8000,::1', env('APP_URL') ? ','.parse_url(env('APP_URL'), PHP_URL_HOST) : '' ))), /* |-------------------------------------------------------------------------- | Sanctum Guards |-------------------------------------------------------------------------- | | This array contains the authentication guards that will be checked when | Sanctum is trying to authenticate a request. If none of these guards | are able to authenticate the request, Sanctum will use the bearer | token that's present on an incoming request for authentication. | */ 'guard' => ['user'], /* |-------------------------------------------------------------------------- | Expiration Minutes |-------------------------------------------------------------------------- | | This value controls the number of minutes until an issued token will be | considered expired. If this value is null, personal access tokens do | not expire. This won't tweak the lifetime of first-party sessions. | */ 'expiration' => null, /* |-------------------------------------------------------------------------- | Sanctum Middleware |-------------------------------------------------------------------------- | | When authenticating your first-party SPA with Sanctum you may need to | customize some of the middleware Sanctum uses while processing the | request. You may change the middleware listed below as required. | */ 'middleware' => [ 'verify_csrf_token' => VerifyCsrfToken::class, 'encrypt_cookies' => EncryptCookies::class, ], ]; ================================================ FILE: config/services.php ================================================ [ 'domain' => env('MAILGUN_DOMAIN'), 'secret' => env('MAILGUN_SECRET'), 'endpoint' => env('MAILGUN_ENDPOINT', 'api.mailgun.net'), ], 'postmark' => [ 'token' => env('POSTMARK_TOKEN'), ], 'ses' => [ 'key' => env('AWS_ACCESS_KEY_ID'), 'secret' => env('AWS_SECRET_ACCESS_KEY'), 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'), ], ]; ================================================ FILE: config/session.php ================================================ env('SESSION_DRIVER', 'file'), /* |-------------------------------------------------------------------------- | Session Lifetime |-------------------------------------------------------------------------- | | Here you may specify the number of minutes that you wish the session | to be allowed to remain idle before it expires. If you want them | to immediately expire on the browser closing, set that option. | */ 'lifetime' => env('SESSION_LIFETIME', 120), 'expire_on_close' => false, /* |-------------------------------------------------------------------------- | Session Encryption |-------------------------------------------------------------------------- | | This option allows you to easily specify that all of your session data | should be encrypted before it is stored. All encryption will be run | automatically by Laravel and you can use the Session like normal. | */ 'encrypt' => false, /* |-------------------------------------------------------------------------- | Session File Location |-------------------------------------------------------------------------- | | When using the native session driver, we need a location where session | files may be stored. A default has been set for you but a different | location may be specified. This is only needed for file sessions. | */ 'files' => storage_path('framework/sessions'), /* |-------------------------------------------------------------------------- | Session Database Connection |-------------------------------------------------------------------------- | | When using the "database" or "redis" session drivers, you may specify a | connection that should be used to manage these sessions. This should | correspond to a connection in your database configuration options. | */ 'connection' => env('SESSION_CONNECTION', null), /* |-------------------------------------------------------------------------- | Session Database Table |-------------------------------------------------------------------------- | | When using the "database" session driver, you may specify the table we | should use to manage the sessions. Of course, a sensible default is | provided for you; however, you are free to change this as needed. | */ 'table' => 'sessions', /* |-------------------------------------------------------------------------- | Session Cache Store |-------------------------------------------------------------------------- | | While using one of the framework's cache driven session backends you may | list a cache store that should be used for these sessions. This value | must match with one of the application's configured cache "stores". | | Affects: "apc", "dynamodb", "memcached", "redis" | */ 'store' => env('SESSION_STORE', null), /* |-------------------------------------------------------------------------- | Session Sweeping Lottery |-------------------------------------------------------------------------- | | Some session drivers must manually sweep their storage location to get | rid of old sessions from storage. Here are the chances that it will | happen on a given request. By default, the odds are 2 out of 100. | */ 'lottery' => [2, 100], /* |-------------------------------------------------------------------------- | Session Cookie Name |-------------------------------------------------------------------------- | | Here you may change the name of the cookie used to identify a session | instance by ID. The name specified here will get used every time a | new session cookie is created by the framework for every driver. | */ 'cookie' => env( 'SESSION_COOKIE', Str::slug(env('APP_NAME', 'laravel'), '_').'_session' ), /* |-------------------------------------------------------------------------- | Session Cookie Path |-------------------------------------------------------------------------- | | The session cookie path determines the path for which the cookie will | be regarded as available. Typically, this will be the root path of | your application but you are free to change this when necessary. | */ 'path' => '/', /* |-------------------------------------------------------------------------- | Session Cookie Domain |-------------------------------------------------------------------------- | | Here you may change the domain of the cookie used to identify a session | in your application. This will determine which domains the cookie is | available to in your application. A sensible default has been set. | */ 'domain' => env('SESSION_DOMAIN', null), /* |-------------------------------------------------------------------------- | HTTPS Only Cookies |-------------------------------------------------------------------------- | | By setting this option to true, session cookies will only be sent back | to the server if the browser has a HTTPS connection. This will keep | the cookie from being sent to you if it can not be done securely. | */ 'secure' => env('SESSION_SECURE_COOKIE'), /* |-------------------------------------------------------------------------- | HTTP Access Only |-------------------------------------------------------------------------- | | Setting this value to true will prevent JavaScript from accessing the | value of the cookie and the cookie will only be accessible through | the HTTP protocol. You are free to modify this option if needed. | */ 'http_only' => true, /* |-------------------------------------------------------------------------- | Same-Site Cookies |-------------------------------------------------------------------------- | | This option determines how your cookies behave when cross-site requests | take place, and can be used to mitigate CSRF attacks. By default, we | will set this value to "lax" since this is a secure default value. | | Supported: "lax", "strict", "none", null | */ 'same_site' => 'lax', ]; ================================================ FILE: config/tinker.php ================================================ [ // App\Console\Commands\ExampleCommand::class, ], /* |-------------------------------------------------------------------------- | Auto Aliased Classes |-------------------------------------------------------------------------- | | Tinker will not automatically alias classes in your vendor namespaces | but you may explicitly allow a subset of classes to get aliased by | adding the names of each of those classes to the following list. | */ 'alias' => [ // ], /* |-------------------------------------------------------------------------- | Classes That Should Not Be Aliased |-------------------------------------------------------------------------- | | Typically, Tinker automatically aliases classes as you require them in | Tinker. However, you may wish to never alias certain classes, which | you may accomplish by listing the classes in the following array. | */ 'dont_alias' => [ 'App\Nova', ], ]; ================================================ FILE: config/view.php ================================================ [ resource_path('views'), ], /* |-------------------------------------------------------------------------- | Compiled View Path |-------------------------------------------------------------------------- | | This option determines where all the compiled Blade templates will be | stored for your application. Typically, this is within the storage | directory. However, as usual, you are free to change this value. | */ 'compiled' => env( 'VIEW_COMPILED_PATH', realpath(storage_path('framework/views')) ), ]; ================================================ FILE: database/.gitignore ================================================ *.sqlite *.sqlite-journal ================================================ FILE: database/factories/UserFactory.php ================================================ $this->faker->name, 'email' => $this->faker->unique()->safeEmail, 'email_verified_at' => now(), 'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password 'remember_token' => Str::random(10), ]; } /** * Indicate that the model's email address should be unverified. * * @return Factory */ public function unverified() { return $this->state(function (array $attributes) { return [ 'email_verified_at' => null, ]; }); } } ================================================ FILE: database/migrations/2019_08_19_000000_create_failed_jobs_table.php ================================================ id(); $table->string('uuid')->unique(); $table->text('connection'); $table->text('queue'); $table->longText('payload'); $table->longText('exception'); $table->timestamp('failed_at')->useCurrent(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('failed_jobs'); } }; ================================================ FILE: database/migrations/2019_12_14_000001_create_personal_access_tokens_table.php ================================================ id(); $table->morphs('tokenable'); $table->string('name'); $table->string('token', 64)->unique(); $table->text('abilities')->nullable(); $table->timestamp('last_used_at')->nullable(); $table->timestamp('expires_at')->nullable(); $table->timestamps(); }); } /** * Reverse the migrations. */ public function down(): void { Schema::dropIfExists('personal_access_tokens'); } }; ================================================ FILE: database/migrations/2024_09_09_094040_create_job_batches_table.php ================================================ string('id')->primary(); $table->string('name'); $table->integer('total_jobs'); $table->integer('pending_jobs'); $table->integer('failed_jobs'); $table->text('failed_job_ids'); $table->mediumText('options')->nullable(); $table->integer('cancelled_at')->nullable(); $table->integer('created_at'); $table->integer('finished_at')->nullable(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('job_batches'); } }; ================================================ FILE: database/migrations/2024_09_09_094042_create_jobs_table.php ================================================ bigIncrements('id'); $table->string('queue')->index(); $table->longText('payload'); $table->unsignedTinyInteger('attempts'); $table->unsignedInteger('reserved_at')->nullable(); $table->unsignedInteger('available_at'); $table->unsignedInteger('created_at'); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('jobs'); } }; ================================================ FILE: database/seeders/DatabaseSeeder.php ================================================ call(KrayinDatabaseSeeder::class); } } ================================================ FILE: lang/en/auth.php ================================================ 'These credentials do not match our records.', 'password' => 'The provided password is incorrect.', 'throttle' => 'Too many login attempts. Please try again in :seconds seconds.', ]; ================================================ FILE: lang/en/pagination.php ================================================ '« Previous', 'next' => 'Next »', ]; ================================================ FILE: lang/en/passwords.php ================================================ 'Your password has been reset!', 'sent' => 'We have emailed your password reset link!', 'throttled' => 'Please wait before retrying.', 'token' => 'This password reset token is invalid.', 'user' => "We can't find a user with that email address.", ]; ================================================ FILE: lang/en/validation.php ================================================ 'The :attribute must be accepted.', 'active_url' => 'The :attribute is not a valid URL.', 'after' => 'The :attribute must be a date after :date.', 'after_or_equal' => 'The :attribute must be a date after or equal to :date.', 'alpha' => 'The :attribute may only contain letters.', 'alpha_dash' => 'The :attribute may only contain letters, numbers, dashes and underscores.', 'alpha_num' => 'The :attribute may only contain letters and numbers.', 'array' => 'The :attribute must be an array.', 'before' => 'The :attribute must be a date before :date.', 'before_or_equal' => 'The :attribute must be a date before or equal to :date.', 'between' => [ 'numeric' => 'The :attribute must be between :min and :max.', 'file' => 'The :attribute must be between :min and :max kilobytes.', 'string' => 'The :attribute must be between :min and :max characters.', 'array' => 'The :attribute must have between :min and :max items.', ], 'boolean' => 'The :attribute field must be true or false.', 'confirmed' => 'The :attribute confirmation does not match.', 'date' => 'The :attribute is not a valid date.', 'date_equals' => 'The :attribute must be a date equal to :date.', 'date_format' => 'The :attribute does not match the format :format.', 'different' => 'The :attribute and :other must be different.', 'digits' => 'The :attribute must be :digits digits.', 'digits_between' => 'The :attribute must be between :min and :max digits.', 'dimensions' => 'The :attribute has invalid image dimensions.', 'distinct' => 'The :attribute field has a duplicate value.', 'email' => 'The :attribute must be a valid email address.', 'ends_with' => 'The :attribute must end with one of the following: :values.', 'exists' => 'The selected :attribute is invalid.', 'file' => 'The :attribute must be a file.', 'filled' => 'The :attribute field must have a value.', 'gt' => [ 'numeric' => 'The :attribute must be greater than :value.', 'file' => 'The :attribute must be greater than :value kilobytes.', 'string' => 'The :attribute must be greater than :value characters.', 'array' => 'The :attribute must have more than :value items.', ], 'gte' => [ 'numeric' => 'The :attribute must be greater than or equal :value.', 'file' => 'The :attribute must be greater than or equal :value kilobytes.', 'string' => 'The :attribute must be greater than or equal :value characters.', 'array' => 'The :attribute must have :value items or more.', ], 'image' => 'The :attribute must be an image.', 'in' => 'The selected :attribute is invalid.', 'in_array' => 'The :attribute field does not exist in :other.', 'integer' => 'The :attribute must be an integer.', 'ip' => 'The :attribute must be a valid IP address.', 'ipv4' => 'The :attribute must be a valid IPv4 address.', 'ipv6' => 'The :attribute must be a valid IPv6 address.', 'json' => 'The :attribute must be a valid JSON string.', 'lt' => [ 'numeric' => 'The :attribute must be less than :value.', 'file' => 'The :attribute must be less than :value kilobytes.', 'string' => 'The :attribute must be less than :value characters.', 'array' => 'The :attribute must have less than :value items.', ], 'lte' => [ 'numeric' => 'The :attribute must be less than or equal :value.', 'file' => 'The :attribute must be less than or equal :value kilobytes.', 'string' => 'The :attribute must be less than or equal :value characters.', 'array' => 'The :attribute must not have more than :value items.', ], 'max' => [ 'numeric' => 'The :attribute may not be greater than :max.', 'file' => 'The :attribute may not be greater than :max kilobytes.', 'string' => 'The :attribute may not be greater than :max characters.', 'array' => 'The :attribute may not have more than :max items.', ], 'mimes' => 'The :attribute must be a file of type: :values.', 'mimetypes' => 'The :attribute must be a file of type: :values.', 'min' => [ 'numeric' => 'The :attribute must be at least :min.', 'file' => 'The :attribute must be at least :min kilobytes.', 'string' => 'The :attribute must be at least :min characters.', 'array' => 'The :attribute must have at least :min items.', ], 'multiple_of' => 'The :attribute must be a multiple of :value.', 'not_in' => 'The selected :attribute is invalid.', 'not_regex' => 'The :attribute format is invalid.', 'numeric' => 'The :attribute must be a number.', 'password' => 'The password is incorrect.', 'present' => 'The :attribute field must be present.', 'regex' => 'The :attribute format is invalid.', 'required' => 'The :attribute field is required.', 'required_if' => 'The :attribute field is required when :other is :value.', 'required_unless' => 'The :attribute field is required unless :other is in :values.', 'required_with' => 'The :attribute field is required when :values is present.', 'required_with_all' => 'The :attribute field is required when :values are present.', 'required_without' => 'The :attribute field is required when :values is not present.', 'required_without_all' => 'The :attribute field is required when none of :values are present.', 'same' => 'The :attribute and :other must match.', 'size' => [ 'numeric' => 'The :attribute must be :size.', 'file' => 'The :attribute must be :size kilobytes.', 'string' => 'The :attribute must be :size characters.', 'array' => 'The :attribute must contain :size items.', ], 'starts_with' => 'The :attribute must start with one of the following: :values.', 'string' => 'The :attribute must be a string.', 'timezone' => 'The :attribute must be a valid zone.', 'unique' => 'The :attribute has already been taken.', 'uploaded' => 'The :attribute failed to upload.', 'url' => 'The :attribute format is invalid.', 'uuid' => 'The :attribute must be a valid UUID.', /* |-------------------------------------------------------------------------- | Custom Validation Language Lines |-------------------------------------------------------------------------- | | Here you may specify custom validation messages for attributes using the | convention "attribute.rule" to name the lines. This makes it quick to | specify a specific custom language line for a given attribute rule. | */ 'custom' => [ 'attribute-name' => [ 'rule-name' => 'custom-message', ], ], /* |-------------------------------------------------------------------------- | Custom Validation Attributes |-------------------------------------------------------------------------- | | The following language lines are used to swap our attribute placeholder | with something more reader friendly such as "E-Mail Address" instead | of "email". This simply helps us make our message more expressive. | */ 'attributes' => [], ]; ================================================ FILE: package.json ================================================ { "private": true, "type": "module", "scripts": { "dev": "vite", "build": "vite build" }, "devDependencies": { "axios": "^1.6.4", "laravel-vite-plugin": "^1.0.0", "vite": "^5.0.0" } } ================================================ FILE: packages/Webkul/Activity/composer.json ================================================ { "name": "krayin/laravel-activity", "license": "MIT", "authors": [ { "name": "Jitendra Singh", "email": "jitendra@webkul.com" } ], "require": { "krayin/laravel-core": "^1.0", "krayin/laravel-user": "^1.0" }, "autoload": { "psr-4": { "Webkul\\Activity\\": "src/" } }, "extra": { "laravel": { "providers": [ "Webkul\\Activity\\Providers\\ActivityServiceProvider" ], "aliases": {} } }, "minimum-stability": "dev" } ================================================ FILE: packages/Webkul/Activity/src/Contracts/Activity.php ================================================ increments('id'); $table->string('title')->nullable(); $table->string('type'); $table->text('comment')->nullable(); $table->json('additional')->nullable(); $table->datetime('schedule_from')->nullable(); $table->datetime('schedule_to')->nullable(); $table->boolean('is_done')->default(0); $table->integer('user_id')->unsigned(); $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade'); $table->timestamps(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('activities'); } }; ================================================ FILE: packages/Webkul/Activity/src/Database/Migrations/2021_05_15_151855_create_activity_files_table.php ================================================ increments('id'); $table->string('name'); $table->string('path'); $table->integer('activity_id')->unsigned(); $table->foreign('activity_id')->references('id')->on('activities')->onDelete('cascade'); $table->timestamps(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('activity_files'); } }; ================================================ FILE: packages/Webkul/Activity/src/Database/Migrations/2021_07_28_142453_create_activity_participants_table.php ================================================ increments('id'); $table->integer('activity_id')->unsigned(); $table->foreign('activity_id')->references('id')->on('activities')->onDelete('cascade'); $table->integer('user_id')->nullable()->unsigned(); $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade'); $table->integer('person_id')->nullable()->unsigned(); $table->foreign('person_id')->references('id')->on('persons')->onDelete('cascade'); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('activity_participants'); } }; ================================================ FILE: packages/Webkul/Activity/src/Database/Migrations/2021_11_17_190943_add_location_column_in_activities_table.php ================================================ string('location')->nullable(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::table('activities', function (Blueprint $table) { $table->dropColumn('location'); }); } }; ================================================ FILE: packages/Webkul/Activity/src/Database/Migrations/2025_01_17_151632_alter_activities_table.php ================================================ dropForeign(['user_id']); $table->unsignedInteger('user_id')->nullable()->change(); $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade'); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::table('activities', function (Blueprint $table) { $tablePrefix = DB::getTablePrefix(); // Disable foreign key checks temporarily. DB::statement('SET FOREIGN_KEY_CHECKS=0'); // Drop the foreign key constraint using raw SQL. DB::statement('ALTER TABLE '.$tablePrefix.'activities DROP FOREIGN KEY activities_user_id_foreign'); // Drop the index. DB::statement('ALTER TABLE '.$tablePrefix.'activities DROP INDEX activities_user_id_foreign'); // Change the column to be non-nullable. $table->unsignedInteger('user_id')->nullable(false)->change(); $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade'); // Re-enable foreign key checks. DB::statement('SET FOREIGN_KEY_CHECKS=1'); }); } }; ================================================ FILE: packages/Webkul/Activity/src/Models/Activity.php ================================================ 'datetime', 'schedule_to' => 'datetime', ]; /** * The attributes that are mass assignable. * * @var array */ protected $fillable = [ 'title', 'type', 'location', 'comment', 'additional', 'schedule_from', 'schedule_to', 'is_done', 'user_id', ]; /** * Get the user that owns the activity. */ public function user() { return $this->belongsTo(UserProxy::modelClass()); } /** * The participants that belong to the activity. */ public function participants() { return $this->hasMany(ParticipantProxy::modelClass()); } /** * Get the file associated with the activity. */ public function files() { return $this->hasMany(FileProxy::modelClass(), 'activity_id'); } /** * The leads that belong to the activity. */ public function leads() { return $this->belongsToMany(LeadProxy::modelClass(), 'lead_activities'); } /** * The Person that belong to the activity. */ public function persons() { return $this->belongsToMany(PersonProxy::modelClass(), 'person_activities'); } /** * The leads that belong to the activity. */ public function products() { return $this->belongsToMany(ProductProxy::modelClass(), 'product_activities'); } /** * The Warehouse that belong to the activity. */ public function warehouses() { return $this->belongsToMany(WarehouseProxy::modelClass(), 'warehouse_activities'); } } ================================================ FILE: packages/Webkul/Activity/src/Models/ActivityProxy.php ================================================ path); } /** * Get image url for the product image. */ public function getUrlAttribute() { return $this->url(); } /** * Get the activity that owns the file. */ public function activity() { return $this->belongsTo(ActivityProxy::modelClass()); } /** * @return array */ public function toArray() { $array = parent::toArray(); $array['url'] = $this->url; return $array; } } ================================================ FILE: packages/Webkul/Activity/src/Models/FileProxy.php ================================================ belongsTo(ActivityProxy::modelClass()); } /** * Get the user that owns the participant. */ public function user() { return $this->belongsTo(UserProxy::modelClass()); } /** * Get the person that owns the participant. */ public function person() { return $this->belongsTo(PersonProxy::modelClass()); } } ================================================ FILE: packages/Webkul/Activity/src/Models/ParticipantProxy.php ================================================ app->bindIf(ActivityContract::class, Activity::class); $this->app->bindIf(FileContract::class, File::class); $this->app->bindIf(ParticipantContract::class, Participant::class); } /** * Bootstrap services. * * @return void */ public function boot(Router $router) { $this->loadMigrationsFrom(__DIR__.'/../Database/Migrations'); // Guarantee that Concord's model-proxy registry contains the Activity // bindings after all providers have booted. ModelProxy::modelClass() // looks up $concord->model($contract) — a separate store from the // Laravel container — so this registration is strictly necessary for // proxies like ActivityProxy used in Eloquent relationships. $this->app->booted(function () { $concord = app('concord'); if (! $concord->model(ActivityContract::class)) { $concord->registerModel(ActivityContract::class, Activity::class); } if (! $concord->model(FileContract::class)) { $concord->registerModel(FileContract::class, File::class); } if (! $concord->model(ParticipantContract::class)) { $concord->registerModel(ParticipantContract::class, Participant::class); } }); } } ================================================ FILE: packages/Webkul/Activity/src/Providers/ModuleServiceProvider.php ================================================ create([ 'name' => $data['name'] ?? $data['file']->getClientOriginalName(), 'path' => $data['file']->store('activities/'.$activity->id), 'activity_id' => $activity->id, ]); } if (! isset($data['participants'])) { return $activity; } foreach ($data['participants']['users'] ?? [] as $userId) { $activity->participants()->create([ 'user_id' => $userId, ]); } foreach ($data['participants']['persons'] ?? [] as $personId) { $activity->participants()->create([ 'person_id' => $personId, ]); } return $activity; } /** * Update pipeline. * * @param int $id * @param string $attribute * @return Activity */ public function update(array $data, $id, $attribute = 'id') { $activity = parent::update($data, $id); if (isset($data['participants'])) { $activity->participants()->delete(); foreach ($data['participants']['users'] ?? [] as $userId) { /** * In some cases, the component exists in an HTML form, and traditional HTML does not send an empty array. * Therefore, we need to check for an empty string. * This scenario occurs only when all participants are removed. */ if (empty($userId)) { break; } $activity->participants()->create([ 'user_id' => $userId, ]); } foreach ($data['participants']['persons'] ?? [] as $personId) { /** * In some cases, the component exists in an HTML form, and traditional HTML does not send an empty array. * Therefore, we need to check for an empty string. * This scenario occurs only when all participants are removed. */ if (empty($personId)) { break; } $activity->participants()->create([ 'person_id' => $personId, ]); } } return $activity; } /** * @param string $dateRange * @return mixed */ public function getActivities($dateRange) { $tablePrefix = \DB::getTablePrefix(); return $this->select( 'activities.id', 'activities.created_at', 'activities.title', 'activities.schedule_from as start', 'activities.schedule_to as end', 'users.name as user_name', ) ->addSelect(\DB::raw('IF('.$tablePrefix.'activities.is_done, "done", "") as class')) ->leftJoin('activity_participants', 'activities.id', '=', 'activity_participants.activity_id') ->leftJoin('users', 'activities.user_id', '=', 'users.id') ->whereIn('type', ['call', 'meeting', 'lunch']) ->whereBetween('activities.schedule_from', $dateRange) ->where(function ($query) { if ($userIds = bouncer()->getAuthorizedUserIds()) { $query->whereIn('activities.user_id', $userIds) ->orWhereIn('activity_participants.user_id', $userIds); } }) ->distinct() ->get(); } /** * @param string $startFrom * @param string $endFrom * @param array $participants * @param int $id * @return bool */ public function isDurationOverlapping($startFrom, $endFrom, $participants, $id) { $queryBuilder = $this->leftJoin('activity_participants', 'activities.id', '=', 'activity_participants.activity_id') ->where(function ($query) use ($startFrom, $endFrom) { $query->where([ ['activities.schedule_from', '<=', $startFrom], ['activities.schedule_to', '>=', $startFrom], ])->orWhere([ ['activities.schedule_from', '>=', $startFrom], ['activities.schedule_from', '<=', $endFrom], ]); }) ->where(function ($query) use ($participants) { if (is_null($participants)) { return; } if (isset($participants['users'])) { $query->orWhereIn('activity_participants.user_id', $participants['users']); } if (isset($participants['persons'])) { $query->orWhereIn('activity_participants.person_id', $participants['persons']); } }) ->groupBy('activities.id'); if (! is_null($id)) { $queryBuilder->where('activities.id', '!=', $id); } return $queryBuilder->count() ? true : false; } } ================================================ FILE: packages/Webkul/Activity/src/Repositories/FileRepository.php ================================================ entity ?? $model, 'activities')) { return; } if (! $model instanceof AttributeValue) { $activity = app(ActivityRepository::class)->create([ 'type' => 'system', 'title' => trans('admin::app.activities.created'), 'is_done' => 1, 'user_id' => auth()->check() ? auth()->id() : null, ]); $model->activities()->attach($activity->id); return; } static::logActivity($model); }); static::updated(function ($model) { if (! method_exists($model->entity ?? $model, 'activities')) { return; } static::logActivity($model); }); static::deleting(function ($model) { if (! method_exists($model->entity ?? $model, 'activities')) { return; } $model->activities()->delete(); }); } /** * Create activity. */ protected static function logActivity($model) { $customAttributes = []; if (method_exists($model, 'getCustomAttributes')) { $customAttributes = $model->getCustomAttributes()->pluck('code')->toArray(); } $updatedAttributes = static::getUpdatedAttributes($model); foreach ($updatedAttributes as $attributeCode => $attributeData) { if (in_array($attributeCode, $customAttributes)) { continue; } $attributeCode = $model->attribute?->name ?: $attributeCode; $activity = app(ActivityRepository::class)->create([ 'type' => 'system', 'title' => trans('admin::app.activities.updated', ['attribute' => $attributeCode]), 'is_done' => 1, 'additional' => json_encode([ 'attribute' => $attributeCode, 'new' => [ 'value' => $attributeData['new'], 'label' => static::getAttributeLabel($attributeData['new'], $model->attribute), ], 'old' => [ 'value' => $attributeData['old'], 'label' => static::getAttributeLabel($attributeData['old'], $model->attribute), ], ]), 'user_id' => auth()->id(), ]); if ($model instanceof AttributeValue) { $model->entity->activities()->attach($activity->id); } else { $model->activities()->attach($activity->id); } } } /** * Get attribute label. */ protected static function getAttributeLabel($value, $attribute) { return app(AttributeValueRepository::class)->getAttributeLabel($value, $attribute); } /** * Create activity. */ protected static function getUpdatedAttributes($model) { $updatedAttributes = []; foreach ($model->getDirty() as $key => $value) { if (in_array($key, [ 'id', 'attribute_id', 'entity_id', 'entity_type', 'updated_at', ])) { continue; } $newValue = static::decodeValueIfJson($value); $oldValue = static::decodeValueIfJson($model->getOriginal($key)); if ($newValue != $oldValue) { $updatedAttributes[$key] = [ 'new' => $newValue, 'old' => $oldValue, ]; } } return $updatedAttributes; } /** * Convert value if json. */ protected static function decodeValueIfJson($value) { if ( ! is_array($value) && json_decode($value, true) ) { $value = json_decode($value, true); } if (! is_array($value)) { return $value; } static::ksortRecursive($value); return $value; } /** * Sort array recursively. */ protected static function ksortRecursive(&$array) { if (! is_array($array)) { return; } ksort($array); foreach ($array as &$value) { if (! is_array($value)) { continue; } static::ksortRecursive($value); } } } ================================================ FILE: packages/Webkul/Admin/.gitignore ================================================ /node_modules /package-lock.json npm-debug.log /playwright-report /test-results ================================================ FILE: packages/Webkul/Admin/composer.json ================================================ { "name": "krayin/laravel-admin", "license": "MIT", "authors": [ { "name": "Jitendra Singh", "email": "jitendra@webkul.com" } ], "require": { "krayin/laravel-attribute": "^1.0", "krayin/laravel-contact": "^1.0", "krayin/laravel-core": "^1.0", "krayin/laravel-email": "^1.0", "krayin/laravel-lead": "^1.0", "krayin/laravel-product": "^1.0", "krayin/laravel-tag": "^1.0", "krayin/laravel-ui": "^1.0", "krayin/laravel-user": "^1.0" }, "autoload": { "psr-4": { "Webkul\\Admin\\": "src/" } }, "extra": { "laravel": { "providers": [ "Webkul\\Admin\\Providers\\AdminServiceProvider" ], "aliases": {} } }, "minimum-stability": "dev" } ================================================ FILE: packages/Webkul/Admin/package.json ================================================ { "private": true, "type": "module", "scripts": { "dev": "vite", "build": "vite build" }, "devDependencies": { "@playwright/test": "^1.50.1", "@types/node": "^22.7.8", "autoprefixer": "^10.4.16", "axios": "^1.7.4", "laravel-vite-plugin": "^1.0", "postcss": "^8.4.23", "tailwindcss": "^3.3.2", "vite": "^5.4.12", "vue": "^3.4.21" }, "dependencies": { "@vee-validate/i18n": "^4.9.1", "@vee-validate/rules": "^4.9.1", "@vitejs/plugin-vue": "^4.2.3", "chartjs-chart-funnel": "^4.2.1", "dompurify": "^3.1.7", "dotenv": "^16.4.7", "flatpickr": "^4.6.13", "mitt": "^3.0.1", "playwright": "^1.48.1", "readline-sync": "^1.4.10", "vee-validate": "^4.9.1", "vue-cal": "^4.9.0", "vue-flatpickr": "^2.3.0", "vuedraggable": "^4.1.0" } } ================================================ FILE: packages/Webkul/Admin/postcss.config.cjs ================================================ module.exports = ({ env }) => ({ plugins: [require("tailwindcss")(), require("autoprefixer")()], }); ================================================ FILE: packages/Webkul/Admin/src/Bouncer.php ================================================ guard('user')->check() && auth()->guard('user')->user()->role->permission_type == 'all') { return true; } else { if (! auth()->guard('user')->check() || ! auth()->guard('user')->user()->hasPermission($permission)) { return false; } } return true; } /** * Checks if user allowed or not for certain action * * @param string $permission * @return void */ public static function allow($permission) { if (! auth()->guard('user')->check() || ! auth()->guard('user')->user()->hasPermission($permission)) { abort(401, 'This action is unauthorized'); } } /** * This function will return user ids of current user's groups * * @return array|null */ public function getAuthorizedUserIds() { $user = auth()->guard('user')->user(); if ($user->view_permission == 'global') { return null; } if ($user->view_permission == 'group') { return app(UserRepository::class)->getCurrentUserGroupsUserIds(); } else { return [$user->id]; } } } ================================================ FILE: packages/Webkul/Admin/src/Config/acl.php ================================================ 'dashboard', 'name' => 'admin::app.layouts.dashboard', 'route' => 'admin.dashboard.index', 'sort' => 1, ], [ 'key' => 'leads', 'name' => 'admin::app.acl.leads', 'route' => 'admin.leads.index', 'sort' => 2, ], [ 'key' => 'leads.create', 'name' => 'admin::app.acl.create', 'route' => ['admin.leads.create', 'admin.leads.store'], 'sort' => 1, ], [ 'key' => 'leads.view', 'name' => 'admin::app.acl.view', 'route' => 'admin.leads.view', 'sort' => 2, ], [ 'key' => 'leads.edit', 'name' => 'admin::app.acl.edit', 'route' => ['admin.leads.edit', 'admin.leads.update', 'admin.leads.mass_update'], 'sort' => 3, ], [ 'key' => 'leads.delete', 'name' => 'admin::app.acl.delete', 'route' => ['admin.leads.delete', 'admin.leads.mass_delete'], 'sort' => 4, ], [ 'key' => 'quotes', 'name' => 'admin::app.acl.quotes', 'route' => 'admin.quotes.index', 'sort' => 3, ], [ 'key' => 'quotes.create', 'name' => 'admin::app.acl.create', 'route' => ['admin.quotes.create', 'admin.quotes.store'], 'sort' => 1, ], [ 'key' => 'quotes.edit', 'name' => 'admin::app.acl.edit', 'route' => ['admin.quotes.edit', 'admin.quotes.update'], 'sort' => 2, ], [ 'key' => 'quotes.print', 'name' => 'admin::app.acl.print', 'route' => 'admin.quotes.print', 'sort' => 3, ], [ 'key' => 'quotes.delete', 'name' => 'admin::app.acl.delete', 'route' => ['admin.quotes.delete', 'admin.quotes.mass_delete'], 'sort' => 4, ], [ 'key' => 'mail', 'name' => 'admin::app.acl.mail', 'route' => 'admin.mail.index', 'sort' => 4, ], [ 'key' => 'mail.inbox', 'name' => 'admin::app.acl.inbox', 'route' => 'admin.mail.index', 'sort' => 1, ], [ 'key' => 'mail.draft', 'name' => 'admin::app.acl.draft', 'route' => 'admin.mail.index', 'sort' => 2, ], [ 'key' => 'mail.outbox', 'name' => 'admin::app.acl.outbox', 'route' => 'admin.mail.index', 'sort' => 3, ], [ 'key' => 'mail.sent', 'name' => 'admin::app.acl.sent', 'route' => 'admin.mail.index', 'sort' => 4, ], [ 'key' => 'mail.trash', 'name' => 'admin::app.acl.trash', 'route' => 'admin.mail.index', 'sort' => 5, ], [ 'key' => 'mail.compose', 'name' => 'admin::app.acl.create', 'route' => ['admin.mail.store'], 'sort' => 6, ], [ 'key' => 'mail.view', 'name' => 'admin::app.acl.view', 'route' => 'admin.mail.view', 'sort' => 7, ], [ 'key' => 'mail.edit', 'name' => 'admin::app.acl.edit', 'route' => 'admin.mail.update', 'sort' => 8, ], [ 'key' => 'mail.delete', 'name' => 'admin::app.acl.delete', 'route' => ['admin.mail.delete', 'admin.mail.mass_delete'], 'sort' => 9, ], [ 'key' => 'activities', 'name' => 'admin::app.acl.activities', 'route' => 'admin.activities.index', 'sort' => 5, ], [ 'key' => 'activities.create', 'name' => 'admin::app.acl.create', 'route' => ['admin.activities.create', 'admin.activities.store'], 'sort' => 1, ], [ 'key' => 'activities.edit', 'name' => 'admin::app.acl.edit', 'route' => ['admin.activities.edit', 'admin.activities.update', 'admin.activities.mass_update'], 'sort' => 2, ], [ 'key' => 'activities.delete', 'name' => 'admin::app.acl.delete', 'route' => ['admin.activities.delete', 'admin.activities.mass_delete'], 'sort' => 3, ], [ 'key' => 'contacts', 'name' => 'admin::app.acl.contacts', 'route' => 'admin.contacts.users.index', 'sort' => 6, ], [ 'key' => 'contacts.persons', 'name' => 'admin::app.acl.persons', 'route' => 'admin.contacts.persons.index', 'sort' => 1, ], [ 'key' => 'contacts.persons.create', 'name' => 'admin::app.acl.create', 'route' => ['admin.contacts.persons.create', 'admin.contacts.persons.store'], 'sort' => 2, ], [ 'key' => 'contacts.persons.edit', 'name' => 'admin::app.acl.edit', 'route' => ['admin.contacts.persons.edit', 'admin.contacts.persons.update'], 'sort' => 3, ], [ 'key' => 'contacts.persons.delete', 'name' => 'admin::app.acl.delete', 'route' => ['admin.contacts.persons.delete', 'admin.contacts.persons.mass_delete'], 'sort' => 4, ], [ 'key' => 'contacts.persons.view', 'name' => 'admin::app.acl.view', 'route' => 'admin.contacts.persons.view', 'sort' => 5, ], [ 'key' => 'contacts.organizations', 'name' => 'admin::app.acl.organizations', 'route' => 'admin.contacts.organizations.index', 'sort' => 2, ], [ 'key' => 'contacts.organizations.create', 'name' => 'admin::app.acl.create', 'route' => ['admin.contacts.organizations.create', 'admin.contacts.organizations.store'], 'sort' => 1, ], [ 'key' => 'contacts.organizations.edit', 'name' => 'admin::app.acl.edit', 'route' => ['admin.contacts.organizations.edit', 'admin.contacts.organizations.update'], 'sort' => 2, ], [ 'key' => 'contacts.organizations.delete', 'name' => 'admin::app.acl.delete', 'route' => ['admin.contacts.organizations.delete', 'admin.contacts.organizations.mass_delete'], 'sort' => 3, ], [ 'key' => 'products', 'name' => 'admin::app.acl.products', 'route' => 'admin.products.index', 'sort' => 7, ], [ 'key' => 'products.create', 'name' => 'admin::app.acl.create', 'route' => ['admin.products.create', 'admin.products.store'], 'sort' => 1, ], [ 'key' => 'products.edit', 'name' => 'admin::app.acl.edit', 'route' => ['admin.products.edit', 'admin.products.update'], 'sort' => 2, ], [ 'key' => 'products.delete', 'name' => 'admin::app.acl.delete', 'route' => ['admin.products.delete', 'admin.products.mass_delete'], 'sort' => 3, ], [ 'key' => 'products.view', 'name' => 'admin::app.acl.view', 'route' => 'admin.products.view', 'sort' => 3, ], [ 'key' => 'settings', 'name' => 'admin::app.acl.settings', 'route' => 'admin.settings.index', 'sort' => 8, ], [ 'key' => 'settings.user', 'name' => 'admin::app.acl.user', 'route' => ['admin.settings.groups.index', 'admin.settings.roles.index', 'admin.settings.users.index'], 'sort' => 1, ], [ 'key' => 'settings.user.groups', 'name' => 'admin::app.acl.groups', 'route' => 'admin.settings.groups.index', 'sort' => 1, ], [ 'key' => 'settings.user.groups.create', 'name' => 'admin::app.acl.create', 'route' => ['admin.settings.groups.create', 'admin.settings.groups.store'], 'sort' => 1, ], [ 'key' => 'settings.user.groups.edit', 'name' => 'admin::app.acl.edit', 'route' => ['admin.settings.groups.edit', 'admin.settings.groups.update'], 'sort' => 2, ], [ 'key' => 'settings.user.groups.delete', 'name' => 'admin::app.acl.delete', 'route' => 'admin.settings.groups.delete', 'sort' => 3, ], [ 'key' => 'settings.user.roles', 'name' => 'admin::app.acl.roles', 'route' => 'admin.settings.roles.index', 'sort' => 2, ], [ 'key' => 'settings.user.roles.create', 'name' => 'admin::app.acl.create', 'route' => ['admin.settings.roles.create', 'admin.settings.roles.store'], 'sort' => 1, ], [ 'key' => 'settings.user.roles.edit', 'name' => 'admin::app.acl.edit', 'route' => ['admin.settings.roles.edit', 'admin.settings.roles.update'], 'sort' => 2, ], [ 'key' => 'settings.user.roles.delete', 'name' => 'admin::app.acl.delete', 'route' => 'admin.settings.roles.delete', 'sort' => 3, ], [ 'key' => 'settings.user.users', 'name' => 'admin::app.acl.users', 'route' => 'admin.settings.users.index', 'sort' => 3, ], [ 'key' => 'settings.user.users.create', 'name' => 'admin::app.acl.create', 'route' => ['admin.settings.users.create', 'admin.settings.users.store'], 'sort' => 1, ], [ 'key' => 'settings.user.users.edit', 'name' => 'admin::app.acl.edit', 'route' => ['admin.settings.users.edit', 'admin.settings.users.update', 'admin.settings.users.mass_update'], 'sort' => 2, ], [ 'key' => 'settings.user.users.delete', 'name' => 'admin::app.acl.delete', 'route' => ['admin.settings.users.delete', 'admin.settings.users.mass_delete'], 'sort' => 3, ], [ 'key' => 'settings.lead', 'name' => 'admin::app.acl.lead', 'route' => ['admin.settings.pipelines.index', 'admin.settings.sources.index', 'admin.settings.types.index'], 'sort' => 2, ], [ 'key' => 'settings.lead.pipelines', 'name' => 'admin::app.acl.pipelines', 'route' => 'admin.settings.pipelines.index', 'sort' => 1, ], [ 'key' => 'settings.lead.pipelines.create', 'name' => 'admin::app.acl.create', 'route' => ['admin.settings.pipelines.create', 'admin.settings.pipelines.store'], 'sort' => 1, ], [ 'key' => 'settings.lead.pipelines.edit', 'name' => 'admin::app.acl.edit', 'route' => ['admin.settings.pipelines.edit', 'admin.settings.pipelines.update'], 'sort' => 2, ], [ 'key' => 'settings.lead.pipelines.delete', 'name' => 'admin::app.acl.delete', 'route' => 'admin.settings.pipelines.delete', 'sort' => 3, ], [ 'key' => 'settings.lead.sources', 'name' => 'admin::app.acl.sources', 'route' => 'admin.settings.sources.index', 'sort' => 2, ], [ 'key' => 'settings.lead.sources.create', 'name' => 'admin::app.acl.create', 'route' => ['admin.settings.sources.store'], 'sort' => 1, ], [ 'key' => 'settings.lead.sources.edit', 'name' => 'admin::app.acl.edit', 'route' => ['admin.settings.sources.edit', 'admin.settings.sources.update'], 'sort' => 2, ], [ 'key' => 'settings.lead.sources.delete', 'name' => 'admin::app.acl.delete', 'route' => 'admin.settings.sources.delete', 'sort' => 3, ], [ 'key' => 'settings.lead.types', 'name' => 'admin::app.acl.types', 'route' => 'admin.settings.types.index', 'sort' => 3, ], [ 'key' => 'settings.lead.types.create', 'name' => 'admin::app.acl.create', 'route' => ['admin.settings.types.store'], 'sort' => 1, ], [ 'key' => 'settings.lead.types.edit', 'name' => 'admin::app.acl.edit', 'route' => ['admin.settings.types.edit', 'admin.settings.types.update'], 'sort' => 2, ], [ 'key' => 'settings.lead.types.delete', 'name' => 'admin::app.acl.delete', 'route' => 'admin.settings.types.delete', 'sort' => 3, ], [ 'key' => 'settings.inventory', 'name' => 'admin::app.acl.inventory', 'route' => ['admin.settings.warehouse.index'], 'sort' => 3, ], [ 'key' => 'settings.inventory.warehouse', 'name' => 'admin::app.acl.warehouses', 'route' => ['admin.settings.warehouse.index'], 'sort' => 1, ], [ 'key' => 'settings.inventory.warehouse.create', 'name' => 'admin::app.acl.create', 'route' => ['admin.settings.warehouse.create', 'admin.settings.warehouse.store'], 'sort' => 1, ], [ 'key' => 'settings.inventory.warehouse.edit', 'name' => 'admin::app.acl.edit', 'route' => ['admin.settings.warehouse.edit', 'admin.settings.warehouse.update'], 'sort' => 2, ], [ 'key' => 'settings.inventory.warehouse.delete', 'name' => 'admin::app.acl.delete', 'route' => 'admin.settings.warehouse.delete', 'sort' => 3, ], [ 'key' => 'settings.automation', 'name' => 'admin::app.acl.automation', 'route' => ['admin.settings.attributes.index', 'admin.settings.email_templates.index', 'admin.settings.workflows.index'], 'sort' => 4, ], [ 'key' => 'settings.automation.attributes', 'name' => 'admin::app.acl.attributes', 'route' => 'admin.settings.attributes.index', 'sort' => 1, ], [ 'key' => 'settings.automation.attributes.create', 'name' => 'admin::app.acl.create', 'route' => ['admin.settings.attributes.create', 'admin.settings.attributes.store'], 'sort' => 1, ], [ 'key' => 'settings.automation.attributes.edit', 'name' => 'admin::app.acl.edit', 'route' => ['admin.settings.attributes.edit', 'admin.settings.attributes.update', 'admin.settings.attributes.mass_update'], 'sort' => 2, ], [ 'key' => 'settings.automation.attributes.delete', 'name' => 'admin::app.acl.delete', 'route' => 'admin.settings.attributes.delete', 'sort' => 3, ], [ 'key' => 'settings.automation.email_templates', 'name' => 'admin::app.acl.email-templates', 'route' => 'admin.settings.email_templates.index', 'sort' => 2, ], [ 'key' => 'settings.automation.email_templates.create', 'name' => 'admin::app.acl.create', 'route' => ['admin.settings.email_templates.create', 'admin.settings.email_templates.store'], 'sort' => 1, ], [ 'key' => 'settings.automation.email_templates.edit', 'name' => 'admin::app.acl.edit', 'route' => ['admin.settings.email_templates.edit', 'admin.settings.email_templates.update'], 'sort' => 2, ], [ 'key' => 'settings.automation.email_templates.delete', 'name' => 'admin::app.acl.delete', 'route' => 'admin.settings.email_templates.delete', 'sort' => 3, ], [ 'key' => 'settings.automation.workflows', 'name' => 'admin::app.acl.workflows', 'route' => 'admin.settings.workflows.index', 'sort' => 3, ], [ 'key' => 'settings.automation.workflows.create', 'name' => 'admin::app.acl.create', 'route' => ['admin.settings.workflows.create', 'admin.settings.workflows.store'], 'sort' => 1, ], [ 'key' => 'settings.automation.workflows.edit', 'name' => 'admin::app.acl.edit', 'route' => ['admin.settings.workflows.edit', 'admin.settings.workflows.update'], 'sort' => 2, ], [ 'key' => 'settings.automation.workflows.delete', 'name' => 'admin::app.acl.delete', 'route' => 'admin.settings.workflows.delete', 'sort' => 3, ], [ 'key' => 'settings.automation.events', 'name' => 'admin::app.acl.event', 'route' => 'admin.settings.marketing.events.index', 'sort' => 4, ], [ 'key' => 'settings.automation.events.create', 'name' => 'admin::app.acl.create', 'route' => ['admin.settings.marketing.events.create', 'admin.settings.marketing.events.store'], 'sort' => 1, ], [ 'key' => 'settings.automation.events.edit', 'name' => 'admin::app.acl.edit', 'route' => ['admin.settings.marketing.events.edit', 'admin.settings.marketing.events.update'], 'sort' => 2, ], [ 'key' => 'settings.automation.events.delete', 'name' => 'admin::app.acl.delete', 'route' => ['admin.settings.marketing.events.delete', 'admin.settings.marketing.events.mass_delete'], 'sort' => 3, ], [ 'key' => 'settings.automation.campaigns', 'name' => 'admin::app.acl.campaigns', 'route' => 'admin.settings.marketing.campaigns.index', 'sort' => 5, ], [ 'key' => 'settings.automation.campaigns.create', 'name' => 'admin::app.acl.create', 'route' => ['admin.settings.marketing.campaigns.create', 'admin.settings.marketing.campaigns.store'], 'sort' => 1, ], [ 'key' => 'settings.automation.campaigns.edit', 'name' => 'admin::app.acl.edit', 'route' => ['admin.settings.marketing.campaigns.edit', 'admin.settings.marketing.campaigns.update'], 'sort' => 2, ], [ 'key' => 'settings.automation.campaigns.delete', 'name' => 'admin::app.acl.delete', 'route' => ['admin.settings.marketing.campaigns.delete', 'admin.settings.marketing.campaigns.mass_delete'], 'sort' => 3, ], [ 'key' => 'settings.automation.webhooks', 'name' => 'admin::app.acl.webhook', 'route' => 'admin.settings.webhooks.index', 'sort' => 6, ], [ 'key' => 'settings.automation.webhooks.create', 'name' => 'admin::app.acl.create', 'route' => ['admin.settings.webhooks.create', 'admin.settings.webhooks.store'], 'sort' => 1, ], [ 'key' => 'settings.automation.webhooks.edit', 'name' => 'admin::app.acl.edit', 'route' => ['admin.settings.webhooks.edit', 'admin.settings.webhooks.update'], 'sort' => 2, ], [ 'key' => 'settings.automation.webhooks.delete', 'name' => 'admin::app.acl.delete', 'route' => 'admin.settings.webhooks.delete', 'sort' => 3, ], [ 'key' => 'settings.automation.data_transfer', 'name' => 'admin::app.acl.data-transfer', 'route' => 'admin.settings.data_transfer.imports.index', 'sort' => 7, ], [ 'key' => 'settings.automation.data_transfer.imports', 'name' => 'admin::app.acl.imports', 'route' => 'admin.settings.data_transfer.imports.index', 'sort' => 1, ], [ 'key' => 'settings.automation.data_transfer.imports.create', 'name' => 'admin::app.acl.create', 'route' => 'admin.settings.data_transfer.imports.create', 'sort' => 1, ], [ 'key' => 'settings.automation.data_transfer.imports.edit', 'name' => 'admin::app.acl.edit', 'route' => 'admin.settings.data_transfer.imports.edit', 'sort' => 2, ], [ 'key' => 'settings.automation.data_transfer.imports.delete', 'name' => 'admin::app.acl.delete', 'route' => 'admin.settings.data_transfer.imports.delete', 'sort' => 3, ], [ 'key' => 'settings.automation.data_transfer.imports.import', 'name' => 'admin::app.acl.import', 'route' => 'admin.settings.data_transfer.imports.imports', 'sort' => 4, ], [ 'key' => 'settings.other_settings', 'name' => 'admin::app.acl.other-settings', 'route' => 'admin.settings.tags.index', 'sort' => 5, ], [ 'key' => 'settings.other_settings.tags', 'name' => 'admin::app.acl.tags', 'route' => 'admin.settings.tags.index', 'sort' => 1, ], [ 'key' => 'settings.other_settings.tags.create', 'name' => 'admin::app.acl.create', 'route' => ['admin.settings.tags.create', 'admin.settings.tags.store', 'admin.leads.tags.attach'], 'sort' => 1, ], [ 'key' => 'settings.other_settings.tags.edit', 'name' => 'admin::app.acl.edit', 'route' => ['admin.settings.tags.edit', 'admin.settings.tags.update'], 'sort' => 1, ], [ 'key' => 'settings.other_settings.tags.delete', 'name' => 'admin::app.acl.delete', 'route' => ['admin.settings.tags.delete', 'admin.settings.tags.mass_delete', 'admin.leads.tags.detach'], 'sort' => 2, ], [ 'key' => 'configuration', 'name' => 'admin::app.acl.configuration', 'route' => 'admin.configuration.index', 'sort' => 9, ], ]; ================================================ FILE: packages/Webkul/Admin/src/Config/attribute_entity_types.php ================================================ [ 'name' => 'admin::app.leads.index.title', 'repository' => 'Webkul\Lead\Repositories\LeadRepository', ], 'persons' => [ 'name' => 'admin::app.contacts.persons.index.title', 'repository' => 'Webkul\Contact\Repositories\PersonRepository', ], 'organizations' => [ 'name' => 'admin::app.contacts.organizations.index.title', 'repository' => 'Webkul\Contact\Repositories\OrganizationRepository', ], 'products' => [ 'name' => 'admin::app.products.index.title', 'repository' => 'Webkul\Product\Repositories\ProductRepository', ], 'quotes' => [ 'name' => 'admin::app.quotes.index.title', 'repository' => 'Webkul\Quote\Repositories\QuoteRepository', ], 'warehouses' => [ 'name' => 'admin::app.settings.warehouses.index.title', 'repository' => 'Webkul\Warehouse\Repositories\WarehouseRepository', ], ]; ================================================ FILE: packages/Webkul/Admin/src/Config/attribute_lookups.php ================================================ [ 'name' => 'Leads', 'repository' => 'Webkul\Lead\Repositories\LeadRepository', 'label_column' => 'title', ], 'lead_sources' => [ 'name' => 'Lead Sources', 'repository' => 'Webkul\Lead\Repositories\SourceRepository', ], 'lead_types' => [ 'name' => 'Lead Types', 'repository' => 'Webkul\Lead\Repositories\TypeRepository', ], 'lead_pipelines' => [ 'name' => 'Lead Pipelines', 'repository' => 'Webkul\Lead\Repositories\PipelineRepository', ], 'lead_pipeline_stages' => [ 'name' => 'Lead Pipeline Stages', 'repository' => 'Webkul\Lead\Repositories\StageRepository', ], 'users' => [ 'name' => 'Sales Owners', 'repository' => 'Webkul\User\Repositories\UserRepository', ], 'organizations' => [ 'name' => 'Organizations', 'repository' => 'Webkul\Contact\Repositories\OrganizationRepository', ], 'persons' => [ 'name' => 'Persons', 'repository' => 'Webkul\Contact\Repositories\PersonRepository', ], 'warehouses' => [ 'name' => 'Warehouses', 'repository' => 'Webkul\Warehouse\Repositories\WarehouseRepository', ], 'locations' => [ 'name' => 'Locations', 'repository' => 'Webkul\Warehouse\Repositories\LocationRepository', ], ]; ================================================ FILE: packages/Webkul/Admin/src/Config/core_config.php ================================================ 'general', 'name' => 'admin::app.configuration.index.general.title', 'info' => 'admin::app.configuration.index.general.info', 'sort' => 1, ], [ 'key' => 'general.general', 'name' => 'admin::app.configuration.index.general.general.title', 'info' => 'admin::app.configuration.index.general.general.info', 'icon' => 'icon-setting', 'sort' => 1, ], [ 'key' => 'general.general.locale_settings', 'name' => 'admin::app.configuration.index.general.general.locale-settings.title', 'info' => 'admin::app.configuration.index.general.general.locale-settings.title-info', 'sort' => 1, 'fields' => [ [ 'name' => 'locale', 'title' => 'admin::app.configuration.index.general.general.locale-settings.title', 'type' => 'select', 'default' => 'en', 'options' => 'Webkul\Core\Core@locales', ], ], ], [ 'key' => 'general.general.admin_logo', 'name' => 'admin::app.configuration.index.general.general.admin-logo.title', 'info' => 'admin::app.configuration.index.general.general.admin-logo.title-info', 'sort' => 2, 'fields' => [ [ 'name' => 'logo_image', 'title' => 'admin::app.configuration.index.general.general.admin-logo.logo-image', 'type' => 'image', 'validation' => 'mimes:bmp,jpeg,jpg,png,webp,svg', ], ], ], [ 'key' => 'general.settings', 'name' => 'admin::app.configuration.index.general.settings.title', 'info' => 'admin::app.configuration.index.general.settings.info', 'icon' => 'icon-configuration', 'sort' => 2, ], [ 'key' => 'general.settings.footer', 'name' => 'admin::app.configuration.index.general.settings.footer.title', 'info' => 'admin::app.configuration.index.general.settings.footer.info', 'sort' => 1, 'fields' => [ [ 'name' => 'label', 'title' => 'admin::app.configuration.index.general.settings.footer.powered-by', 'type' => 'editor', 'default' => 'Powered by Krayin, an open-source project by Webkul.', 'tinymce' => true, ], ], ], [ 'key' => 'general.settings.menu', 'name' => 'admin::app.configuration.index.general.settings.menu.title', 'info' => 'admin::app.configuration.index.general.settings.menu.info', 'sort' => 2, 'fields' => [ [ 'name' => 'dashboard', 'title' => 'admin::app.configuration.index.general.settings.menu.dashboard', 'type' => 'text', 'default' => 'Dashboard', 'validation' => 'max:20', ], [ 'name' => 'leads', 'title' => 'admin::app.configuration.index.general.settings.menu.leads', 'type' => 'text', 'default' => 'Leads', 'validation' => 'max:20', ], [ 'name' => 'quotes', 'title' => 'admin::app.configuration.index.general.settings.menu.quotes', 'type' => 'text', 'default' => 'Quotes', 'validation' => 'max:20', ], [ 'name' => 'mail.mail', 'title' => 'admin::app.configuration.index.general.settings.menu.mail', 'type' => 'text', 'default' => 'Mail', 'validation' => 'max:20', ], [ 'name' => 'mail.inbox', 'title' => 'admin::app.configuration.index.general.settings.menu.inbox', 'type' => 'text', 'default' => 'Inbox', 'validation' => 'max:20', ], [ 'name' => 'mail.draft', 'title' => 'admin::app.configuration.index.general.settings.menu.draft', 'type' => 'text', 'default' => 'Draft', 'validation' => 'max:20', ], [ 'name' => 'mail.outbox', 'title' => 'admin::app.configuration.index.general.settings.menu.outbox', 'type' => 'text', 'default' => 'Outbox', 'validation' => 'max:20', ], [ 'name' => 'mail.sent', 'title' => 'admin::app.configuration.index.general.settings.menu.sent', 'type' => 'text', 'default' => 'Sent', 'validation' => 'max:20', ], [ 'name' => 'mail.trash', 'title' => 'admin::app.configuration.index.general.settings.menu.trash', 'type' => 'text', 'default' => 'Trash', 'validation' => 'max:20', ], [ 'name' => 'activities', 'title' => 'admin::app.configuration.index.general.settings.menu.activities', 'type' => 'text', 'default' => 'Activities', 'validation' => 'max:20', ], [ 'name' => 'contacts.contacts', 'title' => 'admin::app.configuration.index.general.settings.menu.contacts', 'type' => 'text', 'default' => 'Contacts', 'validation' => 'max:20', ], [ 'name' => 'contacts.persons', 'title' => 'admin::app.configuration.index.general.settings.menu.persons', 'type' => 'text', 'default' => 'Persons', 'validation' => 'max:20', ], [ 'name' => 'contacts.organizations', 'title' => 'admin::app.configuration.index.general.settings.menu.organizations', 'type' => 'text', 'default' => 'Organizations', 'validation' => 'max:20', ], [ 'name' => 'products', 'title' => 'admin::app.configuration.index.general.settings.menu.products', 'type' => 'text', 'default' => 'Products', 'validation' => 'max:20', ], [ 'name' => 'settings', 'title' => 'admin::app.configuration.index.general.settings.menu.settings', 'type' => 'text', 'default' => 'Settings', 'validation' => 'max:20', ], [ 'name' => 'configuration', 'title' => 'admin::app.configuration.index.general.settings.menu.configuration', 'type' => 'text', 'default' => 'Configuration', 'validation' => 'max:20', ], ], ], [ 'key' => 'general.settings.menu_color', 'name' => 'admin::app.configuration.index.general.settings.menu-color.title', 'info' => 'admin::app.configuration.index.general.settings.menu-color.info', 'sort' => 3, 'fields' => [ [ 'name' => 'brand_color', 'title' => 'admin::app.configuration.index.general.settings.menu-color.brand-color', 'type' => 'color', 'default' => '#0E90D9', ], ], ], [ 'key' => 'general.magic_ai', 'name' => 'admin::app.configuration.index.magic-ai.title', 'info' => 'admin::app.configuration.index.magic-ai.info', 'icon' => 'icon-setting', 'sort' => 3, ], [ 'key' => 'general.magic_ai.settings', 'name' => 'admin::app.configuration.index.magic-ai.settings.title', 'info' => 'admin::app.configuration.index.magic-ai.settings.info', 'sort' => 1, 'fields' => [ [ 'name' => 'enable', 'title' => 'admin::app.configuration.index.magic-ai.settings.enable', 'type' => 'boolean', 'channel_based' => true, ], [ 'name' => 'api_key', 'title' => 'admin::app.configuration.index.magic-ai.settings.api-key', 'type' => 'password', 'depends' => 'enable:1', 'validation' => 'required_if:enable,1', 'info' => 'admin::app.configuration.index.magic-ai.settings.api-key-info', ], [ 'name' => 'model', 'title' => 'admin::app.configuration.index.magic-ai.settings.models.title', 'type' => 'select', 'channel_based' => true, 'depends' => 'enable:1', 'options' => [ [ 'title' => 'admin::app.configuration.index.magic-ai.settings.models.gpt-4o', 'value' => 'openai/chatgpt-4o-latest', ], [ 'title' => 'admin::app.configuration.index.magic-ai.settings.models.gpt-4o-mini', 'value' => 'openai/gpt-4o-mini', ], [ 'title' => 'admin::app.configuration.index.magic-ai.settings.models.gemini-2-0-flash-001', 'value' => 'google/gemini-2.0-flash-001', ], [ 'title' => 'admin::app.configuration.index.magic-ai.settings.models.deepseek-r1', 'value' => 'deepseek/deepseek-r1-distill-llama-8b', ], [ 'title' => 'admin::app.configuration.index.magic-ai.settings.models.llama-3-2-3b-instruct', 'value' => 'meta-llama/llama-3.2-3b-instruct', ], [ 'title' => 'admin::app.configuration.index.magic-ai.settings.models.grok-2-1212', 'value' => 'x-ai/grok-2-1212', ], ], ], [ 'name' => 'other_model', 'title' => 'admin::app.configuration.index.magic-ai.settings.other', 'type' => 'text', 'info' => 'admin::app.configuration.index.magic-ai.settings.other-model', 'default' => null, 'depends' => 'enable:1', ], ], ], [ 'key' => 'general.magic_ai.doc_generation', 'name' => 'admin::app.configuration.index.magic-ai.settings.doc-generation', 'info' => 'admin::app.configuration.index.magic-ai.settings.doc-generation-info', 'sort' => 2, 'fields' => [ [ 'name' => 'enabled', 'title' => 'admin::app.configuration.index.magic-ai.settings.enable', 'type' => 'boolean', ], ], ], /** * Email. */ [ 'key' => 'email', 'name' => 'admin::app.configuration.index.email.title', 'info' => 'admin::app.configuration.index.email.info', 'sort' => 2, ], [ 'key' => 'email.imap', 'name' => 'admin::app.configuration.index.email.imap.title', 'info' => 'admin::app.configuration.index.email.imap.info', 'icon' => 'icon-setting', 'sort' => 1, ], [ 'key' => 'email.imap.account', 'name' => 'admin::app.configuration.index.email.imap.account.title', 'info' => 'admin::app.configuration.index.email.imap.account.title-info', 'sort' => 1, 'fields' => [ [ 'name' => 'host', 'title' => 'admin::app.configuration.index.email.imap.account.host', 'type' => 'text', 'default' => config('imap.accounts.default.host'), ], [ 'name' => 'port', 'title' => 'admin::app.configuration.index.email.imap.account.port', 'type' => 'text', 'default' => config('imap.accounts.default.port'), ], [ 'name' => 'encryption', 'title' => 'admin::app.configuration.index.email.imap.account.encryption', 'type' => 'text', 'default' => config('imap.accounts.default.encryption'), ], [ 'name' => 'validate_cert', 'title' => 'admin::app.configuration.index.email.imap.account.validate-cert', 'type' => 'boolean', 'default' => config('imap.accounts.default.validate_cert'), ], [ 'name' => 'username', 'title' => 'admin::app.configuration.index.email.imap.account.username', 'type' => 'text', 'default' => config('imap.accounts.default.username'), ], [ 'name' => 'password', 'title' => 'admin::app.configuration.index.email.imap.account.password', 'type' => 'password', 'default' => config('imap.accounts.default.password'), ], ], ], ]; ================================================ FILE: packages/Webkul/Admin/src/Config/menu.php ================================================ 'dashboard', 'name' => 'admin::app.layouts.dashboard', 'route' => 'admin.dashboard.index', 'sort' => 1, 'icon-class' => 'icon-dashboard', ], /** * Leads. */ [ 'key' => 'leads', 'name' => 'admin::app.layouts.leads', 'route' => 'admin.leads.index', 'sort' => 2, 'icon-class' => 'icon-leads', ], /** * Quotes. */ [ 'key' => 'quotes', 'name' => 'admin::app.layouts.quotes', 'route' => 'admin.quotes.index', 'sort' => 3, 'icon-class' => 'icon-quote', ], /** * Emails. */ [ 'key' => 'mail', 'name' => 'admin::app.layouts.mail.title', 'route' => 'admin.mail.index', 'params' => ['route' => 'inbox'], 'sort' => 4, 'icon-class' => 'icon-mail', ], [ 'key' => 'mail.inbox', 'name' => 'admin::app.layouts.mail.inbox', 'route' => 'admin.mail.index', 'params' => ['route' => 'inbox'], 'sort' => 2, 'icon-class' => '', ], [ 'key' => 'mail.draft', 'name' => 'admin::app.layouts.mail.draft', 'route' => 'admin.mail.index', 'params' => ['route' => 'draft'], 'sort' => 3, 'icon-class' => '', ], [ 'key' => 'mail.outbox', 'name' => 'admin::app.layouts.mail.outbox', 'route' => 'admin.mail.index', 'params' => ['route' => 'outbox'], 'sort' => 4, 'icon-class' => '', ], [ 'key' => 'mail.sent', 'name' => 'admin::app.layouts.mail.sent', 'route' => 'admin.mail.index', 'params' => ['route' => 'sent'], 'sort' => 4, 'icon-class' => '', ], [ 'key' => 'mail.trash', 'name' => 'admin::app.layouts.mail.trash', 'route' => 'admin.mail.index', 'params' => ['route' => 'trash'], 'sort' => 5, 'icon-class' => '', ], /** * Activities. */ [ 'key' => 'activities', 'name' => 'admin::app.layouts.activities', 'route' => 'admin.activities.index', 'sort' => 5, 'icon-class' => 'icon-activity', ], /** * Contacts. */ [ 'key' => 'contacts', 'name' => 'admin::app.layouts.contacts', 'route' => 'admin.contacts.persons.index', 'sort' => 6, 'icon-class' => 'icon-contact', ], [ 'key' => 'contacts.persons', 'name' => 'admin::app.layouts.persons', 'route' => 'admin.contacts.persons.index', 'sort' => 1, 'icon-class' => '', ], [ 'key' => 'contacts.organizations', 'name' => 'admin::app.layouts.organizations', 'route' => 'admin.contacts.organizations.index', 'sort' => 2, 'icon-class' => '', ], /** * Products. */ [ 'key' => 'products', 'name' => 'admin::app.layouts.products', 'route' => 'admin.products.index', 'sort' => 7, 'icon-class' => 'icon-product', ], /** * Settings. */ [ 'key' => 'settings', 'name' => 'admin::app.layouts.settings', 'route' => 'admin.settings.index', 'sort' => 8, 'icon-class' => 'icon-setting', ], [ 'key' => 'settings.user', 'name' => 'admin::app.layouts.user', 'route' => 'admin.settings.groups.index', 'info' => 'admin::app.layouts.user-info', 'sort' => 1, 'icon-class' => 'icon-settings-group', ], [ 'key' => 'settings.user.groups', 'name' => 'admin::app.layouts.groups', 'info' => 'admin::app.layouts.groups-info', 'route' => 'admin.settings.groups.index', 'sort' => 1, 'icon-class' => 'icon-settings-group', ], [ 'key' => 'settings.user.roles', 'name' => 'admin::app.layouts.roles', 'info' => 'admin::app.layouts.roles-info', 'route' => 'admin.settings.roles.index', 'sort' => 2, 'icon-class' => 'icon-role', ], [ 'key' => 'settings.user.users', 'name' => 'admin::app.layouts.users', 'info' => 'admin::app.layouts.users-info', 'route' => 'admin.settings.users.index', 'sort' => 3, 'icon-class' => 'icon-user', ], [ 'key' => 'settings.lead', 'name' => 'admin::app.layouts.lead', 'info' => 'admin::app.layouts.lead-info', 'route' => 'admin.settings.pipelines.index', 'sort' => 2, 'icon-class' => '', ], [ 'key' => 'settings.lead.pipelines', 'name' => 'admin::app.layouts.pipelines', 'info' => 'admin::app.layouts.pipelines-info', 'route' => 'admin.settings.pipelines.index', 'sort' => 1, 'icon-class' => 'icon-settings-pipeline', ], [ 'key' => 'settings.lead.sources', 'name' => 'admin::app.layouts.sources', 'info' => 'admin::app.layouts.sources-info', 'route' => 'admin.settings.sources.index', 'sort' => 2, 'icon-class' => 'icon-settings-sources', ], [ 'key' => 'settings.lead.types', 'name' => 'admin::app.layouts.types', 'info' => 'admin::app.layouts.types-info', 'route' => 'admin.settings.types.index', 'sort' => 3, 'icon-class' => 'icon-settings-type', ], [ 'key' => 'settings.inventory', 'name' => 'admin::app.layouts.inventory', 'info' => 'admin::app.layouts.inventory-info', 'route' => 'admin.settings.pipelines.index', 'icon-class' => '', 'sort' => 2, ], [ 'key' => 'settings.inventory.warehouse', 'name' => 'admin::app.layouts.warehouses', 'info' => 'admin::app.layouts.warehouses-info', 'route' => 'admin.settings.warehouses.index', 'sort' => 1, 'icon-class' => 'icon-settings-warehouse', ], [ 'key' => 'settings.automation', 'name' => 'admin::app.layouts.automation', 'info' => 'admin::app.layouts.automation-info', 'route' => 'admin.settings.attributes.index', 'sort' => 3, 'icon-class' => '', ], [ 'key' => 'settings.automation.attributes', 'name' => 'admin::app.layouts.attributes', 'info' => 'admin::app.layouts.attributes-info', 'route' => 'admin.settings.attributes.index', 'sort' => 1, 'icon-class' => 'icon-attribute', ], [ 'key' => 'settings.automation.email_templates', 'name' => 'admin::app.layouts.email-templates', 'info' => 'admin::app.layouts.email-templates-info', 'route' => 'admin.settings.email_templates.index', 'sort' => 2, 'icon-class' => 'icon-settings-mail', ], [ 'key' => 'settings.automation.events', 'name' => 'admin::app.layouts.events', 'info' => 'admin::app.layouts.events-info', 'route' => 'admin.settings.marketing.events.index', 'sort' => 2, 'icon-class' => 'icon-calendar', ], [ 'key' => 'settings.automation.campaigns', 'name' => 'admin::app.layouts.campaigns', 'info' => 'admin::app.layouts.campaigns-info', 'route' => 'admin.settings.marketing.campaigns.index', 'sort' => 2, 'icon-class' => 'icon-note', ], [ 'key' => 'settings.automation.webhooks', 'name' => 'admin::app.layouts.webhooks', 'info' => 'admin::app.layouts.webhooks-info', 'route' => 'admin.settings.webhooks.index', 'sort' => 2, 'icon-class' => 'icon-settings-webhooks', ], [ 'key' => 'settings.automation.workflows', 'name' => 'admin::app.layouts.workflows', 'info' => 'admin::app.layouts.workflows-info', 'route' => 'admin.settings.workflows.index', 'sort' => 3, 'icon-class' => 'icon-settings-flow', ], [ 'key' => 'settings.automation.data_transfer', 'name' => 'admin::app.layouts.data_transfer', 'info' => 'admin::app.layouts.data_transfer_info', 'route' => 'admin.settings.data_transfer.imports.index', 'sort' => 4, 'icon-class' => 'icon-download', ], [ 'key' => 'settings.other_settings', 'name' => 'admin::app.layouts.other-settings', 'info' => 'admin::app.layouts.other-settings-info', 'route' => 'admin.settings.tags.index', 'sort' => 4, 'icon-class' => 'icon-settings', ], [ 'key' => 'settings.other_settings.tags', 'name' => 'admin::app.layouts.tags', 'info' => 'admin::app.layouts.tags-info', 'route' => 'admin.settings.tags.index', 'sort' => 1, 'icon-class' => 'icon-settings-tag', ], /** * Configuration. */ [ 'key' => 'configuration', 'name' => 'admin::app.layouts.configuration', 'route' => 'admin.configuration.index', 'sort' => 9, 'icon-class' => 'icon-configuration', ], ]; ================================================ FILE: packages/Webkul/Admin/src/DataGrids/Activity/ActivityDataGrid.php ================================================ distinct() ->select( 'activities.*', 'leads.id as lead_id', 'leads.title as lead_title', 'leads.lead_pipeline_id', 'users.id as created_by_id', 'users.name as created_by', ) ->leftJoin('activity_participants', 'activities.id', '=', 'activity_participants.activity_id') ->leftJoin('lead_activities', 'activities.id', '=', 'lead_activities.activity_id') ->leftJoin('leads', 'lead_activities.lead_id', '=', 'leads.id') ->leftJoin('users', 'activities.user_id', '=', 'users.id') ->whereIn('type', ['call', 'meeting', 'lunch']) ->where(function ($query) { if ($userIds = bouncer()->getAuthorizedUserIds()) { $query->whereIn('activities.user_id', $userIds) ->orWhereIn('activity_participants.user_id', $userIds); } })->groupBy('activities.id', 'leads.id', 'users.id'); $this->addFilter('id', 'activities.id'); $this->addFilter('title', 'activities.title'); $this->addFilter('schedule_from', 'activities.schedule_from'); $this->addFilter('created_by', 'users.name'); $this->addFilter('created_by_id', 'users.name'); $this->addFilter('created_at', 'activities.created_at'); $this->addFilter('lead_title', 'leads.title'); return $queryBuilder; } /** * Prepare columns. */ public function prepareColumns(): void { $this->addColumn([ 'index' => 'id', 'label' => trans('admin::app.activities.index.datagrid.id'), 'type' => 'integer', 'searchable' => true, 'filterable' => true, 'sortable' => true, ]); $this->addColumn([ 'index' => 'is_done', 'label' => trans('admin::app.activities.index.datagrid.is_done'), 'type' => 'string', 'dropdown_options' => $this->getBooleanDropdownOptions('yes_no'), 'searchable' => false, 'closure' => fn ($row) => view('admin::activities.datagrid.is-done', compact('row'))->render(), ]); $this->addColumn([ 'index' => 'title', 'label' => trans('admin::app.activities.index.datagrid.title'), 'type' => 'string', 'searchable' => true, 'filterable' => true, 'sortable' => true, ]); $this->addColumn([ 'index' => 'created_by_id', 'label' => trans('admin::app.activities.index.datagrid.created_by'), 'type' => 'string', 'searchable' => false, 'sortable' => true, 'filterable' => true, 'filterable_type' => 'searchable_dropdown', 'filterable_options' => [ 'repository' => UserRepository::class, 'column' => [ 'label' => 'name', 'value' => 'name', ], ], 'closure' => function ($row) { $route = urldecode(route('admin.settings.users.index', ['id[eq]' => $row->created_by_id])); return "".$row->created_by.''; }, ]); $this->addColumn([ 'index' => 'comment', 'label' => trans('admin::app.activities.index.datagrid.comment'), 'type' => 'string', ]); $this->addColumn([ 'index' => 'lead_title', 'label' => trans('admin::app.activities.index.datagrid.lead'), 'type' => 'string', 'searchable' => true, 'sortable' => true, 'filterable' => true, 'filterable_type' => 'searchable_dropdown', 'filterable_options' => [ 'repository' => LeadRepository::class, 'column' => [ 'label' => 'title', 'value' => 'title', ], ], 'closure' => function ($row) { if ($row->lead_title == null) { return "N/A"; } $route = urldecode(route('admin.leads.view', $row->lead_id)); return "".$row->lead_title.''; }, ]); $this->addColumn([ 'index' => 'type', 'label' => trans('admin::app.activities.index.datagrid.type'), 'type' => 'string', 'searchable' => false, 'filterable' => false, 'sortable' => true, 'closure' => fn ($row) => trans('admin::app.activities.index.datagrid.'.$row->type), ]); $this->addColumn([ 'index' => 'schedule_from', 'label' => trans('admin::app.activities.index.datagrid.schedule_from'), 'type' => 'date', 'sortable' => true, 'searchable' => true, 'filterable' => true, 'closure' => fn ($row) => core()->formatDate($row->schedule_from), ]); $this->addColumn([ 'index' => 'schedule_to', 'label' => trans('admin::app.activities.index.datagrid.schedule_to'), 'type' => 'date', 'sortable' => true, 'searchable' => true, 'filterable' => true, 'closure' => fn ($row) => core()->formatDate($row->schedule_to), ]); $this->addColumn([ 'index' => 'created_at', 'label' => trans('admin::app.activities.index.datagrid.created_at'), 'type' => 'date', 'sortable' => true, 'searchable' => true, 'filterable' => true, 'closure' => fn ($row) => core()->formatDate($row->created_at), ]); } /** * Prepare actions. */ public function prepareActions(): void { if (bouncer()->hasPermission('activities.edit')) { $this->addAction([ 'index' => 'edit', 'icon' => 'icon-edit', 'title' => trans('admin::app.activities.index.datagrid.edit'), 'method' => 'GET', 'url' => fn ($row) => route('admin.activities.edit', $row->id), ]); } if (bouncer()->hasPermission('activities.delete')) { $this->addAction([ 'index' => 'delete', 'icon' => 'icon-delete', 'title' => trans('admin::app.activities.index.datagrid.update'), 'method' => 'DELETE', 'url' => fn ($row) => route('admin.activities.delete', $row->id), ]); } } /** * Prepare mass actions. */ public function prepareMassActions(): void { $this->addMassAction([ 'icon' => 'icon-delete', 'title' => trans('admin::app.activities.index.datagrid.mass-delete'), 'method' => 'POST', 'url' => route('admin.activities.mass_delete'), ]); $this->addMassAction([ 'title' => trans('admin::app.activities.index.datagrid.mass-update'), 'url' => route('admin.activities.mass_update'), 'method' => 'POST', 'options' => [ [ 'label' => trans('admin::app.activities.index.datagrid.done'), 'value' => 1, ], [ 'label' => trans('admin::app.activities.index.datagrid.not-done'), 'value' => 0, ], ], ]); } } ================================================ FILE: packages/Webkul/Admin/src/DataGrids/Contact/OrganizationDataGrid.php ================================================ addSelect( 'organizations.id', 'organizations.name', 'organizations.address', 'organizations.created_at' ); if ($userIds = bouncer()->getAuthorizedUserIds()) { $queryBuilder->whereIn('organizations.user_id', $userIds); } $this->addFilter('id', 'organizations.id'); $this->addFilter('organization', 'organizations.name'); } /** * Add columns. */ public function prepareColumns(): void { $this->addColumn([ 'index' => 'id', 'label' => trans('admin::app.contacts.organizations.index.datagrid.id'), 'type' => 'integer', 'filterable' => true, 'sortable' => true, ]); $this->addColumn([ 'index' => 'name', 'label' => trans('admin::app.contacts.organizations.index.datagrid.name'), 'type' => 'string', 'searchable' => true, 'sortable' => true, 'filterable' => true, ]); $this->addColumn([ 'index' => 'persons_count', 'label' => trans('admin::app.contacts.organizations.index.datagrid.persons-count'), 'type' => 'string', 'searchable' => false, 'sortable' => false, 'filterable' => false, 'closure' => function ($row) { $personsCount = $this->personRepository->findWhere(['organization_id' => $row->id])->count(); return $personsCount; }, ]); $this->addColumn([ 'index' => 'created_at', 'label' => trans('admin::app.settings.tags.index.datagrid.created-at'), 'type' => 'date', 'searchable' => true, 'filterable' => true, 'filterable_type' => 'date_range', 'sortable' => true, 'closure' => fn ($row) => core()->formatDate($row->created_at), ]); } /** * Prepare actions. */ public function prepareActions(): void { if (bouncer()->hasPermission('contacts.organizations.edit')) { $this->addAction([ 'icon' => 'icon-edit', 'title' => trans('admin::app.contacts.organizations.index.datagrid.edit'), 'method' => 'GET', 'url' => fn ($row) => route('admin.contacts.organizations.edit', $row->id), ]); } if (bouncer()->hasPermission('contacts.organizations.delete')) { $this->addAction([ 'icon' => 'icon-delete', 'title' => trans('admin::app.contacts.organizations.index.datagrid.delete'), 'method' => 'DELETE', 'url' => fn ($row) => route('admin.contacts.organizations.delete', $row->id), ]); } } /** * Prepare mass actions. */ public function prepareMassActions(): void { $this->addMassAction([ 'icon' => 'icon-delete', 'title' => trans('admin::app.contacts.organizations.index.datagrid.delete'), 'method' => 'PUT', 'url' => route('admin.contacts.organizations.mass_delete'), ]); } } ================================================ FILE: packages/Webkul/Admin/src/DataGrids/Contact/PersonDataGrid.php ================================================ addSelect( 'persons.id', 'persons.name as person_name', 'persons.emails', 'persons.contact_numbers', 'organizations.name as organization', 'organizations.id as organization_id' ) ->leftJoin('organizations', 'persons.organization_id', '=', 'organizations.id'); if ($userIds = bouncer()->getAuthorizedUserIds()) { $queryBuilder->whereIn('persons.user_id', $userIds); } $this->addFilter('id', 'persons.id'); $this->addFilter('person_name', 'persons.name'); $this->addFilter('organization', 'organizations.name'); return $queryBuilder; } /** * Add columns. */ public function prepareColumns(): void { $this->addColumn([ 'index' => 'id', 'label' => trans('admin::app.contacts.persons.index.datagrid.id'), 'type' => 'integer', 'filterable' => true, 'sortable' => true, 'searchable' => true, ]); $this->addColumn([ 'index' => 'person_name', 'label' => trans('admin::app.contacts.persons.index.datagrid.name'), 'type' => 'string', 'sortable' => true, 'filterable' => true, 'searchable' => true, ]); $this->addColumn([ 'index' => 'emails', 'label' => trans('admin::app.contacts.persons.index.datagrid.emails'), 'type' => 'string', 'sortable' => false, 'filterable' => true, 'searchable' => true, 'closure' => fn ($row) => collect(json_decode($row->emails, true) ?? [])->pluck('value')->join(', '), ]); $this->addColumn([ 'index' => 'contact_numbers', 'label' => trans('admin::app.contacts.persons.index.datagrid.contact-numbers'), 'type' => 'string', 'sortable' => true, 'filterable' => true, 'searchable' => true, 'closure' => fn ($row) => collect(json_decode($row->contact_numbers, true) ?? [])->pluck('value')->join(', '), ]); $this->addColumn([ 'index' => 'organization', 'label' => trans('admin::app.contacts.persons.index.datagrid.organization-name'), 'type' => 'string', 'searchable' => true, 'filterable' => true, 'sortable' => true, 'filterable_type' => 'searchable_dropdown', 'filterable_options' => [ 'repository' => OrganizationRepository::class, 'column' => [ 'label' => 'name', 'value' => 'name', ], ], ]); } /** * Prepare actions. */ public function prepareActions(): void { if (bouncer()->hasPermission('contacts.persons.view')) { $this->addAction([ 'icon' => 'icon-eye', 'title' => trans('admin::app.contacts.persons.index.datagrid.view'), 'method' => 'GET', 'url' => function ($row) { return route('admin.contacts.persons.view', $row->id); }, ]); } if (bouncer()->hasPermission('contacts.persons.edit')) { $this->addAction([ 'icon' => 'icon-edit', 'title' => trans('admin::app.contacts.persons.index.datagrid.edit'), 'method' => 'GET', 'url' => function ($row) { return route('admin.contacts.persons.edit', $row->id); }, ]); } if (bouncer()->hasPermission('contacts.persons.delete')) { $this->addAction([ 'icon' => 'icon-delete', 'title' => trans('admin::app.contacts.persons.index.datagrid.delete'), 'method' => 'DELETE', 'url' => function ($row) { return route('admin.contacts.persons.delete', $row->id); }, ]); } } /** * Prepare mass actions. */ public function prepareMassActions(): void { if (bouncer()->hasPermission('contacts.persons.delete')) { $this->addMassAction([ 'icon' => 'icon-delete', 'title' => trans('admin::app.contacts.persons.index.datagrid.delete'), 'method' => 'POST', 'url' => route('admin.contacts.persons.mass_delete'), ]); } } } ================================================ FILE: packages/Webkul/Admin/src/DataGrids/Lead/LeadDataGrid.php ================================================ pipeline = $this->pipelineRepository->find(request('pipeline_id')); } else { $this->pipeline = $this->pipelineRepository->getDefaultPipeline(); } } /** * Prepare query builder. */ public function prepareQueryBuilder(): Builder { $tablePrefix = DB::getTablePrefix(); $queryBuilder = DB::table('leads') ->addSelect( 'leads.id', 'leads.title', 'leads.status', 'leads.lead_value', 'leads.expected_close_date', 'lead_sources.name as lead_source_name', 'lead_types.name as lead_type_name', 'leads.created_at', 'lead_pipeline_stages.name as stage', 'lead_tags.tag_id as tag_id', 'users.id as user_id', 'users.name as sales_person', 'persons.id as person_id', 'persons.name as person_name', 'tags.name as tag_name', 'lead_pipelines.rotten_days as pipeline_rotten_days', 'lead_pipeline_stages.code as stage_code', DB::raw('CASE WHEN DATEDIFF(NOW(),'.$tablePrefix.'leads.created_at) >='.$tablePrefix.'lead_pipelines.rotten_days THEN 1 ELSE 0 END as rotten_lead'), ) ->leftJoin('users', 'leads.user_id', '=', 'users.id') ->leftJoin('persons', 'leads.person_id', '=', 'persons.id') ->leftJoin('lead_types', 'leads.lead_type_id', '=', 'lead_types.id') ->leftJoin('lead_pipeline_stages', 'leads.lead_pipeline_stage_id', '=', 'lead_pipeline_stages.id') ->leftJoin('lead_sources', 'leads.lead_source_id', '=', 'lead_sources.id') ->leftJoin('lead_pipelines', 'leads.lead_pipeline_id', '=', 'lead_pipelines.id') ->leftJoin('lead_tags', 'leads.id', '=', 'lead_tags.lead_id') ->leftJoin('tags', 'tags.id', '=', 'lead_tags.tag_id') ->groupBy('leads.id') ->where('leads.lead_pipeline_id', $this->pipeline->id); if ($userIds = bouncer()->getAuthorizedUserIds()) { $queryBuilder->whereIn('leads.user_id', $userIds); } if (! is_null(request()->input('rotten_lead.in'))) { $queryBuilder->havingRaw($tablePrefix.'rotten_lead = '.request()->input('rotten_lead.in')); } $this->addFilter('id', 'leads.id'); $this->addFilter('user', 'leads.user_id'); $this->addFilter('sales_person', 'users.name'); $this->addFilter('lead_source_name', 'lead_sources.id'); $this->addFilter('lead_type_name', 'lead_types.id'); $this->addFilter('person_name', 'persons.name'); $this->addFilter('type', 'lead_pipeline_stages.code'); $this->addFilter('stage', 'lead_pipeline_stages.id'); $this->addFilter('tag_name', 'tags.name'); $this->addFilter('expected_close_date', 'leads.expected_close_date'); $this->addFilter('created_at', 'leads.created_at'); $this->addFilter('rotten_lead', DB::raw('DATEDIFF(NOW(), '.$tablePrefix.'leads.created_at) >= '.$tablePrefix.'lead_pipelines.rotten_days')); return $queryBuilder; } /** * Prepare columns. */ public function prepareColumns(): void { $this->addColumn([ 'index' => 'id', 'label' => trans('admin::app.leads.index.datagrid.id'), 'type' => 'integer', 'sortable' => true, 'filterable' => true, ]); $this->addColumn([ 'index' => 'sales_person', 'label' => trans('admin::app.leads.index.datagrid.sales-person'), 'type' => 'string', 'searchable' => false, 'sortable' => true, 'filterable' => true, 'filterable_type' => 'searchable_dropdown', 'filterable_options' => [ 'repository' => UserRepository::class, 'column' => [ 'label' => 'name', 'value' => 'name', ], ], ]); $this->addColumn([ 'index' => 'title', 'label' => trans('admin::app.leads.index.datagrid.subject'), 'type' => 'string', 'searchable' => true, 'sortable' => true, ]); $this->addColumn([ 'index' => 'lead_source_name', 'label' => trans('admin::app.leads.index.datagrid.source'), 'type' => 'string', 'searchable' => false, 'sortable' => true, 'filterable' => true, 'filterable_type' => 'dropdown', 'filterable_options' => $this->sourceRepository->all(['name as label', 'id as value'])->toArray(), ]); $this->addColumn([ 'index' => 'lead_value', 'label' => trans('admin::app.leads.index.datagrid.lead-value'), 'type' => 'string', 'sortable' => true, 'searchable' => false, 'filterable' => true, 'closure' => fn ($row) => core()->formatBasePrice($row->lead_value, 2), ]); $this->addColumn([ 'index' => 'lead_type_name', 'label' => trans('admin::app.leads.index.datagrid.lead-type'), 'type' => 'string', 'searchable' => false, 'sortable' => true, 'filterable' => true, 'filterable_type' => 'dropdown', 'filterable_options' => $this->typeRepository->all(['name as label', 'id as value'])->toArray(), ]); $this->addColumn([ 'index' => 'tag_name', 'label' => trans('admin::app.leads.index.datagrid.tag-name'), 'type' => 'string', 'searchable' => false, 'sortable' => true, 'filterable' => true, 'filterable_type' => 'searchable_dropdown', 'closure' => fn ($row) => $row->tag_name ?? '--', 'filterable_options' => [ 'repository' => TagRepository::class, 'column' => [ 'label' => 'name', 'value' => 'name', ], ], ]); $this->addColumn([ 'index' => 'person_name', 'label' => trans('admin::app.leads.index.datagrid.contact-person'), 'type' => 'string', 'searchable' => false, 'sortable' => true, 'filterable' => true, 'filterable_type' => 'searchable_dropdown', 'filterable_options' => [ 'repository' => PersonRepository::class, 'column' => [ 'label' => 'name', 'value' => 'name', ], ], 'closure' => function ($row) { $route = route('admin.contacts.persons.view', $row->person_id); return "".$row->person_name.''; }, ]); $this->addColumn([ 'index' => 'stage', 'label' => trans('admin::app.leads.index.datagrid.stage'), 'type' => 'string', 'searchable' => false, 'sortable' => true, 'filterable' => true, 'filterable_type' => 'dropdown', 'filterable_options' => $this->pipeline->stages->pluck('name', 'id') ->map(function ($name, $id) { return ['value' => $id, 'label' => $name]; }) ->values() ->all(), ]); $this->addColumn([ 'index' => 'rotten_lead', 'label' => trans('admin::app.leads.index.datagrid.rotten-lead'), 'type' => 'string', 'sortable' => true, 'searchable' => false, 'closure' => function ($row) { if (! $row->rotten_lead) { return trans('admin::app.leads.index.datagrid.no'); } if (in_array($row->stage_code, ['won', 'lost'])) { return trans('admin::app.leads.index.datagrid.no'); } return trans('admin::app.leads.index.datagrid.yes'); }, ]); $this->addColumn([ 'index' => 'expected_close_date', 'label' => trans('admin::app.leads.index.datagrid.date-to'), 'type' => 'date', 'searchable' => false, 'sortable' => true, 'filterable' => true, 'filterable_type' => 'date_range', 'closure' => function ($row) { if (! $row->expected_close_date) { return '--'; } return $row->expected_close_date; }, ]); $this->addColumn([ 'index' => 'created_at', 'label' => trans('admin::app.leads.index.datagrid.created-at'), 'type' => 'date', 'searchable' => false, 'sortable' => true, 'filterable' => true, 'filterable_type' => 'date_range', ]); } /** * Prepare actions. */ public function prepareActions(): void { if (bouncer()->hasPermission('leads.view')) { $this->addAction([ 'icon' => 'icon-eye', 'title' => trans('admin::app.leads.index.datagrid.view'), 'method' => 'GET', 'url' => fn ($row) => route('admin.leads.view', $row->id), ]); } if (bouncer()->hasPermission('leads.delete')) { $this->addAction([ 'icon' => 'icon-delete', 'title' => trans('admin::app.leads.index.datagrid.delete'), 'method' => 'delete', 'url' => fn ($row) => route('admin.leads.delete', $row->id), ]); } } /** * Prepare mass actions. */ public function prepareMassActions(): void { $this->addMassAction([ 'icon' => 'icon-delete', 'title' => trans('admin::app.leads.index.datagrid.mass-delete'), 'method' => 'POST', 'url' => route('admin.leads.mass_delete'), ]); $this->addMassAction([ 'title' => trans('admin::app.leads.index.datagrid.mass-update'), 'url' => route('admin.leads.mass_update'), 'method' => 'POST', 'options' => $this->pipeline->stages->map(fn ($stage) => [ 'label' => $stage->name, 'value' => $stage->id, ])->toArray(), ]); } } ================================================ FILE: packages/Webkul/Admin/src/DataGrids/Mail/EmailDataGrid.php ================================================ select( 'emails.id', 'emails.name', 'emails.from', 'emails.subject', 'emails.reply', 'emails.is_read', 'emails.created_at', 'tags.name as tags', DB::raw('COUNT(DISTINCT '.DB::getTablePrefix().'email_attachments.id) as attachments') ) ->leftJoin('email_attachments', 'emails.id', '=', 'email_attachments.email_id') ->leftJoin('email_tags', 'emails.id', '=', 'email_tags.email_id') ->leftJoin('tags', 'tags.id', '=', 'email_tags.tag_id') ->groupBy('emails.id') ->where('folders', 'like', '%"'.request('route').'"%') ->whereNull('parent_id'); $this->addFilter('id', 'emails.id'); $this->addFilter('name', 'emails.name'); $this->addFilter('tags', 'tags.name'); $this->addFilter('created_at', 'emails.created_at'); return $queryBuilder; } /** * Prepare Columns. */ public function prepareColumns(): void { $this->addColumn([ 'index' => 'attachments', 'label' => trans('admin::app.mail.index.datagrid.attachments'), 'type' => 'string', 'searchable' => false, 'filterable' => false, 'sortable' => false, 'closure' => fn ($row) => $row->attachments ? '' : '', ]); $this->addColumn([ 'index' => 'name', 'label' => trans('admin::app.mail.index.datagrid.from'), 'type' => 'string', 'sortable' => true, 'searchable' => true, 'filterable' => true, 'closure' => function ($row) { return $row->name ? trim($row->name, '"') : trim($row->from, '"'); }, ]); $this->addColumn([ 'index' => 'subject', 'label' => trans('admin::app.mail.index.datagrid.subject'), 'type' => 'string', 'sortable' => true, 'searchable' => true, 'filterable' => true, ]); $this->addColumn([ 'index' => 'reply', 'label' => trans('admin::app.mail.index.datagrid.content'), 'type' => 'string', 'sortable' => true, 'searchable' => true, 'filterable' => true, ]); $this->addColumn([ 'index' => 'tags', 'label' => trans('admin::app.mail.index.datagrid.tags'), 'type' => 'string', 'searchable' => false, 'sortable' => true, 'filterable' => true, 'filterable_type' => 'searchable_dropdown', 'closure' => function ($row) { if ($email = app(EmailRepository::class)->find($row->id)) { return $email->tags; } return '--'; }, 'filterable_options' => [ 'repository' => TagRepository::class, 'column' => [ 'label' => 'name', 'value' => 'name', ], ], ]); $this->addColumn([ 'index' => 'created_at', 'label' => trans('admin::app.mail.index.datagrid.date'), 'type' => 'date', 'searchable' => true, 'filterable' => true, 'filterable_type' => 'date_range', 'sortable' => true, 'closure' => function ($row) { return Carbon::parse($row->created_at)->isToday() ? Carbon::parse($row->created_at)->format('h:i A') : Carbon::parse($row->created_at)->format('M d'); }, ]); } /** * Prepare actions. */ public function prepareActions(): void { if (bouncer()->hasPermission('mail.view')) { $this->addAction([ 'index' => 'edit', 'icon' => request('route') == 'draft' ? 'icon-edit' : 'icon-eye', 'title' => request('route') == 'draft' ? trans('admin::app.mail.index.datagrid.edit') : trans('admin::app.mail.index.datagrid.view'), 'method' => 'GET', 'params' => [ 'type' => request('route') == 'trash' ? 'delete' : 'trash', ], 'url' => fn ($row) => route('admin.mail.view', [request('route'), $row->id]), ]); } if (bouncer()->hasPermission('mail.delete')) { $this->addAction([ 'index' => 'delete', 'icon' => 'icon-delete', 'title' => trans('admin::app.mail.index.datagrid.delete'), 'method' => 'DELETE', 'params' => [ 'type' => request('route') == 'trash' ? 'delete' : 'trash', ], 'url' => fn ($row) => route('admin.mail.delete', $row->id), ]); } } /** * Prepare mass actions. */ public function prepareMassActions(): void { if (request('route') == 'trash') { $this->addMassAction([ 'title' => trans('admin::app.mail.index.datagrid.move-to-inbox'), 'method' => 'POST', 'url' => route('admin.mail.mass_update', ['folders' => ['inbox']]), 'options' => [ [ 'value' => 'trash', 'label' => trans('admin::app.mail.index.datagrid.move-to-inbox'), ], ], ]); } $this->addMassAction([ 'icon' => 'icon-delete', 'title' => request('route') == 'trash' ? trans('admin::app.mail.index.datagrid.delete') : trans('admin::app.mail.index.datagrid.move-to-trash'), 'method' => 'POST', 'url' => route('admin.mail.mass_delete', [ 'type' => request('route') == 'trash' ? 'delete' : 'trash', ]), ]); } } ================================================ FILE: packages/Webkul/Admin/src/DataGrids/Product/ProductDataGrid.php ================================================ leftJoin('product_inventories', 'products.id', '=', 'product_inventories.product_id') ->leftJoin('product_tags', 'products.id', '=', 'product_tags.product_id') ->leftJoin('tags', 'tags.id', '=', 'product_tags.tag_id') ->select( 'products.id', 'products.sku', 'products.name', 'products.price', 'tags.name as tag_name', ) ->addSelect(DB::raw('SUM('.$tablePrefix.'product_inventories.in_stock) as total_in_stock')) ->addSelect(DB::raw('SUM('.$tablePrefix.'product_inventories.allocated) as total_allocated')) ->addSelect(DB::raw('SUM('.$tablePrefix.'product_inventories.in_stock - '.$tablePrefix.'product_inventories.allocated) as total_on_hand')) ->groupBy('products.id'); if (request()->route('id')) { $queryBuilder->where('product_inventories.warehouse_id', request()->route('id')); } $this->addFilter('id', 'products.id'); $this->addFilter('sku', 'products.sku'); $this->addFilter('name', 'products.name'); $this->addFilter('price', 'products.price'); $this->addFilter('total_in_stock', DB::raw('SUM('.$tablePrefix.'product_inventories.in_stock')); $this->addFilter('total_allocated', DB::raw('SUM('.$tablePrefix.'product_inventories.allocated')); $this->addFilter('total_on_hand', DB::raw('SUM('.$tablePrefix.'product_inventories.in_stock - '.$tablePrefix.'product_inventories.allocated')); $this->addFilter('tag_name', 'tags.name'); return $queryBuilder; } /** * Add columns. */ public function prepareColumns(): void { $this->addColumn([ 'index' => 'sku', 'label' => trans('admin::app.products.index.datagrid.sku'), 'type' => 'string', 'sortable' => true, 'searchable' => true, 'filterable' => true, ]); $this->addColumn([ 'index' => 'name', 'label' => trans('admin::app.products.index.datagrid.name'), 'type' => 'string', 'sortable' => true, 'searchable' => true, 'filterable' => true, ]); $this->addColumn([ 'index' => 'price', 'label' => trans('admin::app.products.index.datagrid.price'), 'type' => 'string', 'sortable' => true, 'searchable' => true, 'filterable' => true, 'closure' => fn ($row) => round($row->price, 2), ]); $this->addColumn([ 'index' => 'total_in_stock', 'label' => trans('admin::app.products.index.datagrid.in-stock'), 'type' => 'string', 'sortable' => true, ]); $this->addColumn([ 'index' => 'total_allocated', 'label' => trans('admin::app.products.index.datagrid.allocated'), 'type' => 'string', 'sortable' => true, ]); $this->addColumn([ 'index' => 'total_on_hand', 'label' => trans('admin::app.products.index.datagrid.on-hand'), 'type' => 'string', 'sortable' => true, ]); $this->addColumn([ 'index' => 'tag_name', 'label' => trans('admin::app.products.index.datagrid.tag-name'), 'type' => 'string', 'searchable' => false, 'sortable' => true, 'filterable' => true, 'filterable_type' => 'searchable_dropdown', 'closure' => fn ($row) => $row->tag_name ?? '--', 'filterable_options' => [ 'repository' => TagRepository::class, 'column' => [ 'label' => 'name', 'value' => 'name', ], ], ]); } /** * Prepare actions. */ public function prepareActions(): void { if (bouncer()->hasPermission('products.view')) { $this->addAction([ 'index' => 'view', 'icon' => 'icon-eye', 'title' => trans('admin::app.products.index.datagrid.view'), 'method' => 'GET', 'url' => fn ($row) => route('admin.products.view', $row->id), ]); } if (bouncer()->hasPermission('products.edit')) { $this->addAction([ 'index' => 'edit', 'icon' => 'icon-edit', 'title' => trans('admin::app.products.index.datagrid.edit'), 'method' => 'GET', 'url' => fn ($row) => route('admin.products.edit', $row->id), ]); } if (bouncer()->hasPermission('products.delete')) { $this->addAction([ 'index' => 'delete', 'icon' => 'icon-delete', 'title' => trans('admin::app.products.index.datagrid.delete'), 'method' => 'DELETE', 'url' => fn ($row) => route('admin.products.delete', $row->id), ]); } } /** * Prepare mass actions. */ public function prepareMassActions(): void { $this->addMassAction([ 'icon' => 'icon-delete', 'title' => trans('admin::app.products.index.datagrid.delete'), 'method' => 'POST', 'url' => route('admin.products.mass_delete'), ]); } } ================================================ FILE: packages/Webkul/Admin/src/DataGrids/Quote/QuoteDataGrid.php ================================================ addSelect( 'quotes.id', 'quotes.subject', 'quotes.expired_at', 'quotes.sub_total', 'quotes.discount_amount', 'quotes.tax_amount', 'quotes.adjustment_amount', 'quotes.grand_total', 'quotes.created_at', 'users.id as user_id', 'users.name as sales_person', 'persons.id as person_id', 'persons.name as person_name', 'quotes.expired_at as expired_quotes' ) ->leftJoin('users', 'quotes.user_id', '=', 'users.id') ->leftJoin('persons', 'quotes.person_id', '=', 'persons.id'); if ($userIds = bouncer()->getAuthorizedUserIds()) { $queryBuilder->whereIn('quotes.user_id', $userIds); } $this->addFilter('id', 'quotes.id'); $this->addFilter('user', 'quotes.user_id'); $this->addFilter('sales_person', 'users.name'); $this->addFilter('person_name', 'persons.name'); $this->addFilter('expired_at', 'quotes.expired_at'); $this->addFilter('created_at', 'quotes.created_at'); if (request()->input('expired_quotes.in') == 1) { $this->addFilter('expired_quotes', DB::raw('DATEDIFF(NOW(), '.$tablePrefix.'quotes.expired_at) >= '.$tablePrefix.'NOW()')); } else { $this->addFilter('expired_quotes', DB::raw('DATEDIFF(NOW(), '.$tablePrefix.'quotes.expired_at) < '.$tablePrefix.'NOW()')); } return $queryBuilder; } /** * Prepare columns. */ public function prepareColumns(): void { $this->addColumn([ 'index' => 'subject', 'label' => trans('admin::app.quotes.index.datagrid.subject'), 'type' => 'string', 'filterable' => true, 'searchable' => true, 'sortable' => true, ]); $this->addColumn([ 'index' => 'sales_person', 'label' => trans('admin::app.quotes.index.datagrid.sales-person'), 'type' => 'string', 'sortable' => true, 'searchable' => true, 'filterable' => true, 'filterable_type' => 'searchable_dropdown', 'filterable_options' => [ 'repository' => UserRepository::class, 'column' => [ 'label' => 'name', 'value' => 'name', ], ], ]); $this->addColumn([ 'index' => 'person_name', 'label' => trans('admin::app.quotes.index.datagrid.person'), 'type' => 'string', 'sortable' => true, 'searchable' => true, 'filterable' => true, 'filterable_type' => 'searchable_dropdown', 'filterable_options' => [ 'repository' => PersonRepository::class, 'column' => [ 'label' => 'name', 'value' => 'name', ], ], 'closure' => function ($row) { $route = route('admin.contacts.persons.view', $row->person_id); return "".$row->person_name.''; }, ]); $this->addColumn([ 'index' => 'sub_total', 'label' => trans('admin::app.quotes.index.datagrid.subtotal'), 'type' => 'string', 'sortable' => true, 'filterable' => true, 'closure' => fn ($row) => core()->formatBasePrice($row->sub_total, 2), ]); $this->addColumn([ 'index' => 'discount_amount', 'label' => trans('admin::app.quotes.index.datagrid.discount'), 'type' => 'string', 'sortable' => true, 'filterable' => true, 'closure' => fn ($row) => core()->formatBasePrice($row->discount_amount, 2), ]); $this->addColumn([ 'index' => 'tax_amount', 'label' => trans('admin::app.quotes.index.datagrid.tax'), 'type' => 'string', 'filterable' => true, 'sortable' => true, 'closure' => fn ($row) => core()->formatBasePrice($row->tax_amount, 2), ]); $this->addColumn([ 'index' => 'adjustment_amount', 'label' => trans('admin::app.quotes.index.datagrid.adjustment'), 'type' => 'string', 'sortable' => true, 'filterable' => false, 'closure' => fn ($row) => core()->formatBasePrice($row->adjustment_amount, 2), ]); $this->addColumn([ 'index' => 'grand_total', 'label' => trans('admin::app.quotes.index.datagrid.grand-total'), 'type' => 'string', 'sortable' => true, 'filterable' => true, 'closure' => fn ($row) => core()->formatBasePrice($row->grand_total, 2), ]); $this->addColumn([ 'index' => 'expired_at', 'label' => trans('admin::app.quotes.index.datagrid.expired-at'), 'type' => 'date', 'searchable' => false, 'sortable' => true, 'filterable' => true, 'closure' => fn ($row) => core()->formatDate($row->expired_at, 'd M Y'), ]); $this->addColumn([ 'index' => 'created_at', 'label' => trans('admin::app.quotes.index.datagrid.created-at'), 'type' => 'date', 'searchable' => false, 'sortable' => true, 'filterable' => true, 'closure' => fn ($row) => core()->formatDate($row->created_at), ]); } /** * Prepare actions. */ public function prepareActions(): void { if (bouncer()->hasPermission('quotes.edit')) { $this->addAction([ 'index' => 'edit', 'icon' => 'icon-edit', 'title' => trans('admin::app.quotes.index.datagrid.edit'), 'method' => 'GET', 'url' => fn ($row) => route('admin.quotes.edit', $row->id), ]); } if (bouncer()->hasPermission('quotes.print')) { $this->addAction([ 'index' => 'print', 'icon' => 'icon-print', 'title' => trans('admin::app.quotes.index.datagrid.print'), 'method' => 'GET', 'url' => fn ($row) => route('admin.quotes.print', $row->id), ]); } if (bouncer()->hasPermission('quotes.delete')) { $this->addAction([ 'index' => 'delete', 'icon' => 'icon-delete', 'title' => trans('admin::app.quotes.index.datagrid.delete'), 'method' => 'DELETE', 'url' => fn ($row) => route('admin.quotes.delete', $row->id), ]); } } /** * Prepare mass actions. */ public function prepareMassActions(): void { $this->addMassAction([ 'icon' => 'icon-delete', 'title' => trans('admin::app.quotes.index.datagrid.delete'), 'method' => 'POST', 'url' => route('admin.quotes.mass_delete'), ]); $this->addMassAction([ 'icon' => 'icon-delete', 'title' => trans('admin::app.quotes.index.datagrid.delete'), 'method' => 'POST', 'url' => route('admin.quotes.mass_delete'), ]); } } ================================================ FILE: packages/Webkul/Admin/src/DataGrids/Settings/AttributeDataGrid.php ================================================ select( 'attributes.id', 'attributes.code', 'attributes.name', 'attributes.type', 'attributes.entity_type', 'attributes.is_user_defined as attribute_type' ) ->where('entity_type', '<>', 'locations'); $this->addFilter('id', 'attributes.id'); $this->addFilter('type', 'attributes.type'); $this->addFilter('attribute_type', 'attributes.is_user_defined'); return $queryBuilder; } /** * Prepare columns. */ public function prepareColumns(): void { $this->addColumn([ 'index' => 'id', 'label' => trans('admin::app.settings.attributes.index.datagrid.id'), 'type' => 'string', 'filterable' => true, 'sortable' => true, ]); $this->addColumn([ 'index' => 'code', 'label' => trans('admin::app.settings.attributes.index.datagrid.code'), 'type' => 'string', 'sortable' => true, 'searchable' => true, 'filterable' => true, ]); $this->addColumn([ 'index' => 'name', 'label' => trans('admin::app.settings.attributes.index.datagrid.name'), 'type' => 'string', 'sortable' => true, 'searchable' => true, 'filterable' => true, ]); $this->addColumn([ 'index' => 'entity_type', 'label' => trans('admin::app.settings.attributes.index.datagrid.entity-type'), 'type' => 'string', 'sortable' => true, 'searchable' => false, 'filterable' => true, 'filterable_type' => 'dropdown', 'filterable_options' => app(AttributeRepository::class) ->select('entity_type as label', 'entity_type as value') ->distinct() ->get() ->map(function ($item) { $item->label = trans('admin::app.settings.attributes.index.datagrid.entity-types.'.$item->label); return $item; }) ->toArray(), 'closure' => fn ($row) => ucfirst($row->entity_type), ]); $this->addColumn([ 'index' => 'type', 'label' => trans('admin::app.settings.attributes.index.datagrid.type'), 'type' => 'string', 'sortable' => true, 'filterable' => true, 'filterable_type' => 'dropdown', 'filterable_options' => app(AttributeRepository::class) ->select('type as label', 'type as value') ->distinct() ->get() ->map(function ($item) { $item->label = trans('admin::app.settings.attributes.index.datagrid.types.'.$item->label); return $item; }) ->toArray(), ]); $this->addColumn([ 'index' => 'attribute_type', 'label' => trans('admin::app.settings.attributes.index.datagrid.is-default'), 'type' => 'boolean', 'searchable' => true, 'filterable' => false, 'sortable' => true, 'closure' => fn ($value) => trans('admin::app.settings.attributes.index.datagrid.'.($value->attribute_type ? 'no' : 'yes')), ]); } /** * Prepare actions. */ public function prepareActions(): void { if (bouncer()->hasPermission('settings.automation.attributes.edit')) { $this->addAction([ 'icon' => 'icon-edit', 'title' => trans('admin::app.settings.attributes.index.datagrid.edit'), 'method' => 'GET', 'url' => fn ($row) => route('admin.settings.attributes.edit', $row->id), ]); } if (bouncer()->hasPermission('settings.automation.attributes.delete')) { $this->addAction([ 'icon' => 'icon-delete', 'title' => trans('admin::app.settings.attributes.index.datagrid.delete'), 'method' => 'DELETE', 'url' => fn ($row) => route('admin.settings.attributes.delete', $row->id), ]); } } /** * Prepare mass actions. */ public function prepareMassActions(): void { $this->addMassAction([ 'icon' => 'icon-delete', 'title' => trans('admin::app.settings.attributes.index.datagrid.delete'), 'method' => 'POST', 'url' => route('admin.settings.attributes.mass_delete'), ]); } } ================================================ FILE: packages/Webkul/Admin/src/DataGrids/Settings/DataTransfer/ImportDataGrid.php ================================================ select( 'id', 'state', 'file_path', 'error_file_path', 'started_at', 'completed_at', 'type', 'summary', ); } /** * Prepare Columns. */ public function prepareColumns(): void { $this->addColumn([ 'index' => 'id', 'label' => trans('admin::app.settings.data-transfer.imports.index.datagrid.id'), 'type' => 'integer', 'filterable' => true, 'sortable' => true, ]); $this->addColumn([ 'index' => 'type', 'label' => trans('admin::app.settings.data-transfer.imports.index.datagrid.type'), 'type' => 'string', 'filterable' => true, 'sortable' => true, ]); $this->addColumn([ 'index' => 'state', 'label' => trans('admin::app.settings.data-transfer.imports.index.datagrid.state'), 'type' => 'string', 'filterable' => true, 'sortable' => true, ]); $this->addColumn([ 'index' => 'file_path', 'label' => trans('admin::app.settings.data-transfer.imports.index.datagrid.uploaded-file'), 'type' => 'string', 'closure' => function ($row) { return ''.$row->file_path.''; }, ]); $this->addColumn([ 'index' => 'error_file_path', 'label' => trans('admin::app.settings.data-transfer.imports.index.datagrid.error-file'), 'type' => 'string', 'closure' => function ($row) { if (empty($row->error_file_path)) { return ''; } return ''.$row->error_file_path.''; }, ]); $this->addColumn([ 'index' => 'started_at', 'label' => trans('admin::app.settings.data-transfer.imports.index.datagrid.started-at'), 'type' => 'date', 'filterable' => true, 'filterable_type' => 'date_range', 'sortable' => true, ]); $this->addColumn([ 'index' => 'completed_at', 'label' => trans('admin::app.settings.data-transfer.imports.index.datagrid.completed-at'), 'type' => 'date', 'filterable' => true, 'filterable_type' => 'date_range', 'sortable' => true, ]); $this->addColumn([ 'index' => 'summary', 'label' => trans('admin::app.settings.data-transfer.imports.index.datagrid.summary'), 'type' => 'string', 'closure' => function ($row) { if (empty($row->summary)) { return ''; } $summary = json_decode($row->summary, true); $stats = []; foreach ($summary as $type => $value) { $stats[] = trans('admin::app.settings.data-transfer.imports.index.datagrid.'.$type).': '.$summary[$type]; } return implode(', ', $stats); }, ]); } /** * Prepare actions. */ public function prepareActions(): void { if (bouncer()->hasPermission('settings.automation.data_transfer.imports.import')) { $this->addAction([ 'index' => 'import', 'icon' => 'icon-import', 'title' => trans('admin::app.settings.data-transfer.imports.index.datagrid.import'), 'method' => 'GET', 'url' => function ($row) { return route('admin.settings.data_transfer.imports.import', $row->id); }, ]); } if (bouncer()->hasPermission('settings.automation.data_transfer.imports.edit')) { $this->addAction([ 'index' => 'edit', 'icon' => 'icon-edit', 'title' => trans('admin::app.settings.data-transfer.imports.index.datagrid.edit'), 'method' => 'GET', 'url' => function ($row) { return route('admin.settings.data_transfer.imports.edit', $row->id); }, ]); } if (bouncer()->hasPermission('settings.automation.data_transfer.imports.delete')) { $this->addAction([ 'index' => 'delete', 'icon' => 'icon-delete', 'title' => trans('admin::app.settings.data-transfer.imports.index.datagrid.delete'), 'method' => 'DELETE', 'url' => function ($row) { return route('admin.settings.data_transfer.imports.delete', $row->id); }, ]); } } } ================================================ FILE: packages/Webkul/Admin/src/DataGrids/Settings/EmailTemplateDataGrid.php ================================================ addSelect( 'email_templates.id', 'email_templates.name', 'email_templates.subject', ); $this->addFilter('id', 'email_templates.id'); return $queryBuilder; } /** * Add columns. * * @return void */ public function prepareColumns() { $this->addColumn([ 'index' => 'id', 'label' => trans('admin::app.settings.email-template.index.datagrid.id'), 'type' => 'string', 'sortable' => true, 'searchable' => true, 'filterable' => true, ]); $this->addColumn([ 'index' => 'name', 'label' => trans('admin::app.settings.email-template.index.datagrid.name'), 'type' => 'string', 'sortable' => true, 'searchable' => true, 'filterable' => true, ]); $this->addColumn([ 'index' => 'subject', 'label' => trans('admin::app.settings.email-template.index.datagrid.subject'), 'type' => 'string', 'sortable' => true, ]); } /** * Prepare actions. * * @return void */ public function prepareActions() { if (bouncer()->hasPermission('settings.automation.email_templates.edit')) { $this->addAction([ 'index' => 'delete', 'icon' => 'icon-edit', 'title' => trans('admin::app.settings.email-template.index.datagrid.edit'), 'method' => 'GET', 'url' => fn ($row) => route('admin.settings.email_templates.edit', $row->id), ]); } if (bouncer()->hasPermission('settings.automation.email_templates.delete')) { $this->addAction([ 'index' => 'delete', 'icon' => 'icon-delete', 'title' => trans('admin::app.settings.email-template.index.datagrid.delete'), 'method' => 'DELETE', 'url' => fn ($row) => route('admin.settings.email_templates.delete', $row->id), ]); } } } ================================================ FILE: packages/Webkul/Admin/src/DataGrids/Settings/GroupDataGrid.php ================================================ addSelect( 'groups.id', 'groups.name', 'groups.description' ); $this->addFilter('id', 'groups.id'); return $queryBuilder; } /** * Prepare columns. */ public function prepareColumns(): void { $this->addColumn([ 'index' => 'id', 'label' => trans('admin::app.settings.groups.index.datagrid.id'), 'type' => 'string', 'searchable' => true, 'filterable' => true, 'sortable' => true, ]); $this->addColumn([ 'index' => 'name', 'type' => 'string', 'label' => trans('admin::app.settings.groups.index.datagrid.name'), 'searchable' => true, 'filterable' => true, 'sortable' => true, ]); $this->addColumn([ 'index' => 'description', 'label' => trans('admin::app.settings.groups.index.datagrid.description'), 'type' => 'string', 'sortable' => false, ]); } /** * Prepare actions. */ public function prepareActions(): void { if (bouncer()->hasPermission('settings.user.groups.edit')) { $this->addAction([ 'index' => 'edit', 'icon' => 'icon-edit', 'title' => trans('admin::app.settings.groups.index.datagrid.edit'), 'method' => 'GET', 'url' => fn ($row) => route('admin.settings.groups.edit', $row->id), ]); } if (bouncer()->hasPermission('settings.user.groups.delete')) { $this->addAction([ 'index' => 'delete', 'icon' => 'icon-delete', 'title' => trans('admin::app.settings.groups.index.datagrid.delete'), 'method' => 'DELETE', 'url' => fn ($row) => route('admin.settings.groups.delete', $row->id), ]); } } } ================================================ FILE: packages/Webkul/Admin/src/DataGrids/Settings/Marketing/CampaignDatagrid.php ================================================ addSelect( 'marketing_campaigns.id', 'marketing_campaigns.name', 'marketing_campaigns.subject', 'marketing_campaigns.status', ); $this->addFilter('id', 'marketing_campaigns.id'); return $queryBuilder; } /** * Add columns. * * @return void */ public function prepareColumns() { $this->addColumn([ 'index' => 'id', 'label' => trans('admin::app.settings.marketing.campaigns.index.datagrid.id'), 'type' => 'string', 'sortable' => true, 'searchable' => true, 'filterable' => true, ]); $this->addColumn([ 'index' => 'name', 'label' => trans('admin::app.settings.marketing.campaigns.index.datagrid.name'), 'type' => 'string', 'sortable' => true, 'searchable' => true, 'filterable' => true, ]); $this->addColumn([ 'index' => 'subject', 'label' => trans('admin::app.settings.marketing.campaigns.index.datagrid.subject'), 'type' => 'string', 'sortable' => true, ]); $this->addColumn([ 'index' => 'status', 'label' => trans('admin::app.settings.marketing.campaigns.index.datagrid.status'), 'type' => 'string', 'sortable' => true, ]); } /** * Prepare actions. * * @return void */ public function prepareActions() { if (bouncer()->hasPermission('settings.automation.campaigns.edit')) { $this->addAction([ 'index' => 'edit', 'icon' => 'icon-edit', 'title' => trans('admin::app.settings.marketing.campaigns.index.datagrid.edit'), 'method' => 'GET', 'url' => fn ($row) => route('admin.settings.marketing.campaigns.edit', $row->id), ]); } if (bouncer()->hasPermission('settings.automation.campaigns.delete')) { $this->addAction([ 'index' => 'delete', 'icon' => 'icon-delete', 'title' => trans('admin::app.settings.marketing.campaigns.index.datagrid.delete'), 'method' => 'DELETE', 'url' => fn ($row) => route('admin.settings.marketing.campaigns.delete', $row->id), ]); } } /** * Prepare mass actions. */ public function prepareMassActions(): void { if (bouncer()->hasPermission('settings.automation.campaigns.mass_delete')) { $this->addMassAction([ 'icon' => 'icon-delete', 'title' => trans('admin::app.settings.marketing.campaigns.index.datagrid.delete'), 'method' => 'POST', 'url' => route('admin.settings.marketing.campaigns.mass_delete'), ]); } } } ================================================ FILE: packages/Webkul/Admin/src/DataGrids/Settings/Marketing/EventDataGrid.php ================================================ addSelect( 'marketing_events.id', 'marketing_events.name', 'marketing_events.description', 'marketing_events.date', ); $this->addFilter('id', 'marketing_events.id'); return $queryBuilder; } /** * Add columns. * * @return void */ public function prepareColumns() { $this->addColumn([ 'index' => 'id', 'label' => trans('admin::app.settings.marketing.events.index.datagrid.id'), 'type' => 'string', 'sortable' => true, 'searchable' => true, 'filterable' => true, ]); $this->addColumn([ 'index' => 'name', 'label' => trans('admin::app.settings.marketing.events.index.datagrid.name'), 'type' => 'string', 'sortable' => true, 'searchable' => true, 'filterable' => true, ]); $this->addColumn([ 'index' => 'description', 'label' => trans('admin::app.settings.marketing.events.index.datagrid.description'), 'type' => 'string', 'sortable' => true, ]); $this->addColumn([ 'index' => 'date', 'label' => trans('admin::app.settings.marketing.events.index.datagrid.date'), 'type' => 'string', 'sortable' => true, ]); } /** * Prepare actions. * * @return void */ public function prepareActions() { if (bouncer()->hasPermission('settings.automation.events.edit')) { $this->addAction([ 'index' => 'edit', 'icon' => 'icon-edit', 'title' => trans('admin::app.settings.marketing.events.index.datagrid.edit'), 'method' => 'GET', 'url' => fn ($row) => route('admin.settings.marketing.events.edit', $row->id), ]); } if (bouncer()->hasPermission('settings.automation.events.delete')) { $this->addAction([ 'index' => 'delete', 'icon' => 'icon-delete', 'title' => trans('admin::app.settings.marketing.events.index.datagrid.delete'), 'method' => 'DELETE', 'url' => fn ($row) => route('admin.settings.marketing.events.delete', $row->id), ]); } } /** * Prepare mass actions. */ public function prepareMassActions(): void { if (bouncer()->hasPermission('settings.automation.events.delete')) { $this->addMassAction([ 'icon' => 'icon-delete', 'title' => trans('admin::app.settings.marketing.events.index.datagrid.delete'), 'method' => 'POST', 'url' => route('admin.settings.marketing.events.mass_delete'), ]); } } } ================================================ FILE: packages/Webkul/Admin/src/DataGrids/Settings/PipelineDataGrid.php ================================================ addSelect( 'lead_pipelines.id', 'lead_pipelines.name', 'lead_pipelines.rotten_days', 'lead_pipelines.is_default', ); $this->addFilter('id', 'lead_pipelines.id'); return $queryBuilder; } /** * Prepare columns. */ public function prepareColumns(): void { $this->addColumn([ 'index' => 'id', 'label' => trans('admin::app.settings.pipelines.index.datagrid.id'), 'type' => 'string', 'sortable' => true, ]); $this->addColumn([ 'index' => 'name', 'label' => trans('admin::app.settings.pipelines.index.datagrid.name'), 'type' => 'string', 'searchable' => true, 'filterable' => true, 'sortable' => true, ]); $this->addColumn([ 'index' => 'rotten_days', 'label' => trans('admin::app.settings.pipelines.index.datagrid.rotten-days'), 'type' => 'string', 'sortable' => true, ]); $this->addColumn([ 'index' => 'is_default', 'label' => trans('admin::app.settings.pipelines.index.datagrid.is-default'), 'type' => 'boolean', 'searchable' => true, 'filterable' => true, 'sortable' => true, 'closure' => fn ($value) => trans('admin::app.settings.pipelines.index.datagrid.'.($value->is_default ? 'yes' : 'no')), ]); } /** * Prepare actions. */ public function prepareActions(): void { if (bouncer()->hasPermission('settings.lead.pipelines.edit')) { $this->addAction([ 'index' => 'edit', 'icon' => 'icon-edit', 'title' => trans('admin::app.settings.pipelines.index.datagrid.edit'), 'method' => 'GET', 'url' => fn ($row) => route('admin.settings.pipelines.edit', $row->id), ]); } if (bouncer()->hasPermission('settings.lead.pipelines.delete')) { $this->addAction([ 'index' => 'delete', 'icon' => 'icon-delete', 'title' => trans('admin::app.settings.pipelines.index.datagrid.delete'), 'method' => 'DELETE', 'url' => fn ($row) => route('admin.settings.pipelines.delete', $row->id), ]); } } } ================================================ FILE: packages/Webkul/Admin/src/DataGrids/Settings/RoleDataGrid.php ================================================ addSelect( 'roles.id', 'roles.name', 'roles.description', 'roles.permission_type' ); $this->addFilter('id', 'roles.id'); $this->addFilter('name', 'roles.name'); return $queryBuilder; } /** * Prepare Columns. */ public function prepareColumns(): void { $this->addColumn([ 'index' => 'id', 'label' => trans('admin::app.settings.roles.index.datagrid.id'), 'type' => 'string', 'searchable' => true, 'filterable' => true, 'sortable' => true, ]); $this->addColumn([ 'index' => 'name', 'label' => trans('admin::app.settings.roles.index.datagrid.name'), 'type' => 'string', 'searchable' => true, 'filterable' => true, 'sortable' => true, ]); $this->addColumn([ 'index' => 'description', 'label' => trans('admin::app.settings.roles.index.datagrid.description'), 'type' => 'string', 'searchable' => true, 'sortable' => false, ]); $this->addColumn([ 'index' => 'permission_type', 'label' => trans('admin::app.settings.roles.index.datagrid.permission-type'), 'type' => 'string', 'searchable' => true, 'filterable' => true, 'filterable_type' => 'dropdown', 'filterable_options' => [ [ 'label' => trans('admin::app.settings.roles.index.datagrid.custom'), 'value' => 'custom', ], [ 'label' => trans('admin::app.settings.roles.index.datagrid.all'), 'value' => 'all', ], ], 'sortable' => true, ]); } /** * Prepare actions. */ public function prepareActions(): void { if (bouncer()->hasPermission('settings.user.roles.edit')) { $this->addAction([ 'icon' => 'icon-edit', 'title' => trans('admin::app.settings.roles.index.datagrid.edit'), 'method' => 'GET', 'url' => fn ($row) => route('admin.settings.roles.edit', $row->id), ]); } if (bouncer()->hasPermission('settings.user.roles.delete')) { $this->addAction([ 'icon' => 'icon-delete', 'title' => trans('admin::app.settings.roles.index.datagrid.delete'), 'method' => 'DELETE', 'url' => fn ($row) => route('admin.settings.roles.delete', $row->id), ]); } } } ================================================ FILE: packages/Webkul/Admin/src/DataGrids/Settings/SourceDataGrid.php ================================================ addSelect( 'lead_sources.id', 'lead_sources.name' ); $this->addFilter('id', 'lead_sources.id'); return $queryBuilder; } /** * Prepare Columns. */ public function prepareColumns(): void { $this->addColumn([ 'index' => 'id', 'label' => trans('admin::app.settings.sources.index.datagrid.id'), 'type' => 'string', 'sortable' => true, ]); $this->addColumn([ 'index' => 'name', 'label' => trans('admin::app.settings.sources.index.datagrid.name'), 'type' => 'string', 'searchable' => true, 'filterable' => true, 'sortable' => true, ]); } /** * Prepare actions. */ public function prepareActions(): void { if (bouncer()->hasPermission('settings.lead.sources.edit')) { $this->addAction([ 'index' => 'edit', 'icon' => 'icon-edit', 'title' => trans('admin::app.settings.sources.index.datagrid.edit'), 'method' => 'GET', 'url' => fn ($row) => route('admin.settings.sources.edit', $row->id), ]); } if (bouncer()->hasPermission('settings.lead.sources.delete')) { $this->addAction([ 'index' => 'delete', 'icon' => 'icon-delete', 'title' => trans('admin::app.settings.sources.index.datagrid.delete'), 'method' => 'DELETE', 'url' => fn ($row) => route('admin.settings.sources.delete', $row->id), ]); } } } ================================================ FILE: packages/Webkul/Admin/src/DataGrids/Settings/TagDataGrid.php ================================================ addSelect( 'tags.id', 'tags.name', 'tags.color', 'tags.created_at', 'users.name as user_name', ) ->leftJoin('users', 'tags.user_id', '=', 'users.id'); if ($userIds = bouncer()->getAuthorizedUserIds()) { $queryBuilder->whereIn('tags.user_id', $userIds); } $this->addFilter('id', 'tags.id'); $this->addFilter('name', 'tags.name'); $this->addFilter('created_at', 'tags.created_at'); $this->addFilter('user_name', 'users.id'); return $queryBuilder; } /** * Prepare Columns. */ public function prepareColumns(): void { $this->addColumn([ 'index' => 'id', 'label' => trans('admin::app.settings.tags.index.datagrid.id'), 'type' => 'string', 'searchable' => true, 'sortable' => true, 'filterable' => true, ]); $this->addColumn([ 'index' => 'name', 'label' => trans('admin::app.settings.tags.index.datagrid.name'), 'type' => 'string', 'searchable' => true, 'sortable' => true, 'filterable' => true, ]); $this->addColumn([ 'index' => 'user_name', 'label' => trans('admin::app.settings.tags.index.datagrid.users'), 'type' => 'string', 'searchable' => true, 'sortable' => true, 'filterable' => true, ]); $this->addColumn([ 'index' => 'created_at', 'label' => trans('admin::app.settings.tags.index.datagrid.created-at'), 'type' => 'date', 'searchable' => true, 'filterable' => true, 'sortable' => true, 'filterable_type' => 'date_range', 'closure' => fn ($row) => core()->formatDate($row->created_at), ]); } /** * Prepare actions. */ public function prepareActions(): void { if (bouncer()->hasPermission('settings.other_settings.tags.edit')) { $this->addAction([ 'index' => 'edit', 'icon' => 'icon-edit', 'title' => trans('admin::app.settings.tags.index.datagrid.edit'), 'method' => 'GET', 'url' => function ($row) { return route('admin.settings.tags.edit', $row->id); }, ]); } if (bouncer()->hasPermission('settings.other_settings.tags.delete')) { $this->addAction([ 'index' => 'delete', 'icon' => 'icon-delete', 'title' => trans('admin::app.settings.tags.index.datagrid.delete'), 'method' => 'DELETE', 'url' => function ($row) { return route('admin.settings.tags.delete', $row->id); }, ]); } } /** * Prepare mass actions. */ public function prepareMassActions(): void { $this->addMassAction([ 'icon' => 'icon-delete', 'title' => trans('admin::app.settings.tags.index.datagrid.delete'), 'method' => 'POST', 'url' => route('admin.settings.tags.mass_delete'), ]); } } ================================================ FILE: packages/Webkul/Admin/src/DataGrids/Settings/TypeDataGrid.php ================================================ addSelect( 'lead_types.id', 'lead_types.name' ); $this->addFilter('id', 'lead_types.id'); return $queryBuilder; } /** * Prepare Columns. */ public function prepareColumns(): void { $this->addColumn([ 'index' => 'id', 'label' => trans('admin::app.settings.types.index.datagrid.id'), 'type' => 'string', 'searchable' => true, 'filterable' => true, 'sortable' => true, ]); $this->addColumn([ 'index' => 'name', 'label' => trans('admin::app.settings.types.index.datagrid.name'), 'type' => 'string', 'searchable' => true, 'filterable' => true, 'sortable' => true, ]); } /** * Prepare Actions. */ public function prepareActions(): void { if (bouncer()->hasPermission('settings.lead.types.edit')) { $this->addAction([ 'index' => 'edit', 'icon' => 'icon-edit', 'title' => trans('admin::app.settings.roles.index.datagrid.edit'), 'method' => 'GET', 'url' => fn ($row) => route('admin.settings.types.update', $row->id), ]); } if (bouncer()->hasPermission('settings.lead.types.delete')) { $this->addAction([ 'index' => 'delete', 'icon' => 'icon-delete', 'title' => trans('admin::app.settings.roles.index.datagrid.delete'), 'method' => 'DELETE', 'url' => fn ($row) => route('admin.settings.types.delete', $row->id), ]); } } } ================================================ FILE: packages/Webkul/Admin/src/DataGrids/Settings/UserDataGrid.php ================================================ distinct() ->addSelect( 'id', 'name', 'email', 'image', 'status', 'created_at' ) ->leftJoin('user_groups', 'id', '=', 'user_groups.user_id'); if ($userIds = bouncer()->getAuthorizedUserIds()) { $queryBuilder->whereIn('id', $userIds); } return $queryBuilder; } /** * Add columns. */ public function prepareColumns(): void { $this->addColumn([ 'index' => 'id', 'label' => trans('admin::app.settings.users.index.datagrid.id'), 'type' => 'string', 'sortable' => true, ]); $this->addColumn([ 'index' => 'name', 'label' => trans('admin::app.settings.users.index.datagrid.name'), 'type' => 'string', 'sortable' => true, 'searchable' => true, 'filterable' => true, 'closure' => function ($row) { return [ 'image' => $row->image ? Storage::url($row->image) : null, 'name' => $row->name, ]; }, ]); $this->addColumn([ 'index' => 'email', 'label' => trans('admin::app.settings.users.index.datagrid.email'), 'type' => 'string', 'sortable' => true, 'searchable' => true, 'filterable' => true, ]); $this->addColumn([ 'index' => 'status', 'label' => trans('admin::app.settings.users.index.datagrid.status'), 'type' => 'boolean', 'filterable' => true, 'sortable' => true, 'searchable' => true, ]); $this->addColumn([ 'index' => 'created_at', 'label' => trans('admin::app.settings.users.index.datagrid.created-at'), 'type' => 'date', 'sortable' => true, 'searchable' => true, 'filterable_type' => 'date_range', 'filterable' => true, ]); } /** * Prepare actions. */ public function prepareActions(): void { if (bouncer()->hasPermission('settings.user.users.edit')) { $this->addAction([ 'index' => 'edit', 'icon' => 'icon-edit', 'title' => trans('admin::app.settings.users.index.datagrid.edit'), 'method' => 'GET', 'url' => fn ($row) => route('admin.settings.users.edit', $row->id), ]); } if (bouncer()->hasPermission('settings.user.users.delete')) { $this->addAction([ 'index' => 'delete', 'icon' => 'icon-delete', 'title' => trans('admin::app.settings.users.index.datagrid.delete'), 'method' => 'DELETE', 'url' => fn ($row) => route('admin.settings.users.delete', $row->id), ]); } } /** * Prepare mass actions. */ public function prepareMassActions(): void { $this->addMassAction([ 'icon' => 'icon-delete', 'title' => trans('admin::app.settings.users.index.datagrid.delete'), 'method' => 'POST', 'url' => route('admin.settings.users.mass_delete'), ]); $this->addMassAction([ 'title' => trans('admin::app.settings.users.index.datagrid.update-status'), 'method' => 'POST', 'url' => route('admin.settings.users.mass_update'), 'options' => [ [ 'label' => trans('admin::app.settings.users.index.datagrid.active'), 'value' => 1, ], [ 'label' => trans('admin::app.settings.users.index.datagrid.inactive'), 'value' => 0, ], ], ]); } } ================================================ FILE: packages/Webkul/Admin/src/DataGrids/Settings/WarehouseDataGrid.php ================================================ leftJoin('product_inventories', 'warehouses.id', '=', 'product_inventories.warehouse_id') ->select( 'warehouses.id', 'warehouses.name', 'warehouses.contact_name', 'warehouses.contact_emails', 'warehouses.contact_numbers', 'warehouses.created_at', ) ->addSelect(DB::raw('count(DISTINCT '.DB::getTablePrefix().'product_inventories.product_id) as products')) ->groupBy('warehouses.id'); $this->addFilter('id', 'warehouses.id'); $this->addFilter('created_at', 'warehouses.created_at'); return $queryBuilder; } /** * Add columns. */ public function prepareColumns(): void { $this->addColumn([ 'index' => 'id', 'label' => trans('admin::app.settings.warehouses.index.datagrid.id'), 'type' => 'string', 'sortable' => true, 'filterable' => true, ]); $this->addColumn([ 'index' => 'name', 'label' => trans('admin::app.settings.warehouses.index.datagrid.name'), 'type' => 'string', 'sortable' => true, 'filterable' => true, ]); $this->addColumn([ 'index' => 'contact_name', 'label' => trans('admin::app.settings.warehouses.index.datagrid.contact-name'), 'type' => 'string', 'searchable' => true, 'filterable' => true, 'sortable' => true, ]); $this->addColumn([ 'index' => 'contact_emails', 'label' => trans('admin::app.settings.warehouses.index.datagrid.contact-emails'), 'type' => 'string', 'searchable' => true, 'filterable' => true, 'sortable' => true, 'closure' => function ($row) { $emails = json_decode($row->contact_emails, true); if ($emails) { return collect($emails)->pluck('value')->join(', '); } }, ]); $this->addColumn([ 'index' => 'contact_numbers', 'label' => trans('admin::app.settings.warehouses.index.datagrid.contact-numbers'), 'type' => 'string', 'sortable' => false, 'closure' => function ($row) { $numbers = json_decode($row->contact_numbers, true); if ($numbers) { return collect($numbers)->pluck('value')->join(', '); } }, ]); $this->addColumn([ 'index' => 'products', 'label' => trans('admin::app.settings.warehouses.index.datagrid.products'), 'type' => 'string', 'sortable' => true, 'filterable' => false, ]); $this->addColumn([ 'index' => 'created_at', 'label' => trans('admin::app.settings.warehouses.index.datagrid.created-at'), 'type' => 'date', 'searchable' => true, 'filterable' => true, 'filterable_type' => 'date_range', 'sortable' => true, 'closure' => function ($row) { return core()->formatDate($row->created_at); }, ]); } /** * Prepare actions. * * @return void */ public function prepareActions() { if (bouncer()->hasPermission('settings.inventory.warehouse')) { $this->addAction([ 'icon' => 'icon-eye', 'title' => trans('admin::app.settings.warehouses.index.datagrid.view'), 'method' => 'GET', 'url' => function ($row) { return route('admin.settings.warehouses.view', $row->id); }, ]); } if (bouncer()->hasPermission('settings.inventory.warehouse.edit')) { $this->addAction([ 'icon' => 'icon-edit', 'title' => trans('admin::app.settings.warehouses.index.datagrid.edit'), 'method' => 'GET', 'url' => function ($row) { return route('admin.settings.warehouses.edit', $row->id); }, ]); } if (bouncer()->hasPermission('settings.inventory.warehouse.delete')) { $this->addAction([ 'icon' => 'icon-delete', 'title' => trans('admin::app.settings.warehouses.index.datagrid.delete'), 'method' => 'DELETE', 'url' => function ($row) { return route('admin.settings.warehouses.delete', $row->id); }, ]); } } } ================================================ FILE: packages/Webkul/Admin/src/DataGrids/Settings/WebhookDataGrid.php ================================================ addSelect( 'webhooks.id', 'webhooks.name', 'webhooks.entity_type', 'webhooks.end_point', ); $this->addFilter('id', 'webhooks.id'); return $queryBuilder; } /** * Prepare Columns. */ public function prepareColumns(): void { $this->addColumn([ 'index' => 'id', 'label' => trans('admin::app.settings.webhooks.index.datagrid.id'), 'type' => 'string', 'searchable' => true, 'filterable' => true, 'sortable' => true, ]); $this->addColumn([ 'index' => 'name', 'label' => trans('admin::app.settings.webhooks.index.datagrid.name'), 'type' => 'string', 'searchable' => true, 'filterable' => true, 'sortable' => true, ]); $this->addColumn([ 'index' => 'entity_type', 'label' => trans('admin::app.settings.webhooks.index.datagrid.entity-type'), 'type' => 'string', 'searchable' => true, 'filterable' => true, 'sortable' => true, ]); $this->addColumn([ 'index' => 'end_point', 'label' => trans('admin::app.settings.webhooks.index.datagrid.end-point'), 'type' => 'string', 'searchable' => true, 'filterable' => true, 'sortable' => true, ]); } /** * Prepare actions. */ public function prepareActions(): void { if (bouncer()->hasPermission('settings.automation.webhooks.edit')) { $this->addAction([ 'index' => 'edit', 'icon' => 'icon-edit', 'title' => trans('admin::app.settings.webhooks.index.datagrid.edit'), 'method' => 'GET', 'url' => fn ($row) => route('admin.settings.webhooks.edit', $row->id), ]); } if (bouncer()->hasPermission('settings.automation.webhooks.delete')) { $this->addAction([ 'index' => 'delete', 'icon' => 'icon-delete', 'title' => trans('admin::app.settings.webhooks.index.datagrid.delete'), 'method' => 'DELETE', 'url' => fn ($row) => route('admin.settings.webhooks.delete', $row->id), ]); } } } ================================================ FILE: packages/Webkul/Admin/src/DataGrids/Settings/WorkflowDataGrid.php ================================================ addSelect( 'workflows.id', 'workflows.name' ); $this->addFilter('id', 'workflows.id'); return $queryBuilder; } /** * Prepare Columns. */ public function prepareColumns(): void { $this->addColumn([ 'index' => 'id', 'label' => trans('admin::app.settings.workflows.index.datagrid.id'), 'type' => 'string', 'searchable' => true, 'filterable' => true, 'sortable' => true, ]); $this->addColumn([ 'index' => 'name', 'label' => trans('admin::app.settings.workflows.index.datagrid.name'), 'type' => 'string', 'searchable' => true, 'filterable' => true, 'sortable' => true, ]); } /** * Prepare actions. */ public function prepareActions(): void { if (bouncer()->hasPermission('settings.automation.workflows.edit')) { $this->addAction([ 'index' => 'edit', 'icon' => 'icon-edit', 'title' => trans('admin::app.settings.workflows.index.datagrid.edit'), 'method' => 'GET', 'url' => fn ($row) => route('admin.settings.workflows.edit', $row->id), ]); } if (bouncer()->hasPermission('settings.automation.workflows.delete')) { $this->addAction([ 'index' => 'delete', 'icon' => 'icon-delete', 'title' => trans('admin::app.settings.workflows.index.datagrid.delete'), 'method' => 'DELETE', 'url' => fn ($row) => route('admin.settings.workflows.delete', $row->id), ]); } } } ================================================ FILE: packages/Webkul/Admin/src/Database/Migrations/2021_06_07_162808_add_lead_view_permission_column_in_users_table.php ================================================ string('view_permission')->after('status')->default('global')->nullable(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::table('users', function (Blueprint $table) { $table->dropColumn('view_permission'); }); } }; ================================================ FILE: packages/Webkul/Admin/src/Database/Migrations/2021_10_02_170105_insert_expected_closed_date_column_in_attributes_table.php ================================================ insert([ [ 'id' => '7', 'code' => 'expected_close_date', 'name' => 'Expected Close Date', 'type' => 'date', 'entity_type' => 'leads', 'lookup_type' => null, 'validation' => null, 'sort_order' => '8', 'is_required' => '0', 'is_unique' => '0', 'quick_add' => '1', 'is_user_defined' => '0', 'created_at' => Carbon::now(), 'updated_at' => Carbon::now(), ], ]); } /** * Reverse the migrations. * * @return void */ public function down() {} }; ================================================ FILE: packages/Webkul/Admin/src/Exceptions/Handler.php ================================================ jsonErrorMessages = [ '404' => trans('admin::app.errors.404.title'), '403' => trans('admin::app.errors.403.title'), '401' => trans('admin::app.errors.401.title'), '500' => trans('admin::app.errors.500.title'), ]; } /** * Render an exception into an HTTP response. * * @param Request $request * @return Response */ public function render($request, Throwable $exception) { if (! config('app.debug')) { return $this->renderCustomResponse($exception); } return parent::render($request, $exception); } /** * Convert an authentication exception into a response. * * @param Request $request * @return Response */ protected function unauthenticated($request, AuthenticationException $exception) { if ($request->expectsJson()) { return response()->json(['message' => $this->jsonErrorMessages[401]], 401); } return redirect()->guest(route('customer.session.index')); } /** * Render custom HTTP response. * * @return Response|null */ private function renderCustomResponse(Throwable $exception) { if ($exception instanceof HttpException) { $statusCode = in_array($exception->getStatusCode(), [401, 403, 404, 503]) ? $exception->getStatusCode() : 500; return $this->response($statusCode); } if ($exception instanceof ValidationException) { return parent::render(request(), $exception); } if ($exception instanceof ModelNotFoundException) { return $this->response(404); } elseif ($exception instanceof PDOException || $exception instanceof \ParseError) { return $this->response(500); } else { return $this->response(500); } } /** * Return custom response. * * @param string $path * @param string $errorCode * @return mixed */ private function response($errorCode) { if (request()->expectsJson()) { return response()->json([ 'message' => isset($this->jsonErrorMessages[$errorCode]) ? $this->jsonErrorMessages[$errorCode] : trans('admin::app.common.something-went-wrong'), ], $errorCode); } return response()->view('admin::errors.index', compact('errorCode')); } } ================================================ FILE: packages/Webkul/Admin/src/Facades/Bouncer.php ================================================ $this->leadReporting->getTotalWonLeadValueProgress(), 'total_lost_revenue' => $this->leadReporting->getTotalLostLeadValueProgress(), ]; } /** * Returns the overall statistics. */ public function getOverAllStats(): array { return [ 'total_leads' => $this->leadReporting->getTotalLeadsProgress(), 'average_lead_value' => $this->leadReporting->getAverageLeadValueProgress(), 'average_leads_per_day' => $this->leadReporting->getAverageLeadsPerDayProgress(), 'total_quotations' => $this->quoteReporting->getTotalQuotesProgress(), 'total_persons' => $this->personReporting->getTotalPersonsProgress(), 'total_organizations' => $this->organizationReporting->getTotalOrganizationsProgress(), ]; } /** * Returns leads statistics. */ public function getTotalLeadsStats(): array { return [ 'all' => [ 'over_time' => $this->leadReporting->getTotalLeadsOverTime(), ], 'won' => [ 'over_time' => $this->leadReporting->getTotalWonLeadsOverTime(), ], 'lost' => [ 'over_time' => $this->leadReporting->getTotalLostLeadsOverTime(), ], ]; } /** * Returns leads revenue statistics by sources. */ public function getLeadsStatsBySources(): mixed { return $this->leadReporting->getTotalWonLeadValueBySources(); } /** * Returns leads revenue statistics by types. */ public function getLeadsStatsByTypes(): mixed { return $this->leadReporting->getTotalWonLeadValueByTypes(); } /** * Returns open leads statistics by states. */ public function getOpenLeadsByStates(): mixed { return $this->leadReporting->getOpenLeadsByStates(); } /** * Returns top selling products statistics. */ public function getTopSellingProducts(): Collection { return $this->productReporting->getTopSellingProductsByRevenue(5); } /** * Returns top selling products statistics. */ public function getTopPersons(): Collection { return $this->personReporting->getTopCustomersByRevenue(5); } /** * Get the start date. * * @return \Carbon\Carbon */ public function getStartDate(): Carbon { return $this->leadReporting->getStartDate(); } /** * Get the end date. * * @return \Carbon\Carbon */ public function getEndDate(): Carbon { return $this->leadReporting->getEndDate(); } /** * Returns date range */ public function getDateRange(): string { return $this->getStartDate()->format('d M').' - '.$this->getEndDate()->format('d M'); } } ================================================ FILE: packages/Webkul/Admin/src/Helpers/Reporting/AbstractReporting.php ================================================ setStartDate(request()->date('start')); $this->setEndDate(request()->date('end')); } /** * Set the start date or default to 30 days ago if not provided. * * @param \Carbon\Carbon|null $startDate * @return void */ public function setStartDate(?Carbon $startDate = null): self { $this->startDate = $startDate ? $startDate->startOfDay() : now()->subDays(30)->startOfDay(); $this->setLastStartDate(); return $this; } /** * Sets the end date to the provided date's end of day, or to the current * date if not provided or if the provided date is in the future. * * @param \Carbon\Carbon|null $endDate * @return void */ public function setEndDate(?Carbon $endDate = null): self { $this->endDate = ($endDate && $endDate->endOfDay() <= now()) ? $endDate->endOfDay() : now(); $this->setLastEndDate(); return $this; } /** * Get the start date. * * @return \Carbon\Carbon */ public function getStartDate(): Carbon { return $this->startDate; } /** * Get the end date. * * @return \Carbon\Carbon */ public function getEndDate(): Carbon { return $this->endDate; } /** * Sets the start date for the last period. */ private function setLastStartDate(): void { if (! isset($this->startDate)) { $this->setStartDate(request()->date('start')); } if (! isset($this->endDate)) { $this->setEndDate(request()->date('end')); } $this->lastStartDate = $this->startDate->clone()->subDays($this->startDate->diffInDays($this->endDate)); } /** * Sets the end date for the last period. */ private function setLastEndDate(): void { $this->lastEndDate = $this->startDate->clone(); } /** * Get the last start date. * * @return \Carbon\Carbon */ public function getLastStartDate(): Carbon { return $this->lastStartDate; } /** * Get the last end date. * * @return \Carbon\Carbon */ public function getLastEndDate(): Carbon { return $this->lastEndDate; } /** * Calculate the percentage change between previous and current values. * * @param float|int $previous * @param float|int $current */ public function getPercentageChange($previous, $current): float|int { if (! $previous) { return $current ? 100 : 0; } return ($current - $previous) / $previous * 100; } /** * Returns time intervals. * * @param \Carbon\Carbon $startDate * @param \Carbon\Carbon $endDate * @param string $period * @return array */ public function getTimeInterval($startDate, $endDate, $dateColumn, $period) { if ($period == 'auto') { $totalMonths = $startDate->diffInMonths($endDate) + 1; /** * If the difference between the start and end date is more than 5 months */ $intervals = $this->getMonthsInterval($startDate, $endDate); if (! empty($intervals)) { return [ 'group_column' => "MONTH($dateColumn)", 'intervals' => $intervals, ]; } /** * If the difference between the start and end date is more than 6 weeks */ $intervals = $this->getWeeksInterval($startDate, $endDate); if (! empty($intervals)) { return [ 'group_column' => "WEEK($dateColumn)", 'intervals' => $intervals, ]; } /** * If the difference between the start and end date is less than 6 weeks */ return [ 'group_column' => "DAYOFYEAR($dateColumn)", 'intervals' => $this->getDaysInterval($startDate, $endDate), ]; } else { $datePeriod = CarbonPeriod::create($this->startDate, "1 $period", $this->endDate); if ($period == 'year') { $formatter = '?'; } elseif ($period == 'month') { $formatter = '?-?'; } else { $formatter = '?-?-?'; } $groupColumn = 'DATE_FORMAT('.$dateColumn.', "'.Str::replaceArray('?', ['%Y', '%m', '%d'], $formatter).'")'; $intervals = []; foreach ($datePeriod as $date) { $formattedDate = $date->format(Str::replaceArray('?', ['Y', 'm', 'd'], $formatter)); $intervals[] = [ 'filter' => $formattedDate, 'start' => $formattedDate, ]; } return [ 'group_column' => $groupColumn, 'intervals' => $intervals, ]; } } /** * Returns time intervals. * * @param \Carbon\Carbon $startDate * @param \Carbon\Carbon $endDate * @return array */ public function getMonthsInterval($startDate, $endDate) { $intervals = []; $totalMonths = $startDate->diffInMonths($endDate) + 1; /** * If the difference between the start and end date is less than 5 months */ if ($totalMonths <= 5) { return $intervals; } for ($i = 0; $i < $totalMonths; $i++) { $intervalStartDate = clone $startDate; $intervalStartDate->addMonths($i); $start = $intervalStartDate->startOfDay(); $end = ($totalMonths - 1 == $i) ? $endDate : $intervalStartDate->addMonth()->subDay()->endOfDay(); $intervals[] = [ 'filter' => $start->month, 'start' => $start->format('d M'), 'end' => $end->format('d M'), ]; } return $intervals; } /** * Returns time intervals. * * @param \Carbon\Carbon $startDate * @param \Carbon\Carbon $endDate * @return array */ public function getWeeksInterval($startDate, $endDate) { $intervals = []; $startWeekDay = Carbon::createFromTimeString(core()->xWeekRange($startDate, 0).' 00:00:01'); $endWeekDay = Carbon::createFromTimeString(core()->xWeekRange($endDate, 1).' 23:59:59'); $totalWeeks = $startWeekDay->diffInWeeks($endWeekDay); /** * If the difference between the start and end date is less than 6 weeks */ if ($totalWeeks <= 6) { return $intervals; } for ($i = 0; $i < $totalWeeks; $i++) { $intervalStartDate = clone $startDate; $intervalStartDate->addWeeks($i); $start = $i == 0 ? $startDate : Carbon::createFromTimeString(core()->xWeekRange($intervalStartDate, 0).' 00:00:01'); $end = ($totalWeeks - 1 == $i) ? $endDate : Carbon::createFromTimeString(core()->xWeekRange($intervalStartDate->subDay(), 1).' 23:59:59'); $intervals[] = [ 'filter' => $start->week, 'start' => $start->format('d M'), 'end' => $end->format('d M'), ]; } return $intervals; } /** * Returns time intervals. * * @param \Carbon\Carbon $startDate * @param \Carbon\Carbon $endDate * @return array */ public function getDaysInterval($startDate, $endDate) { $intervals = []; $totalDays = $startDate->diffInDays($endDate) + 1; for ($i = 0; $i < $totalDays; $i++) { $intervalStartDate = clone $startDate; $intervalStartDate->addDays($i); $intervals[] = [ 'filter' => $intervalStartDate->dayOfYear, 'start' => $intervalStartDate->startOfDay()->format('d M'), 'end' => $intervalStartDate->endOfDay()->format('d M'), ]; } return $intervals; } } ================================================ FILE: packages/Webkul/Admin/src/Helpers/Reporting/Activity.php ================================================ allStageIds = $this->stageRepository->pluck('id')->toArray(); $this->wonStageIds = $this->stageRepository->where('code', 'won')->pluck('id')->toArray(); $this->lostStageIds = $this->stageRepository->where('code', 'lost')->pluck('id')->toArray(); parent::__construct(); } /** * Returns current customers over time * * @param string $period */ public function getTotalLeadsOverTime($period = 'auto'): array { $this->stageIds = $this->allStageIds; $period = $this->determinePeriod($period); return $this->getOverTimeStats($this->startDate, $this->endDate, 'leads.id', 'created_at', $period); } /** * Returns current customers over time * * @param string $period */ public function getTotalWonLeadsOverTime($period = 'auto'): array { $this->stageIds = $this->wonStageIds; $period = $this->determinePeriod($period); return $this->getOverTimeStats($this->startDate, $this->endDate, 'leads.id', 'closed_at', $period); } /** * Returns current customers over time * * @param string $period */ public function getTotalLostLeadsOverTime($period = 'auto'): array { $this->stageIds = $this->lostStageIds; $period = $this->determinePeriod($period); return $this->getOverTimeStats($this->startDate, $this->endDate, 'leads.id', 'closed_at', $period); } /** * Determine the appropriate period based on date range * * @param string $period */ protected function determinePeriod($period = 'auto'): string { if ($period !== 'auto') { return $period; } $diffInDays = $this->startDate->diffInDays($this->endDate); $diffInMonths = $this->startDate->diffInMonths($this->endDate); $diffInYears = $this->startDate->diffInYears($this->endDate); if ($diffInYears > 3) { return 'year'; } elseif ($diffInMonths > 6) { return 'month'; } elseif ($diffInDays > 60) { return 'week'; } else { return 'day'; } } /** * Retrieves total leads and their progress. */ public function getTotalLeadsProgress(): array { return [ 'previous' => $previous = $this->getTotalLeads($this->lastStartDate, $this->lastEndDate), 'current' => $current = $this->getTotalLeads($this->startDate, $this->endDate), 'progress' => $this->getPercentageChange($previous, $current), ]; } /** * Retrieves total leads by date * * @param Carbon $startDate * @param Carbon $endDate */ public function getTotalLeads($startDate, $endDate): int { return $this->leadRepository ->resetModel() ->whereBetween('created_at', [$startDate, $endDate]) ->count(); } /** * Retrieves average leads per day and their progress. */ public function getAverageLeadsPerDayProgress(): array { return [ 'previous' => $previous = $this->getAverageLeadsPerDay($this->lastStartDate, $this->lastEndDate), 'current' => $current = $this->getAverageLeadsPerDay($this->startDate, $this->endDate), 'progress' => $this->getPercentageChange($previous, $current), ]; } /** * Retrieves average leads per day * * @param Carbon $startDate * @param Carbon $endDate */ public function getAverageLeadsPerDay($startDate, $endDate): float { $days = $startDate->diffInDays($endDate); if ($days == 0) { return 0; } return $this->getTotalLeads($startDate, $endDate) / $days; } /** * Retrieves total lead value and their progress. */ public function getTotalLeadValueProgress(): array { return [ 'previous' => $previous = $this->getTotalLeadValue($this->lastStartDate, $this->lastEndDate), 'current' => $current = $this->getTotalLeadValue($this->startDate, $this->endDate), 'formatted_total' => core()->formatBasePrice($current), 'progress' => $this->getPercentageChange($previous, $current), ]; } /** * Retrieves total lead value * * @param Carbon $startDate * @param Carbon $endDate */ public function getTotalLeadValue($startDate, $endDate): float { return $this->leadRepository ->resetModel() ->whereBetween('created_at', [$startDate, $endDate]) ->sum('lead_value'); } /** * Retrieves average lead value and their progress. */ public function getAverageLeadValueProgress(): array { return [ 'previous' => $previous = $this->getAverageLeadValue($this->lastStartDate, $this->lastEndDate), 'current' => $current = $this->getAverageLeadValue($this->startDate, $this->endDate), 'formatted_total' => core()->formatBasePrice($current), 'progress' => $this->getPercentageChange($previous, $current), ]; } /** * Retrieves average lead value * * @param Carbon $startDate * @param Carbon $endDate */ public function getAverageLeadValue($startDate, $endDate): float { return $this->leadRepository ->resetModel() ->whereBetween('created_at', [$startDate, $endDate]) ->avg('lead_value') ?? 0; } /** * Retrieves total won lead value and their progress. */ public function getTotalWonLeadValueProgress(): array { return [ 'previous' => $previous = $this->getTotalWonLeadValue($this->lastStartDate, $this->lastEndDate), 'current' => $current = $this->getTotalWonLeadValue($this->startDate, $this->endDate), 'formatted_total' => core()->formatBasePrice($current), 'progress' => $this->getPercentageChange($previous, $current), ]; } /** * Retrieves average won lead value * * @param Carbon $startDate * @param Carbon $endDate * @return array */ public function getTotalWonLeadValue($startDate, $endDate): ?float { return $this->leadRepository ->resetModel() ->whereIn('lead_pipeline_stage_id', $this->wonStageIds) ->whereBetween('created_at', [$startDate, $endDate]) ->sum('lead_value'); } /** * Retrieves average lost lead value and their progress. */ public function getTotalLostLeadValueProgress(): array { return [ 'previous' => $previous = $this->getTotalLostLeadValue($this->lastStartDate, $this->lastEndDate), 'current' => $current = $this->getTotalLostLeadValue($this->startDate, $this->endDate), 'formatted_total' => core()->formatBasePrice($current), 'progress' => $this->getPercentageChange($previous, $current), ]; } /** * Retrieves average lost lead value * * @param Carbon $startDate * @param Carbon $endDate * @return array */ public function getTotalLostLeadValue($startDate, $endDate): ?float { return $this->leadRepository ->resetModel() ->whereIn('lead_pipeline_stage_id', $this->lostStageIds) ->whereBetween('created_at', [$startDate, $endDate]) ->sum('lead_value'); } /** * Retrieves total lead value by sources. */ public function getTotalWonLeadValueBySources() { return $this->leadRepository ->resetModel() ->select( 'lead_sources.name', DB::raw('SUM(lead_value) as total') ) ->leftJoin('lead_sources', 'leads.lead_source_id', '=', 'lead_sources.id') ->whereIn('lead_pipeline_stage_id', $this->wonStageIds) ->whereBetween('leads.created_at', [$this->startDate, $this->endDate]) ->groupBy('lead_source_id') ->get(); } /** * Retrieves total lead value by types. */ public function getTotalWonLeadValueByTypes() { return $this->leadRepository ->resetModel() ->select( 'lead_types.name', DB::raw('SUM(lead_value) as total') ) ->leftJoin('lead_types', 'leads.lead_type_id', '=', 'lead_types.id') ->whereIn('lead_pipeline_stage_id', $this->wonStageIds) ->whereBetween('leads.created_at', [$this->startDate, $this->endDate]) ->groupBy('lead_type_id') ->get(); } /** * Retrieves open leads by states. */ public function getOpenLeadsByStates() { return $this->leadRepository ->resetModel() ->select( 'lead_pipeline_stages.name', DB::raw('COUNT(lead_value) as total') ) ->leftJoin('lead_pipeline_stages', 'leads.lead_pipeline_stage_id', '=', 'lead_pipeline_stages.id') ->whereNotIn('lead_pipeline_stage_id', $this->wonStageIds) ->whereNotIn('lead_pipeline_stage_id', $this->lostStageIds) ->whereBetween('leads.created_at', [$this->startDate, $this->endDate]) ->groupBy('lead_pipeline_stage_id') ->orderByDesc('total') ->get(); } /** * Returns over time stats. * * @param Carbon $startDate * @param Carbon $endDate * @param string $valueColumn * @param string $dateColumn * @param string $period */ public function getOverTimeStats($startDate, $endDate, $valueColumn, $dateColumn = 'created_at', $period = 'auto'): array { $period = $this->determinePeriod($period); $intervals = $this->generateTimeIntervals($startDate, $endDate, $period); $groupColumn = $this->getGroupColumn($dateColumn, $period); $query = $this->leadRepository ->resetModel() ->select( DB::raw("$groupColumn AS date"), DB::raw('COUNT(DISTINCT id) AS count'), DB::raw('SUM('.\DB::getTablePrefix()."$valueColumn) AS total") ) ->whereIn('lead_pipeline_stage_id', $this->stageIds) ->whereBetween($dateColumn, [$startDate, $endDate]) ->groupBy(DB::raw($groupColumn)) ->orderBy(DB::raw($groupColumn)); $results = $query->get(); $resultLookup = $results->keyBy('date'); $stats = []; foreach ($intervals as $interval) { $result = $resultLookup->get($interval['key']); $stats[] = [ 'label' => $interval['label'], 'count' => $result ? (int) $result->count : 0, 'total' => $result ? (float) $result->total : 0, ]; } return $stats; } /** * Generate time intervals based on period */ protected function generateTimeIntervals(Carbon $startDate, Carbon $endDate, string $period): array { $intervals = []; $current = $startDate->copy(); while ($current <= $endDate) { $interval = [ 'key' => $this->formatDateForGrouping($current, $period), 'label' => $this->formatDateForLabel($current, $period), ]; $intervals[] = $interval; switch ($period) { case 'day': $current->addDay(); break; case 'week': $current->addWeek(); break; case 'month': $current->addMonth(); break; case 'year': $current->addYear(); break; } } return $intervals; } /** * Get the SQL group column based on period */ protected function getGroupColumn(string $dateColumn, string $period): string { switch ($period) { case 'day': return "DATE($dateColumn)"; case 'week': return "DATE_FORMAT($dateColumn, '%Y-%u')"; case 'month': return "DATE_FORMAT($dateColumn, '%Y-%m')"; case 'year': return "YEAR($dateColumn)"; default: return "DATE($dateColumn)"; } } /** * Format date for grouping key */ protected function formatDateForGrouping(Carbon $date, string $period): string { switch ($period) { case 'day': return $date->format('Y-m-d'); case 'week': return $date->format('Y-W'); case 'month': return $date->format('Y-m'); case 'year': return $date->format('Y'); default: return $date->format('Y-m-d'); } } /** * Format date for display label */ protected function formatDateForLabel(Carbon $date, string $period): string { switch ($period) { case 'day': return $date->format('M d'); case 'week': return 'Week '.$date->format('W, Y'); case 'month': return $date->format('M Y'); case 'year': return $date->format('Y'); default: return $date->format('M d'); } } } ================================================ FILE: packages/Webkul/Admin/src/Helpers/Reporting/Organization.php ================================================ $previous = $this->getTotalOrganizations($this->lastStartDate, $this->lastEndDate), 'current' => $current = $this->getTotalOrganizations($this->startDate, $this->endDate), 'progress' => $this->getPercentageChange($previous, $current), ]; } /** * Retrieves total organizations by date * * @param Carbon $startDate * @param Carbon $endDate */ public function getTotalOrganizations($startDate, $endDate): int { return $this->organizationRepository ->resetModel() ->whereBetween('created_at', [$startDate, $endDate]) ->count(); } /** * Gets top customers by revenue. * * @param int $limit */ public function getTopOrganizationsByRevenue($limit = null): Collection { $tablePrefix = DB::getTablePrefix(); $items = $this->organizationRepository ->resetModel() ->leftJoin('persons', 'organizations.id', '=', 'persons.organization_id') ->leftJoin('leads', 'persons.id', '=', 'leads.person_id') ->select('*', 'persons.id as id') ->addSelect(DB::raw('SUM('.$tablePrefix.'leads.lead_value) as revenue')) ->whereBetween('leads.closed_at', [$this->startDate, $this->endDate]) ->having(DB::raw('SUM('.$tablePrefix.'leads.lead_value)'), '>', 0) ->groupBy('organization_id') ->orderBy('revenue', 'DESC') ->limit($limit) ->get(); $items = $items->map(function ($item) { return [ 'id' => $item->id, 'name' => $item->name, 'revenue' => $item->revenue, 'formatted_revenue' => core()->formatBasePrice($item->revenue), ]; }); return $items; } } ================================================ FILE: packages/Webkul/Admin/src/Helpers/Reporting/Person.php ================================================ $previous = $this->getTotalPersons($this->lastStartDate, $this->lastEndDate), 'current' => $current = $this->getTotalPersons($this->startDate, $this->endDate), 'progress' => $this->getPercentageChange($previous, $current), ]; } /** * Retrieves total persons by date * * @param Carbon $startDate * @param Carbon $endDate */ public function getTotalPersons($startDate, $endDate): int { return $this->personRepository ->resetModel() ->whereBetween('created_at', [$startDate, $endDate]) ->count(); } /** * Gets top customers by revenue. * * @param int $limit */ public function getTopCustomersByRevenue($limit = null): Collection { $tablePrefix = DB::getTablePrefix(); $items = $this->personRepository ->resetModel() ->leftJoin('leads', 'persons.id', '=', 'leads.person_id') ->select('*', 'persons.id as id') ->addSelect(DB::raw('SUM('.$tablePrefix.'leads.lead_value) as revenue')) ->whereBetween('leads.closed_at', [$this->startDate, $this->endDate]) ->having(DB::raw('SUM('.$tablePrefix.'leads.lead_value)'), '>', 0) ->groupBy('person_id') ->orderBy('revenue', 'DESC') ->limit($limit) ->get(); $items = $items->map(function ($item) { return [ 'id' => $item->id, 'name' => $item->name, 'emails' => $item->emails, 'contact_numbers' => $item->contact_numbers, 'revenue' => $item->revenue, 'formatted_revenue' => core()->formatBasePrice($item->revenue), ]; }); return $items; } } ================================================ FILE: packages/Webkul/Admin/src/Helpers/Reporting/Product.php ================================================ productRepository ->resetModel() ->with('product') ->leftJoin('leads', 'lead_products.lead_id', '=', 'leads.id') ->leftJoin('products', 'lead_products.product_id', '=', 'products.id') ->select('*') ->addSelect(DB::raw('SUM('.$tablePrefix.'lead_products.amount) as revenue')) ->whereBetween('leads.closed_at', [$this->startDate, $this->endDate]) ->having(DB::raw('SUM('.$tablePrefix.'lead_products.amount)'), '>', 0) ->groupBy('product_id') ->orderBy('revenue', 'DESC') ->limit($limit) ->get(); $items = $items->map(function ($item) { return [ 'id' => $item->product_id, 'name' => $item->name, 'price' => $item->product?->price, 'formatted_price' => core()->formatBasePrice($item->price), 'revenue' => $item->revenue, 'formatted_revenue' => core()->formatBasePrice($item->revenue), ]; }); return $items; } /** * Gets top-selling products by quantity. * * @param int $limit */ public function getTopSellingProductsByQuantity($limit = null): Collection { $tablePrefix = DB::getTablePrefix(); $items = $this->productRepository ->resetModel() ->with('product') ->leftJoin('leads', 'lead_products.lead_id', '=', 'leads.id') ->leftJoin('products', 'lead_products.product_id', '=', 'products.id') ->select('*') ->addSelect(DB::raw('SUM('.$tablePrefix.'lead_products.quantity) as total_qty_ordered')) ->whereBetween('leads.closed_at', [$this->startDate, $this->endDate]) ->having(DB::raw('SUM('.$tablePrefix.'lead_products.quantity)'), '>', 0) ->groupBy('product_id') ->orderBy('total_qty_ordered', 'DESC') ->limit($limit) ->get(); $items = $items->map(function ($item) { return [ 'id' => $item->product_id, 'name' => $item->name, 'price' => $item->product?->price, 'formatted_price' => core()->formatBasePrice($item->price), 'total_qty_ordered' => $item->total_qty_ordered, ]; }); return $items; } } ================================================ FILE: packages/Webkul/Admin/src/Helpers/Reporting/Quote.php ================================================ $previous = $this->getTotalQuotes($this->lastStartDate, $this->lastEndDate), 'current' => $current = $this->getTotalQuotes($this->startDate, $this->endDate), 'progress' => $this->getPercentageChange($previous, $current), ]; } /** * Retrieves total quotes by date * * @param Carbon $startDate * @param Carbon $endDate */ public function getTotalQuotes($startDate, $endDate): int { return $this->quoteRepository ->resetModel() ->whereBetween('created_at', [$startDate, $endDate]) ->count(); } } ================================================ FILE: packages/Webkul/Admin/src/Http/Controllers/Activity/ActivityController.php ================================================ has('view_type')) { return datagrid(ActivityDataGrid::class)->process(); } $startDate = request()->get('startDate') ? Carbon::createFromTimeString(request()->get('startDate').' 00:00:01') : Carbon::now()->startOfWeek()->format('Y-m-d H:i:s'); $endDate = request()->get('endDate') ? Carbon::createFromTimeString(request()->get('endDate').' 23:59:59') : Carbon::now()->endOfWeek()->format('Y-m-d H:i:s'); $activities = $this->activityRepository->getActivities([$startDate, $endDate])->toArray(); return response()->json([ 'activities' => $activities, ]); } /** * Store a newly created resource in storage. */ public function store(): RedirectResponse|JsonResponse { $this->validate(request(), [ 'type' => 'required', 'comment' => 'required_if:type,note', 'schedule_from' => 'required_unless:type,note,file', 'schedule_to' => 'required_unless:type,note,file', 'file' => 'required_if:type,file', ]); if (request('type') === 'meeting') { /** * Check if meeting is overlapping with other meetings. */ $isOverlapping = $this->activityRepository->isDurationOverlapping( request()->input('schedule_from'), request()->input('schedule_to'), request()->input('participants'), request()->input('id') ); if ($isOverlapping) { if (request()->ajax()) { return response()->json([ 'message' => trans('admin::app.activities.overlapping-error'), ], 400); } session()->flash('success', trans('admin::app.activities.overlapping-error')); return redirect()->back(); } } Event::dispatch('activity.create.before'); $activity = $this->activityRepository->create(array_merge(request()->all(), [ 'is_done' => request('type') == 'note' ? 1 : 0, 'user_id' => auth()->guard('user')->user()->id, ])); Event::dispatch('activity.create.after', $activity); if (request()->ajax()) { return response()->json([ 'data' => new ActivityResource($activity), 'message' => trans('admin::app.activities.create-success'), ]); } session()->flash('success', trans('admin::app.activities.create-success')); return redirect()->back(); } /** * Show the form for editing the specified resource. */ public function edit(int $id): View { $activity = $this->activityRepository->findOrFail($id); $leadId = old('lead_id') ?? optional($activity->leads()->first())->id; $lookUpEntityData = $this->attributeRepository->getLookUpEntity('leads', $leadId); return view('admin::activities.edit', compact('activity', 'lookUpEntityData')); } /** * Update the specified resource in storage. */ public function update($id): RedirectResponse|JsonResponse { Event::dispatch('activity.update.before', $id); $data = request()->all(); $activity = $this->activityRepository->update($data, $id); /** * We will not use `empty` directly here because `lead_id` can be a blank string * from the activity form. However, on the activity view page, we are only updating the * `is_done` field, so `lead_id` will not be present in that case. */ if (isset($data['lead_id'])) { $activity->leads()->sync( ! empty($data['lead_id']) ? [$data['lead_id']] : [] ); } Event::dispatch('activity.update.after', $activity); if (request()->ajax()) { return response()->json([ 'data' => new ActivityResource($activity), 'message' => trans('admin::app.activities.update-success'), ]); } session()->flash('success', trans('admin::app.activities.update-success')); return redirect()->route('admin.activities.index'); } /** * Mass Update the specified resources. */ public function massUpdate(MassUpdateRequest $massUpdateRequest): JsonResponse { $activities = $this->activityRepository->findWhereIn('id', $massUpdateRequest->input('indices')); foreach ($activities as $activity) { Event::dispatch('activity.update.before', $activity->id); $activity = $this->activityRepository->update([ 'is_done' => $massUpdateRequest->input('value'), ], $activity->id); Event::dispatch('activity.update.after', $activity); } return response()->json([ 'message' => trans('admin::app.activities.mass-update-success'), ]); } /** * Download file from storage. */ public function download(int $id): StreamedResponse { try { $file = $this->fileRepository->findOrFail($id); return Storage::download($file->path); } catch (\Exception $exception) { abort(404); } } /* * Remove the specified resource from storage. */ public function destroy(int $id): JsonResponse { $activity = $this->activityRepository->findOrFail($id); try { Event::dispatch('activity.delete.before', $id); $activity?->delete($id); Event::dispatch('activity.delete.after', $id); return response()->json([ 'message' => trans('admin::app.activities.destroy-success'), ], 200); } catch (\Exception $exception) { return response()->json([ 'message' => trans('admin::app.activities.destroy-failed'), ], 400); } } /** * Mass Delete the specified resources. */ public function massDestroy(MassDestroyRequest $massDestroyRequest): JsonResponse { $activities = $this->activityRepository->findWhereIn('id', $massDestroyRequest->input('indices')); try { foreach ($activities as $activity) { Event::dispatch('activity.delete.before', $activity->id); $this->activityRepository->delete($activity->id); Event::dispatch('activity.delete.after', $activity->id); } return response()->json([ 'message' => trans('admin::app.activities.mass-destroy-success'), ]); } catch (\Exception $exception) { return response()->json([ 'message' => trans('admin::app.activities.mass-delete-failed'), ], 400); } } } ================================================ FILE: packages/Webkul/Admin/src/Http/Controllers/Configuration/ConfigurationController.php ================================================ route('slug') && request()->route('slug2') ) { return view('admin::configuration.edit'); } return view('admin::configuration.index'); } /** * Store a newly created resource in storage. */ public function store(ConfigurationForm $request): RedirectResponse { Event::dispatch('core.configuration.save.before'); $this->configurationRepository->create($request->all()); Event::dispatch('core.configuration.save.after'); session()->flash('success', trans('admin::app.configuration.index.save-success')); return redirect()->back(); } /** * download the file for the specified resource. * * @return Response */ public function download() { $path = request()->route()->parameters()['path']; $fileName = 'configuration/'.$path; $config = $this->configurationRepository->findOneByField('value', $fileName); return Storage::download($config['value']); } /** * Search for configurations. */ public function search(): JsonResponse { $results = $this->configurationRepository->search( system_config()->getItems(), request()->query('query') ); return new JsonResponse([ 'data' => $results, ]); } } ================================================ FILE: packages/Webkul/Admin/src/Http/Controllers/Contact/OrganizationController.php ================================================ request->add(['entity_type' => 'organizations']); } /** * Display a listing of the resource. */ public function index(): View|JsonResponse { if (request()->ajax()) { return datagrid(OrganizationDataGrid::class)->process(); } return view('admin::contacts.organizations.index'); } /** * Show the form for creating a new resource. */ public function create(): View { return view('admin::contacts.organizations.create'); } /** * Store a newly created resource in storage. */ public function store(AttributeForm $request): RedirectResponse { Event::dispatch('contacts.organization.create.before'); $organization = $this->organizationRepository->create(request()->all()); Event::dispatch('contacts.organization.create.after', $organization); session()->flash('success', trans('admin::app.contacts.organizations.index.create-success')); return redirect()->route('admin.contacts.organizations.index'); } /** * Show the form for editing the specified resource. */ public function edit(int $id): View { $organization = $this->organizationRepository->findOrFail($id); return view('admin::contacts.organizations.edit', compact('organization')); } /** * Update the specified resource in storage. */ public function update(AttributeForm $request, int $id): RedirectResponse { Event::dispatch('contacts.organization.update.before', $id); $organization = $this->organizationRepository->update(request()->all(), $id); Event::dispatch('contacts.organization.update.after', $organization); session()->flash('success', trans('admin::app.contacts.organizations.index.update-success')); return redirect()->route('admin.contacts.organizations.index'); } /** * Remove the specified resource from storage. */ public function destroy(int $id): JsonResponse { try { Event::dispatch('contact.organization.delete.before', $id); $this->organizationRepository->delete($id); Event::dispatch('contact.organization.delete.after', $id); return response()->json([ 'message' => trans('admin::app.contacts.organizations.index.delete-success'), ], 200); } catch (\Exception $exception) { return response()->json([ 'message' => trans('admin::app.contacts.organizations.index.delete-failed'), ], 400); } } /** * Mass Delete the specified resources. */ public function massDestroy(MassDestroyRequest $massDestroyRequest): JsonResponse { $organizations = $this->organizationRepository->findWhereIn('id', $massDestroyRequest->input('indices')); foreach ($organizations as $organization) { Event::dispatch('contact.organization.delete.before', $organization); $this->organizationRepository->delete($organization->id); Event::dispatch('contact.organization.delete.after', $organization); } return response()->json([ 'message' => trans('admin::app.contacts.organizations.index.delete-success'), ]); } } ================================================ FILE: packages/Webkul/Admin/src/Http/Controllers/Contact/Persons/ActivityController.php ================================================ activityRepository ->leftJoin('person_activities', 'activities.id', '=', 'person_activities.activity_id') ->where('person_activities.person_id', $id) ->get(); return ActivityResource::collection($this->concatEmailAsActivities($id, $activities)); } /** * Store a newly created resource in storage. */ public function concatEmailAsActivities($personId, $activities) { $emails = DB::table('emails as child') ->select('child.*') ->join('emails as parent', 'child.parent_id', '=', 'parent.id') ->where('parent.person_id', $personId) ->union(DB::table('emails as parent')->where('parent.person_id', $personId)) ->get(); return $activities->concat($emails->map(function ($email) { return (object) [ 'id' => $email->id, 'parent_id' => $email->parent_id, 'title' => $email->subject, 'type' => 'email', 'is_done' => 1, 'comment' => $email->reply, 'schedule_from' => null, 'schedule_to' => null, 'user' => auth()->guard('user')->user(), 'participants' => [], 'location' => null, 'additional' => [ 'folders' => json_decode($email->folders), 'from' => json_decode($email->from), 'to' => json_decode($email->reply_to), 'cc' => json_decode($email->cc), 'bcc' => json_decode($email->bcc), ], 'files' => $this->attachmentRepository->findWhere(['email_id' => $email->id])->map(function ($attachment) { return (object) [ 'id' => $attachment->id, 'name' => $attachment->name, 'path' => $attachment->path, 'url' => $attachment->url, 'created_at' => $attachment->created_at, 'updated_at' => $attachment->updated_at, ]; }), 'created_at' => $email->created_at, 'updated_at' => $email->updated_at, ]; }))->sortByDesc('id')->sortByDesc('created_at'); } } ================================================ FILE: packages/Webkul/Admin/src/Http/Controllers/Contact/Persons/PersonController.php ================================================ request->add(['entity_type' => 'persons']); } /** * Display a listing of the resource. */ public function index() { if (request()->ajax()) { return datagrid(PersonDataGrid::class)->process(); } return view('admin::contacts.persons.index'); } /** * Show the form for creating a new resource. */ public function create(): View { return view('admin::contacts.persons.create'); } /** * Store a newly created resource in storage. */ public function store(AttributeForm $request): RedirectResponse|JsonResponse { Event::dispatch('contacts.person.create.before'); $person = $this->personRepository->create($request->all()); Event::dispatch('contacts.person.create.after', $person); if (request()->ajax()) { return response()->json([ 'data' => $person, 'message' => trans('admin::app.contacts.persons.index.create-success'), ]); } session()->flash('success', trans('admin::app.contacts.persons.index.create-success')); return redirect()->route('admin.contacts.persons.index'); } /** * Display the specified resource. */ public function show(int $id): View { $person = $this->personRepository->findOrFail($id); return view('admin::contacts.persons.view', compact('person')); } /** * Show the form for editing the specified resource. */ public function edit(int $id): View { $person = $this->personRepository->findOrFail($id); return view('admin::contacts.persons.edit', compact('person')); } /** * Update the specified resource in storage. */ public function update(AttributeForm $request, int $id): RedirectResponse|JsonResponse { Event::dispatch('contacts.person.update.before', $id); $person = $this->personRepository->update($request->all(), $id); Event::dispatch('contacts.person.update.after', $person); if (request()->ajax()) { return response()->json([ 'data' => $person, 'message' => trans('admin::app.contacts.persons.index.update-success'), ], 200); } session()->flash('success', trans('admin::app.contacts.persons.index.update-success')); return redirect()->route('admin.contacts.persons.index'); } /** * Search person results. */ public function search(): JsonResource { if ($userIds = bouncer()->getAuthorizedUserIds()) { $persons = $this->personRepository ->pushCriteria(app(RequestCriteria::class)) ->findWhereIn('user_id', $userIds); } else { $persons = $this->personRepository ->pushCriteria(app(RequestCriteria::class)) ->all(); } return PersonResource::collection($persons); } /** * Remove the specified resource from storage. */ public function destroy(int $id): JsonResponse { $person = $this->personRepository->findOrFail($id); if ( $person->leads && $person->leads->count() > 0 ) { return response()->json([ 'message' => trans('admin::app.contacts.persons.index.delete-failed'), ], 400); } try { Event::dispatch('contacts.person.delete.before', $person); $person->delete(); Event::dispatch('contacts.person.delete.after', $person); return response()->json([ 'message' => trans('admin::app.contacts.persons.index.delete-success'), ], 200); } catch (Exception $exception) { return response()->json([ 'message' => trans('admin::app.contacts.persons.index.delete-failed'), ], 400); } } /** * Mass destroy the specified resources from storage. */ public function massDestroy(MassDestroyRequest $request): JsonResponse { try { $persons = $this->personRepository->findWhereIn('id', $request->input('indices', [])); $deletedCount = 0; $blockedCount = 0; foreach ($persons as $person) { if ( $person->leads && $person->leads->count() > 0 ) { $blockedCount++; continue; } Event::dispatch('contact.person.delete.before', $person); $this->personRepository->delete($person->id); Event::dispatch('contact.person.delete.after', $person); $deletedCount++; } $statusCode = 200; switch (true) { case $deletedCount > 0 && $blockedCount === 0: $message = trans('admin::app.contacts.persons.index.all-delete-success'); break; case $deletedCount > 0 && $blockedCount > 0: $message = trans('admin::app.contacts.persons.index.partial-delete-warning'); break; case $deletedCount === 0 && $blockedCount > 0: $message = trans('admin::app.contacts.persons.index.none-delete-warning'); $statusCode = 400; break; default: $message = trans('admin::app.contacts.persons.index.no-selection'); $statusCode = 400; break; } return response()->json(['message' => $message], $statusCode); } catch (Exception $exception) { return response()->json([ 'message' => trans('admin::app.contacts.persons.index.delete-failed'), ], 400); } } } ================================================ FILE: packages/Webkul/Admin/src/Http/Controllers/Contact/Persons/TagController.php ================================================ personRepository->find($id); if (! $person->tags->contains(request()->input('tag_id'))) { $person->tags()->attach(request()->input('tag_id')); } Event::dispatch('persons.tag.create.after', $person); return response()->json([ 'message' => trans('admin::app.contacts.persons.view.tags.create-success'), ]); } /** * Remove the specified resource from storage. */ public function detach(int $personId): JsonResponse { Event::dispatch('persons.tag.delete.before', $personId); $person = $this->personRepository->find($personId); $person->tags()->detach(request()->input('tag_id')); Event::dispatch('persons.tag.delete.after', $person); return response()->json([ 'message' => trans('admin::app.contacts.persons.view.tags.destroy-success'), ]); } } ================================================ FILE: packages/Webkul/Admin/src/Http/Controllers/Controller.php ================================================ route('admin.session.create'); } } ================================================ FILE: packages/Webkul/Admin/src/Http/Controllers/DashboardController.php ================================================ 'getOverAllStats', 'revenue-stats' => 'getRevenueStats', 'total-leads' => 'getTotalLeadsStats', 'revenue-by-sources' => 'getLeadsStatsBySources', 'revenue-by-types' => 'getLeadsStatsByTypes', 'top-selling-products' => 'getTopSellingProducts', 'top-persons' => 'getTopPersons', 'open-leads-by-states' => 'getOpenLeadsByStates', ]; /** * Create a new controller instance. * * @return void */ public function __construct(protected Dashboard $dashboardHelper) {} /** * Display a listing of the resource. * * @return View */ public function index() { return view('admin::dashboard.index')->with([ 'startDate' => $this->dashboardHelper->getStartDate(), 'endDate' => $this->dashboardHelper->getEndDate(), ]); } /** * Display a listing of the resource. * * @return JsonResponse */ public function stats() { $stats = $this->dashboardHelper->{$this->typeFunctions[request()->query('type')]}(); return response()->json([ 'statistics' => $stats, 'date_range' => $this->dashboardHelper->getDateRange(), ]); } } ================================================ FILE: packages/Webkul/Admin/src/Http/Controllers/DataGrid/SavedFilterController.php ================================================ guard()->user()->id; $this->validate(request(), [ 'name' => 'required|unique:datagrid_saved_filters,name,NULL,id,src,'.request('src').',user_id,'.$userId, ]); Event::dispatch('datagrid.saved_filter.create.before'); $savedFilter = $this->savedFilterRepository->create([ 'user_id' => $userId, 'name' => request('name'), 'src' => request('src'), 'applied' => request('applied'), ]); Event::dispatch('datagrid.saved_filter.create.after', $savedFilter); return response()->json([ 'data' => $savedFilter, 'message' => trans('admin::app.components.datagrid.toolbar.filter.saved-success'), ]); } /** * Retrieves the saved filters. */ public function get() { $savedFilters = $this->savedFilterRepository->findWhere([ 'src' => request()->get('src'), 'user_id' => auth()->guard()->user()->id, ]); return response()->json(['data' => $savedFilters]); } /** * Update the saved filter. */ public function update(int $id) { $userId = auth()->guard()->user()->id; $this->validate(request(), [ 'name' => 'required|unique:datagrid_saved_filters,name,'.$id.',id,src,'.request('src').',user_id,'.$userId, ]); $savedFilter = $this->savedFilterRepository->findOneWhere([ 'id' => $id, 'user_id' => auth()->guard()->user()->id, ]); if (! $savedFilter) { return response()->json([], 404); } Event::dispatch('datagrid.saved_filter.update.before', $id); $updatedFilter = $this->savedFilterRepository->update(request()->only([ 'name', 'src', 'applied', ]), $id); Event::dispatch('datagrid.saved_filter.update.after', $updatedFilter); return response()->json([ 'data' => $updatedFilter, 'message' => trans('admin::app.components.datagrid.toolbar.filter.updated-success'), ]); } /** * Delete the saved filter. */ public function destroy(int $id) { Event::dispatch('datagrid.saved_filter.delete.before', $id); $success = $this->savedFilterRepository->deleteWhere([ 'id' => $id, 'user_id' => auth()->guard()->user()->id, ]); Event::dispatch('datagrid.saved_filter.delete.after', $id); if (! $success) { return response()->json([ 'message' => trans('admin::app.components.datagrid.toolbar.filter.delete-error'), ]); } return response()->json([ 'message' => trans('admin::app.components.datagrid.toolbar.filter.delete-success'), ]); } } ================================================ FILE: packages/Webkul/Admin/src/Http/Controllers/DataGridController.php ================================================ validate(request(), [ 'datagrid_id' => ['required'], 'column' => ['required'], 'search' => ['required', 'min:2'], ]); /** * Preparing the datagrid instance and only columns. */ $datagrid = app(Crypt::decryptString($params['datagrid_id'])); $datagrid->prepareColumns(); /** * Finding the first column from the collection. */ $column = collect($datagrid->getColumns())->map(fn ($column) => $column->toArray())->where('index', $params['column'])->firstOrFail(); /** * Fetching on the basis of column options. */ return app($column['filterable_options']['repository']) ->select([$column['filterable_options']['column']['label'].' as label', $column['filterable_options']['column']['value'].' as value']) ->where($column['filterable_options']['column']['label'], 'LIKE', '%'.$params['search'].'%') ->get(); } } ================================================ FILE: packages/Webkul/Admin/src/Http/Controllers/Lead/ActivityController.php ================================================ activityRepository ->leftJoin('lead_activities', 'activities.id', '=', 'lead_activities.activity_id') ->where('lead_activities.lead_id', $id) ->get(); return ActivityResource::collection($this->concatEmailAsActivities($id, $activities)); } /** * Store a newly created resource in storage. */ public function concatEmailAsActivities($leadId, $activities) { $emails = DB::table('emails as child') ->select('child.*') ->join('emails as parent', 'child.parent_id', '=', 'parent.id') ->where('parent.lead_id', $leadId) ->union(DB::table('emails as parent')->where('parent.lead_id', $leadId)) ->get(); return $activities->concat($emails->map(function ($email) { return (object) [ 'id' => $email->id, 'parent_id' => $email->parent_id, 'title' => $email->subject, 'type' => 'email', 'is_done' => 1, 'comment' => $email->reply, 'schedule_from' => null, 'schedule_to' => null, 'user' => auth()->guard('user')->user(), 'participants' => [], 'location' => null, 'additional' => [ 'folders' => json_decode($email->folders), 'from' => json_decode($email->from), 'to' => json_decode($email->reply_to), 'cc' => json_decode($email->cc), 'bcc' => json_decode($email->bcc), ], 'files' => $this->attachmentRepository->findWhere(['email_id' => $email->id])->map(function ($attachment) { return (object) [ 'id' => $attachment->id, 'name' => $attachment->name, 'path' => $attachment->path, 'url' => $attachment->url, 'created_at' => $attachment->created_at, 'updated_at' => $attachment->updated_at, ]; }), 'created_at' => $email->created_at, 'updated_at' => $email->updated_at, ]; }))->sortByDesc('id')->sortByDesc('created_at'); } } ================================================ FILE: packages/Webkul/Admin/src/Http/Controllers/Lead/EmailController.php ================================================ getContent(), true); return response()->json([ 'data' => $this->transformToActivity($response['data']), 'message' => $response['message'], ]); return $response; } /** * Store a newly created resource in storage. * * @param int $id * @return Response */ public function detach($id) { Event::dispatch('email.update.before', request()->input('email_id')); $email = $this->emailRepository->update([ 'lead_id' => null, ], request()->input('email_id')); Event::dispatch('email.update.after', $email); return response()->json([ 'message' => trans('admin::app.mail.update-success'), ]); } /** * Transform the email data to activity resource. * * @param array $data * @return ActivityResource */ public function transformToActivity($data) { return new ActivityResource((object) [ 'id' => $data['id'], 'parent_id' => $data['parent_id'], 'title' => $data['subject'], 'type' => 'email', 'is_done' => 1, 'comment' => $data['reply'], 'schedule_from' => null, 'schedule_to' => null, 'user' => auth()->guard('user')->user(), 'participants' => [], 'location' => null, 'additional' => json_encode([ 'folders' => $data['folders'], 'from' => $data['from'], 'to' => $data['reply_to'], 'cc' => $data['cc'], 'bcc' => $data['bcc'], ]), 'files' => array_map(function ($attachment) { return (object) $attachment; }, $data['attachments']), 'created_at' => $data['created_at'], 'updated_at' => $data['updated_at'], ]); } } ================================================ FILE: packages/Webkul/Admin/src/Http/Controllers/Lead/LeadController.php ================================================ request->add(['entity_type' => 'leads']); } /** * Display a listing of the resource. */ public function index() { if (request()->ajax()) { return datagrid(LeadDataGrid::class)->process(); } if (request('pipeline_id')) { $pipeline = $this->pipelineRepository->find(request('pipeline_id')); } else { $pipeline = $this->pipelineRepository->getDefaultPipeline(); } return view('admin::leads.index', [ 'pipeline' => $pipeline, 'columns' => $this->getKanbanColumns(), ]); } /** * Returns a listing of the resource. */ public function get(): JsonResponse { if (request()->query('pipeline_id')) { $pipeline = $this->pipelineRepository->find(request()->query('pipeline_id')); } else { $pipeline = $this->pipelineRepository->getDefaultPipeline(); } if ($stageId = request()->query('pipeline_stage_id')) { $stages = $pipeline->stages->where('id', request()->query('pipeline_stage_id')); } else { $stages = $pipeline->stages; } foreach ($stages as $stage) { /** * We have to create a new instance of the lead repository every time, which is * why we're not using the injected one. */ $query = app(LeadRepository::class) ->pushCriteria(app(RequestCriteria::class)) ->where([ 'lead_pipeline_id' => $pipeline->id, 'lead_pipeline_stage_id' => $stage->id, ]); if ($userIds = bouncer()->getAuthorizedUserIds()) { $query->whereIn('leads.user_id', $userIds); } $stage->lead_value = (clone $query)->sum('lead_value'); $data[$stage->sort_order] = (new StageResource($stage))->jsonSerialize(); $data[$stage->sort_order]['leads'] = [ 'data' => LeadResource::collection($paginator = $query->with([ 'tags', 'type', 'source', 'user', 'person', 'person.organization', 'pipeline', 'pipeline.stages', 'stage', 'attribute_values', ])->paginate(10)), 'meta' => [ 'current_page' => $paginator->currentPage(), 'from' => $paginator->firstItem(), 'last_page' => $paginator->lastPage(), 'per_page' => $paginator->perPage(), 'to' => $paginator->lastItem(), 'total' => $paginator->total(), ], ]; } return response()->json($data); } /** * Show the form for creating a new resource. */ public function create(): View { return view('admin::leads.create'); } /** * Store a newly created resource in storage. */ public function store(LeadForm $request): RedirectResponse|JsonResponse { Event::dispatch('lead.create.before'); $data = request()->all(); $data['status'] = 1; if (! empty($data['lead_pipeline_stage_id'])) { $stage = $this->stageRepository->findOrFail($data['lead_pipeline_stage_id']); $data['lead_pipeline_id'] = $stage->lead_pipeline_id; } else { if (empty($data['lead_pipeline_id'])) { $pipeline = $this->pipelineRepository->getDefaultPipeline(); $data['lead_pipeline_id'] = $pipeline->id; } else { $pipeline = $this->pipelineRepository->findOrFail($data['lead_pipeline_id']); } $stage = $pipeline->stages()->first(); $data['lead_pipeline_stage_id'] = $stage->id; } if (in_array($stage->code, ['won', 'lost'])) { $data['closed_at'] = Carbon::now(); } $lead = $this->leadRepository->create($data); if (request()->ajax()) { return response()->json([ 'message' => trans('admin::app.leads.create-success'), 'data' => new LeadResource($lead), ]); } Event::dispatch('lead.create.after', $lead); session()->flash('success', trans('admin::app.leads.create-success')); if (! empty($data['lead_pipeline_id'])) { $params['pipeline_id'] = $data['lead_pipeline_id']; } return redirect()->route('admin.leads.index', $params ?? []); } /** * Show the form for editing the specified resource. */ public function edit(int $id): View { $lead = $this->leadRepository->findOrFail($id); return view('admin::leads.edit', compact('lead')); } /** * Display a resource. */ public function view(int $id) { $lead = $this->leadRepository->findOrFail($id); $userIds = bouncer()->getAuthorizedUserIds(); if ( $userIds && ! in_array($lead->user_id, $userIds) ) { return redirect()->route('admin.leads.index'); } return view('admin::leads.view', compact('lead')); } /** * Update the specified resource in storage. */ public function update(LeadForm $request, int $id): RedirectResponse|JsonResponse { Event::dispatch('lead.update.before', $id); $data = $request->all(); if (isset($data['lead_pipeline_stage_id'])) { $stage = $this->stageRepository->findOrFail($data['lead_pipeline_stage_id']); $data['lead_pipeline_id'] = $stage->lead_pipeline_id; } else { $pipeline = $this->pipelineRepository->getDefaultPipeline(); $stage = $pipeline->stages()->first(); $data['lead_pipeline_id'] = $pipeline->id; $data['lead_pipeline_stage_id'] = $stage->id; } $lead = $this->leadRepository->update($data, $id); Event::dispatch('lead.update.after', $lead); if (request()->ajax()) { return response()->json([ 'message' => trans('admin::app.leads.update-success'), ]); } session()->flash('success', trans('admin::app.leads.update-success')); if (request()->has('closed_at')) { return redirect()->back(); } else { return redirect()->route('admin.leads.index', $data['lead_pipeline_id']); } } /** * Update the lead attributes. */ public function updateAttributes(int $id) { $data = request()->all(); $attributes = $this->attributeRepository->findWhere([ 'entity_type' => 'leads', ['code', 'NOTIN', ['title', 'description']], ]); Event::dispatch('lead.update.before', $id); $lead = $this->leadRepository->update($data, $id, $attributes); Event::dispatch('lead.update.after', $lead); return response()->json([ 'message' => trans('admin::app.leads.update-success'), ]); } /** * Update the lead stage. */ public function updateStage(int $id) { $this->validate(request(), [ 'lead_pipeline_stage_id' => 'required', ]); $lead = $this->leadRepository->findOrFail($id); $stage = $lead->pipeline->stages() ->where('id', request()->input('lead_pipeline_stage_id')) ->firstOrFail(); Event::dispatch('lead.update.before', $id); $payload = request()->merge([ 'entity_type' => 'leads', 'lead_pipeline_stage_id' => $stage->id, ])->only([ 'closed_at', 'lost_reason', 'lead_pipeline_stage_id', 'entity_type', ]); $lead = $this->leadRepository->update($payload, $id, ['lead_pipeline_stage_id']); Event::dispatch('lead.update.after', $lead); return response()->json([ 'message' => trans('admin::app.leads.update-success'), ]); } /** * Search person results. */ public function search(): AnonymousResourceCollection { if ($userIds = bouncer()->getAuthorizedUserIds()) { $results = $this->leadRepository ->pushCriteria(app(RequestCriteria::class)) ->findWhereIn('user_id', $userIds); } else { $results = $this->leadRepository ->pushCriteria(app(RequestCriteria::class)) ->all(); } return LeadResource::collection($results); } /** * Remove the specified resource from storage. */ public function destroy(int $id): JsonResponse { $this->leadRepository->findOrFail($id); try { Event::dispatch('lead.delete.before', $id); $this->leadRepository->delete($id); Event::dispatch('lead.delete.after', $id); return response()->json([ 'message' => trans('admin::app.leads.destroy-success'), ]); } catch (\Exception $exception) { return response()->json([ 'message' => trans('admin::app.leads.destroy-failed'), ], 400); } } /** * Mass update the specified resources. */ public function massUpdate(MassUpdateRequest $massUpdateRequest): JsonResponse { $leads = $this->leadRepository->findWhereIn('id', $massUpdateRequest->input('indices')); try { foreach ($leads as $lead) { Event::dispatch('lead.update.before', $lead->id); $lead = $this->leadRepository->find($lead->id); $lead?->update(['lead_pipeline_stage_id' => $massUpdateRequest->input('value')]); Event::dispatch('lead.update.before', $lead->id); } return response()->json([ 'message' => trans('admin::app.leads.update-success'), ]); } catch (\Exception $th) { return response()->json([ 'message' => trans('admin::app.leads.update-failed'), ], 400); } } /** * Mass delete the specified resources. */ public function massDestroy(MassDestroyRequest $massDestroyRequest): JsonResponse { $leads = $this->leadRepository->findWhereIn('id', $massDestroyRequest->input('indices')); try { foreach ($leads as $lead) { Event::dispatch('lead.delete.before', $lead->id); $this->leadRepository->delete($lead->id); Event::dispatch('lead.delete.after', $lead->id); } return response()->json([ 'message' => trans('admin::app.leads.destroy-success'), ]); } catch (\Exception $exception) { return response()->json([ 'message' => trans('admin::app.leads.destroy-failed'), ]); } } /** * Attach product to lead. */ public function addProduct(int $leadId): JsonResponse { $product = $this->productRepository->updateOrCreate( [ 'lead_id' => $leadId, 'product_id' => request()->input('product_id'), ], array_merge( request()->all(), [ 'lead_id' => $leadId, 'amount' => request()->input('price') * request()->input('quantity'), ], ) ); return response()->json([ 'data' => $product, 'message' => trans('admin::app.leads.update-success'), ]); } /** * Remove product attached to lead. */ public function removeProduct(int $id): JsonResponse { try { Event::dispatch('lead.product.delete.before', $id); $this->productRepository->deleteWhere([ 'lead_id' => $id, 'product_id' => request()->input('product_id'), ]); Event::dispatch('lead.product.delete.after', $id); return response()->json([ 'message' => trans('admin::app.leads.destroy-success'), ]); } catch (\Exception $exception) { return response()->json([ 'message' => trans('admin::app.leads.destroy-failed'), ]); } } /** * Kanban lookup. */ public function kanbanLookup() { $params = $this->validate(request(), [ 'column' => ['required'], 'search' => ['required', 'min:2'], ]); /** * Finding the first column from the collection. */ $column = collect($this->getKanbanColumns())->where('index', $params['column'])->firstOrFail(); /** * Fetching on the basis of column options. */ return app($column['filterable_options']['repository']) ->select([$column['filterable_options']['column']['label'].' as label', $column['filterable_options']['column']['value'].' as value']) ->where($column['filterable_options']['column']['label'], 'LIKE', '%'.$params['search'].'%') ->get() ->map ->only('label', 'value'); } /** * Get columns for the kanban view. */ private function getKanbanColumns(): array { return [ [ 'index' => 'id', 'label' => trans('admin::app.leads.index.kanban.columns.id'), 'type' => 'integer', 'searchable' => false, 'search_field' => 'in', 'filterable' => true, 'filterable_type' => null, 'filterable_options' => [], 'allow_multiple_values' => true, 'sortable' => true, 'visibility' => true, ], [ 'index' => 'lead_value', 'label' => trans('admin::app.leads.index.kanban.columns.lead-value'), 'type' => 'string', 'searchable' => false, 'search_field' => 'in', 'filterable' => true, 'filterable_type' => null, 'filterable_options' => [], 'allow_multiple_values' => true, 'sortable' => true, 'visibility' => true, ], [ 'index' => 'user_id', 'label' => trans('admin::app.leads.index.kanban.columns.sales-person'), 'type' => 'string', 'searchable' => false, 'search_field' => 'in', 'filterable' => true, 'filterable_type' => 'searchable_dropdown', 'filterable_options' => [ 'repository' => UserRepository::class, 'column' => [ 'label' => 'name', 'value' => 'id', ], ], 'allow_multiple_values' => true, 'sortable' => true, 'visibility' => true, ], [ 'index' => 'person.id', 'label' => trans('admin::app.leads.index.kanban.columns.contact-person'), 'type' => 'string', 'searchable' => false, 'search_field' => 'in', 'filterable' => true, 'filterable_options' => [], 'allow_multiple_values' => true, 'sortable' => true, 'visibility' => true, 'filterable_type' => 'searchable_dropdown', 'filterable_options' => [ 'repository' => PersonRepository::class, 'column' => [ 'label' => 'name', 'value' => 'id', ], ], ], [ 'index' => 'lead_type_id', 'label' => trans('admin::app.leads.index.kanban.columns.lead-type'), 'type' => 'string', 'searchable' => false, 'search_field' => 'in', 'filterable' => true, 'filterable_type' => 'dropdown', 'filterable_options' => $this->typeRepository->all(['name as label', 'id as value'])->toArray(), 'allow_multiple_values' => true, 'sortable' => true, 'visibility' => true, ], [ 'index' => 'lead_source_id', 'label' => trans('admin::app.leads.index.kanban.columns.source'), 'type' => 'string', 'searchable' => false, 'search_field' => 'in', 'filterable' => true, 'filterable_type' => 'dropdown', 'filterable_options' => $this->sourceRepository->all(['name as label', 'id as value'])->toArray(), 'allow_multiple_values' => true, 'sortable' => true, 'visibility' => true, ], [ 'index' => 'tags.name', 'label' => trans('admin::app.leads.index.kanban.columns.tags'), 'type' => 'string', 'searchable' => false, 'search_field' => 'in', 'filterable' => true, 'filterable_options' => [], 'allow_multiple_values' => true, 'sortable' => true, 'visibility' => true, 'filterable_type' => 'searchable_dropdown', 'filterable_options' => [ 'repository' => TagRepository::class, 'column' => [ 'label' => 'name', 'value' => 'name', ], ], ], ]; } /** * Create lead with specified AI. */ public function createByAI() { $leadData = []; $errorMessages = []; foreach (request()->file('files') as $file) { $lead = $this->processFile($file); if ( isset($lead['status']) && $lead['status'] === 'error' ) { $errorMessages[] = $lead['message']; } else { $leadData[] = $lead; } } if (isset($errorMessages[0]['code'])) { return response()->json(MagicAI::errorHandler($errorMessages[0]['message'])); } if ( empty($leadData) && ! empty($errorMessages) ) { return response()->json(MagicAI::errorHandler(implode(', ', $errorMessages)), 400); } if (empty($leadData)) { return response()->json(MagicAI::errorHandler(trans('admin::app.leads.no-valid-files')), 400); } return response()->json([ 'message' => trans('admin::app.leads.create-success'), 'leads' => $this->createLeads($leadData), ]); } /** * Process file. * * @param mixed $file */ private function processFile($file) { $validator = Validator::make( ['file' => $file], ['file' => 'required|extensions:'.str_replace(' ', '', self::SUPPORTED_TYPES)] ); if ($validator->fails()) { return MagicAI::errorHandler($validator->errors()->first()); } $base64Pdf = base64_encode(file_get_contents($file->getRealPath())); $extractedData = MagicAIService::extractDataFromFile($base64Pdf); $lead = MagicAI::mapAIDataToLead($extractedData); return $lead; } /** * Create multiple leads. */ private function createLeads($rawLeads): array { $leads = []; foreach ($rawLeads as $rawLead) { Event::dispatch('lead.create.before'); foreach ($rawLead['person']['emails'] as $email) { $person = $this->personRepository ->whereJsonContains('emails', [['value' => $email['value']]]) ->first(); if ($person) { $rawLead['person']['id'] = $person->id; break; } } $pipeline = $this->pipelineRepository->getDefaultPipeline(); $stage = $pipeline->stages()->first(); $lead = $this->leadRepository->create(array_merge($rawLead, [ 'lead_pipeline_id' => $pipeline->id, 'lead_pipeline_stage_id' => $stage->id, ])); Event::dispatch('lead.create.after', $lead); $leads[] = $lead; } return $leads; } } ================================================ FILE: packages/Webkul/Admin/src/Http/Controllers/Lead/QuoteController.php ================================================ leadRepository->find($id); if (! $lead->quotes->contains(request('id'))) { $lead->quotes()->attach(request('id')); } Event::dispatch('leads.quote.create.after', $lead); return response()->json([ 'message' => trans('admin::app.leads.quote-create-success'), ], 200); } /** * Remove the specified resource from storage. * * @param int $leadId * @param int $tagId * @return Response */ public function delete($leadId) { Event::dispatch('leads.quote.delete.before', $leadId); $lead = $this->leadRepository->find($leadId); $lead->quotes()->detach(request('quote_id')); Event::dispatch('leads.quote.delete.after', $lead); return response()->json([ 'message' => trans('admin::app.leads.view.quotes.destroy-success'), ], 200); } } ================================================ FILE: packages/Webkul/Admin/src/Http/Controllers/Lead/TagController.php ================================================ leadRepository->find($id); if (! $lead->tags->contains(request()->input('tag_id'))) { $lead->tags()->attach(request()->input('tag_id')); } Event::dispatch('leads.tag.create.after', $lead); return response()->json([ 'message' => trans('admin::app.leads.view.tags.create-success'), ]); } /** * Remove the specified resource from storage. * * @param int $leadId * @return Response */ public function detach($leadId) { Event::dispatch('leads.tag.delete.before', $leadId); $lead = $this->leadRepository->find($leadId); $lead->tags()->detach(request()->input('tag_id')); Event::dispatch('leads.tag.delete.after', $lead); return response()->json([ 'message' => trans('admin::app.leads.view.tags.destroy-success'), ]); } } ================================================ FILE: packages/Webkul/Admin/src/Http/Controllers/Mail/EmailController.php ================================================ route('admin.mail.index', ['route' => SupportedFolderEnum::INBOX->value]); } if (! bouncer()->hasPermission('mail.'.$route)) { abort(401, trans('admin::app.mail.unauthorized')); } if (request()->ajax()) { return datagrid(EmailDataGrid::class)->process(); } return view('admin::mail.index', compact('route')); } /** * Display a resource. * * @return View */ public function view() { $route = request('route'); $email = $this->emailRepository ->with([ 'emails', 'attachments', 'emails.attachments', 'lead', 'lead.person', 'lead.tags', 'lead.source', 'lead.type', 'person', ]) ->findOrFail(request('id')); if ($userIds = bouncer()->getAuthorizedUserIds()) { $results = $this->leadRepository->findWhere([ ['id', '=', $email->lead_id], ['user_id', 'IN', $userIds], ]); } else { $results = $this->leadRepository->findWhere([ ['id', '=', $email->lead_id], ]); } if (empty($results->toArray())) { unset($email->lead_id); } if ($route == SupportedFolderEnum::DRAFT->value) { return response()->json([ 'data' => new EmailResource($email), ]); } return view('admin::mail.view', compact('email', 'route')); } /** * Store a newly created resource in storage. * * @return Response */ public function store() { $this->validate(request(), [ 'reply_to' => 'required|array|min:1', 'reply_to.*' => 'email', 'reply' => 'required', ]); Event::dispatch('email.create.before'); $email = $this->emailRepository->create(request()->all()); if (! request('is_draft')) { try { Mail::send(new Email($email)); $this->emailRepository->update([ 'folders' => [SupportedFolderEnum::SENT->value], ], $email->id); } catch (Exception $e) { } } Event::dispatch('email.create.after', $email); if (request()->ajax()) { return response()->json([ 'data' => new EmailResource($email), 'message' => trans('admin::app.mail.create-success'), ]); } if (request('is_draft')) { session()->flash('success', trans('admin::app.mail.saved-to-draft')); return redirect()->route('admin.mail.index', ['route' => SupportedFolderEnum::DRAFT->value]); } session()->flash('success', trans('admin::app.mail.create-success')); return redirect()->route('admin.mail.index', ['route' => SupportedFolderEnum::SENT->value]); } /** * Update the specified resource in storage. * * @param int $id * @return Response */ public function update($id) { Event::dispatch('email.update.before', $id); $data = request()->all(); if (! is_null(request('is_draft'))) { $data['folders'] = request('is_draft') ? [SupportedFolderEnum::DRAFT->value] : [SupportedFolderEnum::OUTBOX->value]; } $email = $this->emailRepository->update($data, request('id') ?? $id); Event::dispatch('email.update.after', $email); if (! is_null(request('is_draft')) && ! request('is_draft')) { try { Mail::send(new Email($email)); $this->emailRepository->update([ 'folders' => [SupportedFolderEnum::INBOX->value, SupportedFolderEnum::SENT->value], ], $email->id); } catch (Exception $e) { } } if (! is_null(request('is_draft'))) { if (request('is_draft')) { session()->flash('success', trans('admin::app.mail.saved-to-draft')); return redirect()->route('admin.mail.index', ['route' => SupportedFolderEnum::DRAFT->value]); } else { session()->flash('success', trans('admin::app.mail.create-success')); return redirect()->route('admin.mail.index', ['route' => SupportedFolderEnum::INBOX->value]); } } if (request()->ajax()) { return response()->json([ 'data' => new EmailResource($email->refresh()), 'message' => trans('admin::app.mail.update-success'), ]); } session()->flash('success', trans('admin::app.mail.update-success')); return redirect()->back(); } /** * Run process inbound parse email. * * @return Response */ public function inboundParse(InboundEmailProcessor $inboundEmailProcessor) { $inboundEmailProcessor->processMessage(request('email')); return response()->json([], 200); } /** * Download file from storage * * @param int $id * @return View */ public function download($id) { $attachment = $this->attachmentRepository->findOrFail($id); try { return Storage::download($attachment->path); } catch (Exception $e) { session()->flash('error', $e->getMessage()); return redirect()->back(); } } /** * Mass Update the specified resources. */ public function massUpdate(MassUpdateRequest $massUpdateRequest): JsonResponse { $emails = $this->emailRepository->findWhereIn('id', $massUpdateRequest->input('indices')); try { foreach ($emails as $email) { Event::dispatch('email.update.before', $email->id); $this->emailRepository->update([ 'folders' => request('folders'), ], $email->id); Event::dispatch('email.update.after', $email->id); } return response()->json([ 'message' => trans('admin::app.mail.mass-update-success'), ]); } catch (Exception) { return response()->json([ 'message' => trans('admin::app.mail.mass-update-success'), ], 400); } } /** * Remove the specified resource from storage. */ public function destroy(int $id): JsonResponse|RedirectResponse { $email = $this->emailRepository->findOrFail($id); try { Event::dispatch('email.'.request('type').'.before', $id); $parentId = $email->parent_id; if (request('type') == SupportedFolderEnum::TRASH->value) { $this->emailRepository->update([ 'folders' => [SupportedFolderEnum::TRASH->value], ], $id); } else { $this->emailRepository->delete($id); } Event::dispatch('email.'.request('type').'.after', $id); if (request()->ajax()) { return response()->json([ 'message' => trans('admin::app.mail.delete-success'), ], 200); } session()->flash('success', trans('admin::app.mail.delete-success')); if ($parentId) { return redirect()->back(); } return redirect()->route('admin.mail.index', ['route' => SupportedFolderEnum::INBOX->value]); } catch (Exception $exception) { if (request()->ajax()) { return response()->json([ 'message' => trans('admin::app.mail.delete-failed'), ], 400); } session()->flash('error', trans('admin::app.mail.delete-failed')); return redirect()->back(); } } /** * Mass Delete the specified resources. */ public function massDestroy(MassDestroyRequest $massDestroyRequest): JsonResponse { $mails = $this->emailRepository->findWhereIn('id', $massDestroyRequest->input('indices')); try { foreach ($mails as $email) { Event::dispatch('email.'.$massDestroyRequest->input('type').'.before', $email->id); if ($massDestroyRequest->input('type') == SupportedFolderEnum::TRASH->value) { $this->emailRepository->update(['folders' => [SupportedFolderEnum::TRASH->value]], $email->id); } else { $this->emailRepository->delete($email->id); } Event::dispatch('email.'.$massDestroyRequest->input('type').'.after', $email->id); } return response()->json([ 'message' => trans('admin::app.mail.delete-success'), ]); } catch (Exception $e) { return response()->json([ 'message' => trans('admin::app.mail.delete-success'), ]); } } } ================================================ FILE: packages/Webkul/Admin/src/Http/Controllers/Mail/TagController.php ================================================ emailRepository->find($id); if (! $mail->tags->contains(request()->input('tag_id'))) { $mail->tags()->attach(request()->input('tag_id')); } Event::dispatch('mails.tag.create.after', $mail); return response()->json([ 'message' => trans('admin::app.mail.view.tags.create-success'), ]); } /** * Remove the specified resource from storage. */ public function detach(int $mailId): JsonResponse { Event::dispatch('mails.tag.delete.before', $mailId); $mail = $this->emailRepository->find($mailId); $mail->tags()->detach(request()->input('tag_id')); Event::dispatch('mails.tag.delete.after', $mail); return response()->json([ 'message' => trans('admin::app.mail.view.tags.destroy-success'), ]); } } ================================================ FILE: packages/Webkul/Admin/src/Http/Controllers/Products/ActivityController.php ================================================ activityRepository ->leftJoin('product_activities', 'activities.id', '=', 'product_activities.activity_id') ->where('product_activities.product_id', $id) ->get(); return ActivityResource::collection($this->concatEmail($activities)); } /** * Store a newly created resource in storage. */ public function concatEmail($activities) { return $activities->sortByDesc('id')->sortByDesc('created_at'); } } ================================================ FILE: packages/Webkul/Admin/src/Http/Controllers/Products/ProductController.php ================================================ request->add(['entity_type' => 'products']); } /** * Display a listing of the resource. */ public function index(): View|JsonResponse { if (request()->ajax()) { return datagrid(ProductDataGrid::class)->process(); } return view('admin::products.index'); } /** * Show the form for creating a new resource. */ public function create(): View { return view('admin::products.create'); } /** * Store a newly created resource in storage. * * @return Response */ public function store(AttributeForm $request) { Event::dispatch('product.create.before'); $product = $this->productRepository->create($request->all()); Event::dispatch('product.create.after', $product); session()->flash('success', trans('admin::app.products.index.create-success')); return redirect()->route('admin.products.index'); } /** * Show the form for viewing the specified resource. */ public function view(int $id): View { $product = $this->productRepository->findOrFail($id); return view('admin::products.view', compact('product')); } /** * Show the form for editing the specified resource. */ public function edit(int $id): View|JsonResponse { $product = $this->productRepository->findOrFail($id); $inventories = $product->inventories() ->with('location') ->get() ->map(function ($inventory) { return [ 'id' => $inventory->id, 'name' => $inventory->location->name, 'warehouse_id' => $inventory->warehouse_id, 'warehouse_location_id' => $inventory->warehouse_location_id, 'in_stock' => $inventory->in_stock, 'allocated' => $inventory->allocated, ]; }); return view('admin::products.edit', compact('product', 'inventories')); } /** * Update the specified resource in storage. */ public function update(AttributeForm $request, int $id) { Event::dispatch('product.update.before', $id); $product = $this->productRepository->update($request->all(), $id); Event::dispatch('product.update.after', $product); if (request()->ajax()) { return response()->json([ 'message' => trans('admin::app.products.index.update-success'), ]); } session()->flash('success', trans('admin::app.products.index.update-success')); return redirect()->route('admin.products.index'); } /** * Store a newly created resource in storage. */ public function storeInventories(int $id, ?int $warehouseId = null): JsonResponse { $this->validate(request(), [ 'inventories' => 'array', 'inventories.*.warehouse_location_id' => 'required', 'inventories.*.warehouse_id' => 'required', 'inventories.*.in_stock' => 'required|integer|min:0', 'inventories.*.allocated' => 'required|integer|min:0', ]); $product = $this->productRepository->findOrFail($id); Event::dispatch('product.update.before', $id); $this->productRepository->saveInventories(request()->all(), $id, $warehouseId); Event::dispatch('product.update.after', $product); return new JsonResponse([ 'message' => trans('admin::app.products.index.update-success'), ], 200); } /** * Search product results */ public function search(): JsonResource { $products = $this->productRepository ->pushCriteria(app(RequestCriteria::class)) ->orderBy('created_at', 'desc') ->get(); return ProductResource::collection($products); } /** * Returns product inventories grouped by warehouse. */ public function warehouses(int $id): JsonResponse { $warehouses = $this->productRepository->getInventoriesGroupedByWarehouse($id); return response()->json(array_values($warehouses)); } /** * Remove the specified resource from storage. */ public function destroy(int $id): JsonResponse { $product = $this->productRepository->findOrFail($id); try { Event::dispatch('settings.products.delete.before', $id); $product->delete($id); Event::dispatch('settings.products.delete.after', $id); return new JsonResponse([ 'message' => trans('admin::app.products.index.delete-success'), ], 200); } catch (\Exception $exception) { return new JsonResponse([ 'message' => trans('admin::app.products.index.delete-failed'), ], 400); } } /** * Mass Delete the specified resources. */ public function massDestroy(MassDestroyRequest $massDestroyRequest): JsonResponse { $indices = $massDestroyRequest->input('indices'); foreach ($indices as $index) { Event::dispatch('product.delete.before', $index); $this->productRepository->delete($index); Event::dispatch('product.delete.after', $index); } return new JsonResponse([ 'message' => trans('admin::app.products.index.delete-success'), ]); } } ================================================ FILE: packages/Webkul/Admin/src/Http/Controllers/Products/TagController.php ================================================ productRepository->findOrFail($id); if (! $product->tags->contains(request()->input('tag_id'))) { $product->tags()->attach(request()->input('tag_id')); } Event::dispatch('products.tag.create.after', $product); return response()->json([ 'message' => trans('admin::app.leads.view.tags.create-success'), ]); } /** * Remove the specified resource from storage. * * @param int $productId * @return Response */ public function detach($productId) { Event::dispatch('products.tag.delete.before', $productId); $product = $this->productRepository->find($productId); $product->tags()->detach(request()->input('tag_id')); Event::dispatch('products.tag.delete.after', $product); return response()->json([ 'message' => trans('admin::app.leads.view.tags.destroy-success'), ]); } } ================================================ FILE: packages/Webkul/Admin/src/Http/Controllers/Quote/QuoteController.php ================================================ request->add(['entity_type' => 'quotes']); } /** * Display a listing of the resource. */ public function index(): View|JsonResponse { if (request()->ajax()) { return datagrid(QuoteDataGrid::class)->process(); } return view('admin::quotes.index'); } /** * Show the form for creating a new resource. */ public function create(): View { $lead = $this->leadRepository->find(request('id')); return view('admin::quotes.create', compact('lead')); } /** * Store a newly created resource in storage. */ public function store(AttributeForm $request): RedirectResponse { $this->additionalValidation(); Event::dispatch('quote.create.before'); $quote = $this->quoteRepository->create($request->all()); $leadId = request('lead_id'); if ($leadId) { $lead = $this->leadRepository->find($leadId); $lead->quotes()->attach($quote->id); } Event::dispatch('quote.create.after', $quote); session()->flash('success', trans('admin::app.quotes.index.create-success')); return request()->query('from') === 'lead' && $leadId ? redirect()->route('admin.leads.view', ['id' => $leadId, 'from' => 'quotes']) : redirect()->route('admin.quotes.index'); } /** * Show the form for editing the specified resource. */ public function edit(int $id): View { $quote = $this->quoteRepository->findOrFail($id); return view('admin::quotes.edit', compact('quote')); } /** * Update the specified resource in storage. */ public function update(AttributeForm $request, int $id): RedirectResponse { $this->additionalValidation(); Event::dispatch('quote.update.before', $id); $quote = $this->quoteRepository->update($request->all(), $id); $quote->leads()->detach(); $leadId = request('lead_id'); if ($leadId) { $lead = $this->leadRepository->find($leadId); $lead->quotes()->attach($quote->id); } Event::dispatch('quote.update.after', $quote); session()->flash('success', trans('admin::app.quotes.index.update-success')); return request()->query('from') === 'lead' && $leadId ? redirect()->route('admin.leads.view', ['id' => $leadId, 'from' => 'quotes']) : redirect()->route('admin.quotes.index'); } /** * Search the quotes. */ public function search(): AnonymousResourceCollection { $quotes = $this->quoteRepository ->pushCriteria(app(RequestCriteria::class)) ->all(); return QuoteResource::collection($quotes); } /** * Remove the specified resource from storage. */ public function destroy(int $id): JsonResponse { $this->quoteRepository->findOrFail($id); try { Event::dispatch('quote.delete.before', $id); $this->quoteRepository->delete($id); Event::dispatch('quote.delete.after', $id); return response()->json([ 'message' => trans('admin::app.quotes.index.delete-success'), ], 200); } catch (\Exception $exception) { return response()->json([ 'message' => trans('admin::app.quotes.index.delete-failed'), ], 400); } } /** * Mass Delete the specified resources. */ public function massDestroy(MassDestroyRequest $massDestroyRequest): JsonResponse { $quotes = $this->quoteRepository->findWhereIn('id', $massDestroyRequest->input('indices')); try { foreach ($quotes as $quotes) { Event::dispatch('quote.delete.before', $quotes->id); $this->quoteRepository->delete($quotes->id); Event::dispatch('quote.delete.after', $quotes->id); } return response()->json([ 'message' => trans('admin::app.quotes.index.delete-success'), ]); } catch (\Exception $exception) { return response()->json([ 'message' => trans('admin::app.quotes.index.delete-failed'), ], 400); } } /** * Print and download the for the specified resource. */ public function print($id): Response|StreamedResponse { $quote = $this->quoteRepository->findOrFail($id); return $this->downloadPDF( view('admin::quotes.pdf', compact('quote'))->render(), 'Quote_'.$quote->subject.'_'.$quote->created_at->format('d-m-Y') ); } /** * Additional validation for quote product items. */ private function additionalValidation(): void { $this->validate(request(), [ 'items' => 'required|array', 'items.*.product_id' => 'required|exists:products,id', 'items.*.quantity' => 'required|numeric|min:0', 'items.*.price' => 'required|numeric|min:0', 'items.*.total' => 'required|numeric|min:0', 'items.*.discount_amount' => 'required|numeric|min:0', 'items.*.tax_amount' => 'required|numeric|min:0', 'items.*.final_total' => 'required|numeric|min:0', ]); } } ================================================ FILE: packages/Webkul/Admin/src/Http/Controllers/Settings/AttributeController.php ================================================ ajax()) { return datagrid(AttributeDataGrid::class)->process(); } return view('admin::settings.attributes.index'); } /** * Show the form for creating a new resource. */ public function create(): View { return view('admin::settings.attributes.create'); } /** * Store a newly created resource in storage. */ public function store(): RedirectResponse { $this->validate(request(), [ 'code' => ['required', 'unique:attributes,code,NULL,NULL,entity_type,'.request('entity_type'), new Code], 'name' => 'required', 'type' => 'required', ]); Event::dispatch('settings.attribute.create.before'); request()->request->add(['quick_add' => 1]); $attribute = $this->attributeRepository->create(request()->all()); Event::dispatch('settings.attribute.create.after', $attribute); session()->flash('success', trans('admin::app.settings.attributes.index.create-success')); return redirect()->route('admin.settings.attributes.index'); } /** * Show the form for editing the specified resource. */ public function edit(int $id): View { $attribute = $this->attributeRepository->findOrFail($id); return view('admin::settings.attributes.edit', compact('attribute')); } /** * Update the specified resource in storage. */ public function update($id): RedirectResponse { $this->validate(request(), [ 'code' => ['required', 'unique:attributes,code,NULL,NULL,entity_type,'.$id, new Code], 'name' => 'required', 'type' => 'required', ]); Event::dispatch('settings.attribute.update.before', $id); $attribute = $this->attributeRepository->update(request()->all(), $id); Event::dispatch('settings.attribute.update.after', $attribute); session()->flash('success', trans('admin::app.settings.attributes.index.update-success')); return redirect()->route('admin.settings.attributes.index'); } /** * Remove the specified resource from storage. */ public function destroy(int $id): JsonResponse { $attribute = $this->attributeRepository->findOrFail($id); if (! $attribute->is_user_defined) { return response()->json([ 'message' => trans('admin::app.settings.attributes.index.user-define-error'), ], 400); } try { Event::dispatch('settings.attribute.delete.before', $id); $this->attributeRepository->delete($id); Event::dispatch('settings.attribute.delete.after', $id); return response()->json([ 'status' => true, 'message' => trans('admin::app.settings.attributes.index.delete-success'), ], 200); } catch (\Exception $exception) { return response()->json([ 'message' => trans('admin::app.settings.attributes.index.delete-failed'), ], 400); } } /** * Check unique validation. * * @return void */ public function checkUniqueValidation() { $attribute = $this->attributeRepository->findOneWhere([ 'code' => request('attribute_code'), ]); return response()->json([ 'validated' => $this->attributeValueRepository->isValueUnique( request('entity_id'), request('entity_type'), $attribute, request('attribute_value'), ), ]); } /** * Search attribute lookup results */ public function lookup($lookup): JsonResponse { $results = $this->attributeRepository->getLookUpOptions($lookup, request()->input('query')); return response()->json($results); } /** * Search attribute lookup results */ public function lookupEntity(string $lookup): JsonResponse { $result = $this->attributeRepository->getLookUpEntity($lookup, request()->input('query')); return response()->json($result); } /** * Mass Delete the specified resources. */ public function massDestroy(MassDestroyRequest $massDestroyRequest): JsonResponse { $count = 0; $attributes = $this->attributeRepository->findWhereIn('id', $massDestroyRequest->input('indices')); foreach ($attributes as $attribute) { $attribute = $this->attributeRepository->find($attribute->id); if (! $attribute->is_user_defined) { continue; } Event::dispatch('settings.attribute.delete.before', $attribute->id); $this->attributeRepository->delete($attribute->id); Event::dispatch('settings.attribute.delete.after', $attribute->id); $count++; } if (! $count) { return response()->json([ 'message' => trans('admin::app.settings.attributes.index.mass-delete-failed'), ], 400); } return response()->json([ 'message' => trans('admin::app.settings.attributes.index.delete-success'), ]); } /** * Get attribute options associated with attribute. * * @return View */ public function getAttributeOptions(int $id) { $attribute = $this->attributeRepository->findOrFail($id); return $attribute->options()->orderBy('sort_order')->get(); } /** * Download image or file */ public function download() { if (! request('path')) { return false; } return Storage::download(request('path')); } } ================================================ FILE: packages/Webkul/Admin/src/Http/Controllers/Settings/DataTransfer/ImportController.php ================================================ ajax()) { return datagrid(ImportDataGrid::class)->process(); } return view('admin::settings.data-transfer.imports.index'); } /** * Show the form for creating a new resource. */ public function create(): View { return view('admin::settings.data-transfer.imports.create'); } /** * Store a newly created resource in storage. */ public function store(): RedirectResponse { $importers = array_keys(config('importers')); $this->validate(request(), [ 'type' => 'required|in:'.implode(',', $importers), 'action' => 'required:in:append,delete', 'validation_strategy' => 'required:in:stop-on-errors,skip-errors', 'allowed_errors' => 'required|integer|min:0', 'field_separator' => 'required', 'file' => 'required|mimes:csv,xls,xlsx,txt', ]); Event::dispatch('data_transfer.imports.create.before'); $data = request()->only([ 'type', 'action', 'process_in_queue', 'validation_strategy', 'validation_strategy', 'allowed_errors', 'field_separator', ]); if (! isset($data['process_in_queue'])) { $data['process_in_queue'] = false; } else { $data['process_in_queue'] = true; } $import = $this->importRepository->create( array_merge( [ 'file_path' => request()->file('file')->storeAs( 'imports', time().'-'.request()->file('file')->getClientOriginalName(), 'public' ), ], $data ) ); Event::dispatch('data_transfer.imports.create.after', $import); session()->flash('success', trans('admin::app.settings.data-transfer.imports.create-success')); return redirect()->route('admin.settings.data_transfer.imports.import', $import->id); } /** * Show the form for editing a new resource. */ public function edit(int $id): View { $import = $this->importRepository->findOrFail($id); return view('admin::settings.data-transfer.imports.edit', compact('import')); } /** * Update a resource in storage. */ public function update(int $id): RedirectResponse { $importers = array_keys(config('importers')); $import = $this->importRepository->findOrFail($id); $this->validate(request(), [ 'type' => 'required|in:'.implode(',', $importers), 'action' => 'required:in:append,delete', 'validation_strategy' => 'required:in:stop-on-errors,skip-errors', 'allowed_errors' => 'required|integer|min:0', 'field_separator' => 'required', 'file' => 'mimes:csv,xls,xlsx,txt', ]); Event::dispatch('data_transfer.imports.update.before'); $data = array_merge( request()->only([ 'type', 'action', 'process_in_queue', 'validation_strategy', 'validation_strategy', 'allowed_errors', 'field_separator', ]), [ 'state' => 'pending', 'processed_rows_count' => 0, 'invalid_rows_count' => 0, 'errors_count' => 0, 'errors' => null, 'error_file_path' => null, 'started_at' => null, 'completed_at' => null, 'summary' => null, ] ); Storage::disk('public')->delete($import->error_file_path ?? ''); if (request()->file('file') && request()->file('file')->isValid()) { Storage::disk('public')->delete($import->file_path); $data['file_path'] = request()->file('file')->storeAs( 'imports', time().'-'.request()->file('file')->getClientOriginalName(), 'public' ); } if (! isset($data['process_in_queue'])) { $data['process_in_queue'] = false; } $import = $this->importRepository->update($data, $import->id); Event::dispatch('data_transfer.imports.update.after', $import); session()->flash('success', trans('admin::app.settings.data-transfer.imports.update-success')); return redirect()->route('admin.settings.data_transfer.imports.import', $import->id); } /** * Remove the specified resource from storage. */ public function destroy(int $id): JsonResponse { $import = $this->importRepository->findOrFail($id); try { Storage::disk('public')->delete($import->file_path); Storage::disk('public')->delete($import->error_file_path ?? ''); $this->importRepository->delete($id); return response()->json([ 'message' => trans('admin::app.settings.data-transfer.imports.delete-success'), ]); } catch (\Exception $e) { } return response()->json([ 'message' => trans('admin::app.settings.data-transfer.imports.delete-failed'), ], 500); } /** * Show the form for creating a new resource. */ public function import(int $id): View { $import = $this->importRepository->findOrFail($id); $isValid = $this->importHelper ->setImport($import) ->isValid(); if ($import->state == Import::STATE_LINKING) { if ($this->importHelper->isIndexingRequired()) { $state = Import::STATE_INDEXING; } else { $state = Import::STATE_COMPLETED; } } elseif ($import->state == Import::STATE_INDEXING) { $state = Import::STATE_COMPLETED; } else { $state = Import::STATE_COMPLETED; } $stats = $this->importHelper->stats($state); $import->unsetRelations(); return view('admin::settings.data-transfer.imports.import', compact('import', 'isValid', 'stats')); } /** * Store a newly created resource in storage. */ public function validateImport(int $id): JsonResponse { $import = $this->importRepository->findOrFail($id); $isValid = $this->importHelper ->setImport($import) ->validate(); return new JsonResponse([ 'is_valid' => $isValid, 'import' => $this->importHelper->getImport()->unsetRelations(), ]); } /** * Store a newly created resource in storage. */ public function start(int $id): JsonResponse { $import = $this->importRepository->findOrFail($id); if (! $import->processed_rows_count) { return new JsonResponse([ 'message' => trans('admin::app.settings.data-transfer.imports.nothing-to-import'), ], 400); } $this->importHelper->setImport($import); if (! $this->importHelper->isValid()) { return new JsonResponse([ 'message' => trans('admin::app.settings.data-transfer.imports.not-valid'), ], 400); } if ( $import->process_in_queue && config('queue.default') == 'sync' ) { return new JsonResponse([ 'message' => trans('admin::app.settings.data-transfer.imports.setup-queue-error'), ], 400); } /** * Set the import state to processing */ if ($import->state == Import::STATE_VALIDATED) { $this->importHelper->started(); } /** * Get the first pending batch to import */ $importBatch = $import->batches->where('state', Import::STATE_PENDING)->first(); if ($importBatch) { /** * Start the import process */ try { if ($import->process_in_queue) { $this->importHelper->start(); } else { $this->importHelper->start($importBatch); } } catch (\Exception $e) { return new JsonResponse([ 'message' => $e->getMessage(), ], 400); } } else { if ($this->importHelper->isLinkingRequired()) { $this->importHelper->linking(); } elseif ($this->importHelper->isIndexingRequired()) { $this->importHelper->indexing(); } else { $this->importHelper->completed(); } } return new JsonResponse([ 'stats' => $this->importHelper->stats(Import::STATE_PROCESSED), 'import' => $this->importHelper->getImport()->unsetRelations(), ]); } /** * Store a newly created resource in storage. */ public function link(int $id): JsonResponse { $import = $this->importRepository->findOrFail($id); if (! $import->processed_rows_count) { return new JsonResponse([ 'message' => trans('admin::app.settings.data-transfer.imports.nothing-to-import'), ], 400); } $this->importHelper->setImport($import); if (! $this->importHelper->isValid()) { return new JsonResponse([ 'message' => trans('admin::app.settings.data-transfer.imports.not-valid'), ], 400); } /** * Set the import state to linking */ if ($import->state == Import::STATE_PROCESSED) { $this->importHelper->linking(); } /** * Get the first processing batch to link */ $importBatch = $import->batches->where('state', Import::STATE_PROCESSED)->first(); /** * Set the import state to linking/completed */ if ($importBatch) { /** * Start the resource linking process */ try { $this->importHelper->link($importBatch); } catch (\Exception $e) { return new JsonResponse([ 'message' => $e->getMessage(), ], 400); } } else { if ($this->importHelper->isIndexingRequired()) { $this->importHelper->indexing(); } else { $this->importHelper->completed(); } } return new JsonResponse([ 'stats' => $this->importHelper->stats(Import::STATE_LINKED), 'import' => $this->importHelper->getImport()->unsetRelations(), ]); } /** * Store a newly created resource in storage. */ public function indexData(int $id): JsonResponse { $import = $this->importRepository->findOrFail($id); if (! $import->processed_rows_count) { return new JsonResponse([ 'message' => trans('admin::app.settings.data-transfer.imports.nothing-to-import'), ], 400); } $this->importHelper->setImport($import); if (! $this->importHelper->isValid()) { return new JsonResponse([ 'message' => trans('admin::app.settings.data-transfer.imports.not-valid'), ], 400); } /** * Set the import state to linking */ if ($import->state == Import::STATE_LINKED) { $this->importHelper->indexing(); } /** * Get the first processing batch to link */ $importBatch = $import->batches->where('state', Import::STATE_LINKED)->first(); /** * Set the import state to linking/completed */ if ($importBatch) { /** * Start the resource linking process */ try { $this->importHelper->index($importBatch); } catch (\Exception $e) { return new JsonResponse([ 'message' => $e->getMessage(), ], 400); } } else { /** * Set the import state to completed */ $this->importHelper->completed(); } return new JsonResponse([ 'stats' => $this->importHelper->stats(Import::STATE_INDEXED), 'import' => $this->importHelper->getImport()->unsetRelations(), ]); } /** * Returns import stats */ public function stats(int $id, string $state = Import::STATE_PROCESSED): JsonResponse { $import = $this->importRepository->findOrFail($id); $stats = $this->importHelper ->setImport($import) ->stats($state); return new JsonResponse([ 'stats' => $stats, 'import' => $this->importHelper->getImport()->unsetRelations(), ]); } /** * Download import error report */ public function downloadSample(string $type) { $importer = config('importers.'.$type); return Storage::download($importer['sample_path']); } /** * Download import error report */ public function download(int $id) { $import = $this->importRepository->findOrFail($id); return Storage::disk('public')->download($import->file_path); } /** * Download import error report */ public function downloadErrorReport(int $id) { $import = $this->importRepository->findOrFail($id); return Storage::disk('public')->download($import->file_path); } } ================================================ FILE: packages/Webkul/Admin/src/Http/Controllers/Settings/EmailTemplateController.php ================================================ ajax()) { return datagrid(EmailTemplateDataGrid::class)->process(); } return view('admin::settings.email-templates.index'); } /** * Show the form for creating a new resource. * * @return View */ public function create() { $placeholders = $this->workflowEntityHelper->getEmailTemplatePlaceholders(); return view('admin::settings.email-templates.create', compact('placeholders')); } /** * Store a newly created email templates in storage. */ public function store(): RedirectResponse { $this->validate(request(), [ 'name' => 'required|unique:email_templates,name', 'subject' => 'required', 'content' => 'required', ]); Event::dispatch('settings.email_templates.create.before'); $emailTemplate = $this->emailTemplateRepository->create(request()->all()); Event::dispatch('settings.email_templates.create.after', $emailTemplate); session()->flash('success', trans('admin::app.settings.email-template.index.create-success')); return redirect()->route('admin.settings.email_templates.index'); } /** * Show the form for editing the specified email template. */ public function edit(int $id): View { $emailTemplate = $this->emailTemplateRepository->findOrFail($id); $placeholders = $this->workflowEntityHelper->getEmailTemplatePlaceholders(); return view('admin::settings.email-templates.edit', compact('emailTemplate', 'placeholders')); } /** * Update the specified email template in storage. */ public function update(int $id): RedirectResponse { $this->validate(request(), [ 'name' => 'required|unique:email_templates,name,'.$id, 'subject' => 'required', 'content' => 'required', ]); Event::dispatch('settings.email_templates.update.before', $id); $emailTemplate = $this->emailTemplateRepository->update(request()->all(), $id); Event::dispatch('settings.email_templates.update.after', $emailTemplate); session()->flash('success', trans('admin::app.settings.email-template.index.update-success')); return redirect()->route('admin.settings.email_templates.index'); } /** * Remove the specified email template from storage. */ public function destroy(int $id): JsonResponse { $emailTemplate = $this->emailTemplateRepository->findOrFail($id); try { Event::dispatch('settings.email_templates.delete.before', $id); $emailTemplate->delete($id); Event::dispatch('settings.email_templates.delete.after', $id); return response()->json([ 'message' => trans('admin::app.settings.email-template.index.delete-success'), ], 200); } catch (\Exception $exception) { return response()->json([ 'message' => trans('admin::app.settings.email-template.index.delete-failed'), ], 400); } return response()->json([ 'message' => trans('admin::app.settings.email-template.index.delete-failed'), ], 400); } } ================================================ FILE: packages/Webkul/Admin/src/Http/Controllers/Settings/GroupController.php ================================================ ajax()) { return datagrid(GroupDataGrid::class)->process(); } return view('admin::settings.groups.index'); } /** * Store a newly created resource in storage. */ public function store(): JsonResponse { $this->validate(request(), [ 'name' => 'required|unique:groups,name|max:50', 'description' => 'required|max:250', ]); Event::dispatch('settings.group.create.before'); $group = $this->groupRepository->create(request()->only([ 'name', 'description', ])); Event::dispatch('settings.group.create.after', $group); return new JsonResponse([ 'data' => $group, 'message' => trans('admin::app.settings.groups.index.create-success'), ]); } /** * Show the form for editing the specified resource. */ public function edit(int $id): JsonResource { $group = $this->groupRepository->findOrFail($id); return new JsonResource([ 'data' => $group, ]); } /** * Update the specified resource in storage. */ public function update(int $id): JsonResponse { $this->validate(request(), [ 'name' => 'required|max:50|unique:groups,name,'.$id, 'description' => 'required|max:250', ]); Event::dispatch('settings.group.update.before', $id); $group = $this->groupRepository->update(request()->only([ 'name', 'description', ]), $id); Event::dispatch('settings.group.update.after', $group); return new JsonResponse([ 'data' => $group, 'message' => trans('admin::app.settings.groups.index.update-success'), ]); } /** * Remove the specified resource from storage. * * @return Response */ public function destroy(int $id): JsonResponse { $group = $this->groupRepository->findOrFail($id); if ($group->users()->exists()) { return response()->json([ 'message' => trans('admin::app.settings.groups.index.delete-failed-associated-users'), ], 400); } try { Event::dispatch('settings.group.delete.before', $id); $group->delete($id); Event::dispatch('settings.group.delete.after', $id); return new JsonResponse([ 'message' => trans('admin::app.settings.groups.index.destroy-success'), ], 200); } catch (\Exception $exception) { return new JsonResponse([ 'message' => trans('admin::app.settings.groups.index.delete-failed'), ], 400); } } } ================================================ FILE: packages/Webkul/Admin/src/Http/Controllers/Settings/LocationController.php ================================================ locationRepository ->pushCriteria(app(RequestCriteria::class)) ->all(); return response()->json([ 'data' => $results, ]); } /** * Store a newly created resource in storage. */ public function store(AttributeForm $request): JsonResponse { Event::dispatch('settings.location.create.before'); $location = $this->locationRepository->create(request()->all()); Event::dispatch('settings.location.create.after', $location); return new JsonResponse([ 'data' => $location, 'message' => trans('admin::app.settings.warehouses.view.locations.create-success'), ]); } /** * Remove the specified resource from storage. * * @return Response */ public function destroy(int $id): JsonResponse { $this->locationRepository->findOrFail($id); try { Event::dispatch('settings.location.delete.before', $id); $this->locationRepository->delete($id); Event::dispatch('settings.location.delete.after', $id); return new JsonResponse([ 'message' => trans('admin::app.settings.warehouses.view.locations.delete-success'), ], 200); } catch (\Exception $exception) { return new JsonResponse([ 'message' => trans('admin::app.settings.warehouses.view.locations.delete-failed'), ], 400); } } } ================================================ FILE: packages/Webkul/Admin/src/Http/Controllers/Settings/Marketing/CampaignsController.php ================================================ isXmlHttpRequest()) { return datagrid(CampaignDatagrid::class)->process(); } return view('admin::settings.marketing.campaigns.index'); } /** * Get marketing events. */ public function getEvents(): JsonResponse { $events = $this->eventRepository->get(['id', 'name']); return response()->json([ 'data' => $events, ]); } /** * Get Email Templates. */ public function getEmailTemplates(): JsonResponse { $emailTemplates = $this->emailTemplateRepository->get(['id', 'name']); return response()->json([ 'data' => $emailTemplates, ]); } /** * Store a newly created marketing campaign in storage. */ public function store(): JsonResponse { $validatedData = $this->validate(request(), [ 'name' => 'required|string|max:255', 'subject' => 'required|string|max:255', 'marketing_template_id' => 'required|exists:email_templates,id', 'marketing_event_id' => 'required|exists:marketing_events,id', 'status' => 'sometimes|required|in:0,1', ]); Event::dispatch('settings.marketing.campaigns.create.before'); $marketingCampaign = $this->campaignRepository->create($validatedData); Event::dispatch('settings.marketing.campaigns.create.after', $marketingCampaign); return response()->json([ 'message' => trans('admin::app.settings.marketing.campaigns.index.create-success'), ]); } /** * Show the specified Resource. */ public function show(int $id): JsonResponse { $campaign = $this->campaignRepository->findOrFail($id); return response()->json([ 'data' => $campaign, ]); } /** * Update the specified marketing campaign in storage. */ public function update(int $id): JsonResponse { $validatedData = $this->validate(request(), [ 'name' => 'required|string|max:255', 'subject' => 'required|string|max:255', 'marketing_template_id' => 'required|exists:email_templates,id', 'marketing_event_id' => 'required|exists:marketing_events,id', 'status' => 'sometimes|required|in:0,1', ]); Event::dispatch('settings.marketing.campaigns.update.before', $id); $marketingCampaign = $this->campaignRepository->update($validatedData, $id); Event::dispatch('settings.marketing.campaigns.update.after', $marketingCampaign); return response()->json([ 'message' => trans('admin::app.settings.marketing.campaigns.index.update-success'), ]); } /** * Remove the specified marketing campaign from storage. */ public function destroy(int $id): JsonResponse { Event::dispatch('settings.marketing.campaigns.delete.before', $id); $this->campaignRepository->delete($id); Event::dispatch('settings.marketing.campaigns.delete.after', $id); return response()->json([ 'message' => trans('admin::app.settings.marketing.campaigns.index.delete-success'), ]); } /** * Remove the specified marketing campaigns from storage. */ public function massDestroy(MassDestroyRequest $massDestroyRequest): JsonResponse { $campaigns = $this->campaignRepository->findWhereIn('id', $massDestroyRequest->input('indices')); foreach ($campaigns as $campaign) { Event::dispatch('settings.marketing.campaigns.delete.before', $campaign); $this->campaignRepository->delete($campaign->id); Event::dispatch('settings.marketing.campaigns.delete.after', $campaign); } return response()->json([ 'message' => trans('admin::app.settings.marketing.campaigns.index.mass-delete-success'), ]); } } ================================================ FILE: packages/Webkul/Admin/src/Http/Controllers/Settings/Marketing/EventController.php ================================================ ajax()) { return datagrid(EventDataGrid::class)->process(); } return view('admin::settings.marketing.events.index'); } /** * Store a newly created marketing event in storage. */ public function store(): JsonResponse { $validatedData = $this->validate(request(), [ 'name' => 'required|max:60', 'description' => 'required', 'date' => 'required|date|after_or_equal:today', ]); Event::dispatch('settings.marketing.events.create.before'); $marketingEvent = $this->eventRepository->create($validatedData); Event::dispatch('settings.marketing.events.create.after', $marketingEvent); return response()->json([ 'message' => trans('admin::app.settings.marketing.events.index.create-success'), 'data' => $marketingEvent, ]); } /** * Update the specified marketing event in storage. */ public function update(int $id): JsonResponse { $validatedData = $this->validate(request(), [ 'name' => 'required|max:60', 'description' => 'required', 'date' => 'required|date|after_or_equal:today', ]); Event::dispatch('settings.marketing.events.update.before', $id); $marketingEvent = $this->eventRepository->update($validatedData, $id); Event::dispatch('settings.marketing.events.update.after', $marketingEvent); return response()->json([ 'message' => trans('admin::app.settings.marketing.events.index.update-success'), 'data' => $marketingEvent, ]); } /** * Remove the specified marketing event from storage. */ public function destroy(int $id): JsonResponse { $event = $this->eventRepository->findOrFail($id); if ($event->campaigns->isNotEmpty()) { return response()->json([ 'message' => trans('admin::app.settings.marketing.events.index.delete-failed-associated-campaigns'), ], 422); } Event::dispatch('settings.marketing.events.delete.before', $event); $this->eventRepository->delete($event->id); Event::dispatch('settings.marketing.events.delete.after', $event); return response()->json([ 'message' => trans('admin::app.settings.marketing.events.index.delete-success'), ]); } /** * Remove the specified marketing events from storage. */ public function massDestroy(MassDestroyRequest $request): JsonResponse { try { $events = $this->eventRepository->findWhereIn('id', $request->input('indices', [])); $deletedCount = 0; $blockedCount = 0; foreach ($events as $event) { if ( $event->campaigns && $event->campaigns->isNotEmpty() ) { $blockedCount++; continue; } Event::dispatch('settings.marketing.events.delete.before', $event); $this->eventRepository->delete($event->id); Event::dispatch('settings.marketing.events.delete.after', $event); $deletedCount++; } $statusCode = 200; switch (true) { case $deletedCount > 0 && $blockedCount === 0: $message = trans('admin::app.settings.marketing.events.index.mass-delete-success'); break; case $deletedCount > 0 && $blockedCount > 0: $message = trans('admin::app.settings.marketing.events.index.partial-delete-warning'); break; case $deletedCount === 0 && $blockedCount > 0: $message = trans('admin::app.settings.marketing.events.index.none-delete-warning'); $statusCode = 400; break; default: $message = trans('admin::app.settings.marketing.events.index.no-selection'); $statusCode = 400; break; } return response()->json(['message' => $message], $statusCode); } catch (Exception $e) { return response()->json([ 'message' => trans('admin::app.settings.marketing.events.index.mass-delete-failed'), ], 400); } } } ================================================ FILE: packages/Webkul/Admin/src/Http/Controllers/Settings/PipelineController.php ================================================ ajax()) { return datagrid(PipelineDataGrid::class)->process(); } return view('admin::settings.pipelines.index'); } /** * Show the form for creating a new resource. */ public function create(): View { return view('admin::settings.pipelines.create'); } /** * Store a newly created resource in storage. */ public function store(PipelineForm $request): RedirectResponse { $request->validated(); $request->merge([ 'is_default' => request()->has('is_default') ? 1 : 0, ]); Event::dispatch('settings.pipeline.create.before'); $pipeline = $this->pipelineRepository->create($request->all()); Event::dispatch('settings.pipeline.create.after', $pipeline); session()->flash('success', trans('admin::app.settings.pipelines.index.create-success')); return redirect()->route('admin.settings.pipelines.index'); } /** * Show the form for editing the specified resource. */ public function edit(int $id): View { $pipeline = $this->pipelineRepository->findOrFail($id); return view('admin::settings.pipelines.edit', compact('pipeline')); } /** * Update the specified resource in storage. */ public function update(PipelineForm $request, int $id): RedirectResponse { $request->validated(); $isDefault = request()->has('is_default') ? 1 : 0; if (! $isDefault) { $defaultCount = $this->pipelineRepository->findWhere(['is_default' => 1])->count(); $pipeline = $this->pipelineRepository->find($id); if ( $defaultCount === 1 && $pipeline->is_default ) { session()->flash('error', trans('admin::app.settings.pipelines.index.default-required')); return redirect()->back(); } } $request->merge(['is_default' => $isDefault]); Event::dispatch('settings.pipeline.update.before', $id); $pipeline = $this->pipelineRepository->update($request->all(), $id); Event::dispatch('settings.pipeline.update.after', $pipeline); session()->flash('success', trans('admin::app.settings.pipelines.index.update-success')); return redirect()->route('admin.settings.pipelines.index'); } /** * Remove the specified resource from storage. */ public function destroy($id): JsonResponse { $pipeline = $this->pipelineRepository->findOrFail($id); if ($pipeline->is_default) { return response()->json([ 'message' => trans('admin::app.settings.pipelines.index.default-delete-error'), ], 400); } else { $defaultPipeline = $this->pipelineRepository->getDefaultPipeline(); $pipeline->leads()->update([ 'lead_pipeline_id' => $defaultPipeline->id, 'lead_pipeline_stage_id' => $defaultPipeline->stages()->first()->id, ]); } try { Event::dispatch('settings.pipeline.delete.before', $id); $this->pipelineRepository->delete($id); Event::dispatch('settings.pipeline.delete.after', $id); return response()->json([ 'message' => trans('admin::app.settings.pipelines.index.delete-success'), ], 200); } catch (\Exception $exception) { return response()->json([ 'message' => trans('admin::app.settings.pipelines.index.delete-failed'), ], 400); } return response()->json([ 'message' => trans('admin::app.settings.pipelines.index.delete-failed'), ], 400); } } ================================================ FILE: packages/Webkul/Admin/src/Http/Controllers/Settings/RoleController.php ================================================ ajax()) { return datagrid(RoleDataGrid::class)->process(); } return view('admin::settings.roles.index'); } /** * Show the form for creating a new resource. */ public function create(): View { return view('admin::settings.roles.create'); } /** * Store a newly created resource in storage. */ public function store(): RedirectResponse { $this->validate(request(), [ 'name' => 'required', 'permission_type' => 'required|in:all,custom', 'description' => 'required', ]); if (request('permission_type') == 'custom') { $this->validate(request(), [ 'permissions' => 'required', ]); } Event::dispatch('settings.role.create.before'); $data = request()->only([ 'name', 'description', 'permission_type', 'permissions', ]); $role = $this->roleRepository->create($data); Event::dispatch('settings.role.create.after', $role); session()->flash('success', trans('admin::app.settings.roles.index.create-success')); return redirect()->route('admin.settings.roles.index'); } /** * Show the form for editing the specified resource. */ public function edit(int $id): View { $role = $this->roleRepository->findOrFail($id); return view('admin::settings.roles.edit', compact('role')); } /** * Update the specified resource in storage. */ public function update(int $id): RedirectResponse { $this->validate(request(), [ 'name' => 'required', 'permission_type' => 'required|in:all,custom', 'description' => 'required', 'permissions' => 'required_if:permission_type,custom', ]); Event::dispatch('settings.role.update.before', $id); $data = array_merge(request()->only([ 'name', 'description', 'permission_type', ]), [ 'permissions' => request()->has('permissions') ? request('permissions') : [], ]); $role = $this->roleRepository->update($data, $id); Event::dispatch('settings.role.update.after', $role); session()->flash('success', trans('admin::app.settings.roles.index.update-success')); return redirect()->back(); } /** * Remove the specified resource from storage. */ public function destroy(int $id): JsonResponse { $response = [ 'responseCode' => 400, ]; $role = $this->roleRepository->findOrFail($id); if ($role->users && $role->users->count() >= 1) { $response['message'] = trans('admin::app.settings.roles.index.being-used'); session()->flash('error', $response['message']); } elseif ($this->roleRepository->count() == 1) { $response['message'] = trans('admin::app.settings.roles.index.last-delete-error'); session()->flash('error', $response['message']); } else { try { Event::dispatch('settings.role.delete.before', $id); if (auth()->guard('user')->user()->role_id == $id) { $response['message'] = trans('admin::app.settings.roles.index.current-role-delete-error'); } else { $this->roleRepository->delete($id); Event::dispatch('settings.role.delete.after', $id); $message = trans('admin::app.settings.roles.index.delete-success'); $response = [ 'responseCode' => 200, 'message' => $message, ]; session()->flash('success', $message); } } catch (\Exception $exception) { $message = $exception->getMessage(); $response['message'] = $message; session()->flash('error', $message); } } return response()->json($response, $response['responseCode']); } } ================================================ FILE: packages/Webkul/Admin/src/Http/Controllers/Settings/SettingController.php ================================================ query('query')); if (empty($query)) { return response()->json(['data' => []]); } $results = $this->searchMenuItems($this->getSettingsConfig(), $query); return response()->json([ 'data' => $results->values(), ]); } /** * Recursively search through menu items and children. * * @param Collection $menuItems * @return Collection> */ protected function searchMenuItems(Collection $menuItems, string $query): Collection { $results = collect(); foreach ($menuItems as $item) { if ($this->matchesQuery($item, $query)) { $results->push([ 'name' => $item->getName(), 'url' => $item->getUrl(), 'icon' => $item->getIcon(), 'key' => $item->getKey(), ]); } if ($item->haveChildren()) { $childResults = $this->searchMenuItems($item->getChildren(), $query); $results = $results->merge($childResults); } } return $results; } /** * Determine if the menu item matches the query. */ protected function matchesQuery(MenuItem $item, string $query): bool { $query = strtolower($query); $url = strtolower($item->getUrl()); if ( ! $url || ! str_contains($url, $query) ) { return false; } return true; } /** * Get the settings configuration. */ protected function getSettingsConfig(): Collection { return menu() ->getItems('admin') ->filter(fn (MenuItem $item) => $item->getKey() === 'settings'); } } ================================================ FILE: packages/Webkul/Admin/src/Http/Controllers/Settings/SourceController.php ================================================ ajax()) { return datagrid(SourceDataGrid::class)->process(); } return view('admin::settings.sources.index'); } /** * Store a newly created resource in storage. */ public function store(): JsonResponse { $this->validate(request(), [ 'name' => ['required', 'unique:lead_sources,name'], ]); Event::dispatch('settings.source.create.before'); $source = $this->sourceRepository->create(request()->only(['name'])); Event::dispatch('settings.source.create.after', $source); return new JsonResponse([ 'data' => $source, 'message' => trans('admin::app.settings.sources.index.create-success'), ]); } /** * Show the form for editing the specified resource. */ public function edit(int $id): View|JsonResponse { $source = $this->sourceRepository->findOrFail($id); return new JsonResponse([ 'data' => $source, ]); } /** * Update the specified resource in storage. */ public function update(int $id): JsonResponse { $this->validate(request(), [ 'name' => 'required|unique:lead_sources,name,'.$id, ]); Event::dispatch('settings.source.update.before', $id); $source = $this->sourceRepository->update(request()->only(['name']), $id); Event::dispatch('settings.source.update.after', $source); return new JsonResponse([ 'data' => $source, 'message' => trans('admin::app.settings.sources.index.update-success'), ]); } /** * Remove the specified resource from storage. */ public function destroy(int $id): JsonResponse { $source = $this->sourceRepository->findOrFail($id); if ($source->leads()->count() > 0) { return new JsonResponse([ 'message' => trans('admin::app.settings.sources.index.delete-failed-associated-leads'), ], 400); } try { Event::dispatch('settings.source.delete.before', $id); $source->delete(); Event::dispatch('settings.source.delete.after', $id); return new JsonResponse([ 'message' => trans('admin::app.settings.sources.index.delete-success'), ], 200); } catch (Exception $exception) { return new JsonResponse([ 'message' => trans('admin::app.settings.sources.index.delete-failed'), ], 400); } } } ================================================ FILE: packages/Webkul/Admin/src/Http/Controllers/Settings/TagController.php ================================================ ajax()) { return datagrid(TagDataGrid::class)->process(); } return view('admin::settings.tags.index'); } /** * Store a newly created resource in storage. */ public function store(): JsonResponse { $this->validate(request(), [ 'name' => ['required', 'unique:tags,name', 'max:50'], ]); Event::dispatch('settings.tag.create.before'); $tag = $this->tagRepository->create(array_merge(request()->only([ 'name', 'color', ]), [ 'user_id' => auth()->guard('user')->user()->id, ])); Event::dispatch('settings.tag.create.after', $tag); return new JsonResponse([ 'data' => new TagResource($tag), 'message' => trans('admin::app.settings.tags.index.create-success'), ]); } /** * Show the form for editing the specified tag. */ public function edit(int $id): View|JsonResponse { $tag = $this->tagRepository->findOrFail($id); return new JsonResponse([ 'data' => $tag, ]); } /** * Update the specified tag in storage. */ public function update(int $id): JsonResponse { $this->validate(request(), [ 'name' => 'required|max:50|unique:tags,name,'.$id, ]); Event::dispatch('settings.tag.update.before', $id); $tag = $this->tagRepository->update(request()->only([ 'name', 'color', ]), $id); Event::dispatch('settings.tag.update.after', $tag); return new JsonResponse([ 'data' => new TagResource($tag), 'message' => trans('admin::app.settings.tags.index.update-success'), ]); } /** * Remove the specified type from storage. */ public function destroy(int $id): JsonResponse { $tag = $this->tagRepository->findOrFail($id); try { Event::dispatch('settings.tag.delete.before', $id); $tag->delete($id); Event::dispatch('settings.tag.delete.after', $id); return new JsonResponse([ 'message' => trans('admin::app.settings.tags.index.delete-success'), ], 200); } catch (\Exception $exception) { return new JsonResponse([ 'message' => trans('admin::app.settings.tags.index.delete-failed'), ], 400); } } /** * Search tag results * * @return Response */ public function search() { $tags = $this->tagRepository ->pushCriteria(app(RequestCriteria::class)) ->all(); return TagResource::collection($tags); } /** * Mass Delete the specified resources. */ public function massDestroy(MassDestroyRequest $massDestroyRequest): JsonResponse { $indices = $massDestroyRequest->input('indices'); try { foreach ($indices as $index) { Event::dispatch('settings.tag.delete.before', $index); $this->tagRepository->delete($index); Event::dispatch('settings.tag.delete.after', $index); } return new JsonResponse([ 'message' => trans('admin::app.customers.reviews.index.datagrid.mass-delete-success'), ], 200); } catch (\Exception $e) { return new JsonResponse([ 'message' => $e->getMessage(), ], 500); } } } ================================================ FILE: packages/Webkul/Admin/src/Http/Controllers/Settings/TypeController.php ================================================ ajax()) { return datagrid(TypeDataGrid::class)->process(); } return view('admin::settings.types.index'); } /** * Store a newly created type in storage. */ public function store(): JsonResponse { $this->validate(request(), [ 'name' => ['required', 'unique:lead_types,name'], ]); Event::dispatch('settings.type.create.before'); $type = $this->typeRepository->create(request()->only(['name'])); Event::dispatch('settings.type.create.after', $type); return new JsonResponse([ 'data' => $type, 'message' => trans('admin::app.settings.types.index.create-success'), ]); } /** * Show the form for editing the specified type. */ public function edit(int $id): View|JsonResponse { $type = $this->typeRepository->findOrFail($id); return new JsonResponse([ 'data' => $type, ]); } /** * Update the specified type in storage. */ public function update(int $id): JsonResponse { $this->validate(request(), [ 'name' => 'required|unique:lead_types,name,'.$id, ]); Event::dispatch('settings.type.update.before', $id); $type = $this->typeRepository->update(request()->only(['name']), $id); Event::dispatch('settings.type.update.after', $type); return new JsonResponse([ 'data' => $type, 'message' => trans('admin::app.settings.types.index.update-success'), ]); } /** * Remove the specified type from storage. */ public function destroy(int $id): JsonResponse { $type = $this->typeRepository->findOrFail($id); try { Event::dispatch('settings.type.delete.before', $id); $type->delete($id); Event::dispatch('settings.type.delete.after', $id); return new JsonResponse([ 'message' => trans('admin::app.settings.types.index.delete-success'), ], 200); } catch (\Exception $exception) { return new JsonResponse([ 'message' => trans('admin::app.settings.types.index.delete-failed'), ], 400); } } } ================================================ FILE: packages/Webkul/Admin/src/Http/Controllers/Settings/UserController.php ================================================ ajax()) { return datagrid(UserDataGrid::class)->process(); } $roles = $this->roleRepository->all(); $groups = $this->groupRepository->all(); return view('admin::settings.users.index', compact('roles', 'groups')); } /** * Store a newly created resource in storage. */ public function store(): View|JsonResponse { $this->validate(request(), [ 'email' => 'required|email|unique:users,email', 'name' => 'required', 'password' => 'nullable', 'confirm_password' => 'nullable|required_with:password|same:password', 'role_id' => 'required', 'status' => 'boolean|in:0,1', 'view_permission' => 'string|in:global,group,individual', ]); $data = request()->all(); if ( isset($data['password']) && $data['password'] ) { $data['password'] = bcrypt($data['password']); } Event::dispatch('settings.user.create.before'); $admin = $this->userRepository->create($data); $admin->groups()->sync($data['groups'] ?? []); try { Mail::queue(new UserCreatedNotification($admin)); } catch (\Exception $e) { report($e); } Event::dispatch('settings.user.create.after', $admin); return new JsonResponse([ 'data' => $admin, 'message' => trans('admin::app.settings.users.index.create-success'), ]); } /** * Show the form for editing the specified resource. */ public function edit(int $id): View|JsonResponse { $admin = $this->userRepository->with(['role', 'groups'])->findOrFail($id); return new JsonResponse([ 'data' => $admin, ]); } /** * Update the specified resource in storage. */ public function update(int $id): JsonResponse { $this->validate(request(), [ 'email' => 'required|email|unique:users,email,'.$id, 'name' => 'required|string', 'password' => 'nullable|string|min:6', 'confirm_password' => 'nullable|required_with:password|same:password', 'role_id' => 'required|integer|exists:roles,id', 'status' => 'nullable|boolean|in:0,1', 'view_permission' => 'required|string|in:global,group,individual', ]); $data = request()->all(); if (empty($data['password'])) { $data = Arr::except($data, ['password', 'confirm_password']); } else { $data['password'] = bcrypt($data['password']); } $authUser = auth()->guard('user')->user(); if ($authUser->id == $id) { $data['status'] = true; } Event::dispatch('settings.user.update.before', $id); $admin = $this->userRepository->update($data, $id); $admin->groups()->sync($data['groups'] ?? []); Event::dispatch('settings.user.update.after', $admin); return new JsonResponse([ 'data' => $admin, 'message' => trans('admin::app.settings.users.index.update-success'), ]); } /** * Search user results. */ public function search(): JsonResource { $users = $this->userRepository ->pushCriteria(app(RequestCriteria::class)) ->all(); return UserResource::collection($users); } /** * Destroy specified user. */ public function destroy(int $id): JsonResponse { if ($this->userRepository->count() == 1) { return new JsonResponse([ 'message' => trans('admin::app.settings.users.index.last-delete-error'), ], 400); } try { Event::dispatch('user.admin.delete.before', $id); $this->userRepository->delete($id); Event::dispatch('user.admin.delete.after', $id); return new JsonResponse([ 'message' => trans('admin::app.settings.users.index.delete-success'), ], 200); } catch (\Exception $e) { } return new JsonResponse([ 'message' => trans('admin::app.settings.users.index.delete-failed'), ], 500); } /** * Mass Update the specified resources. */ public function massUpdate(MassUpdateRequest $massDestroyRequest): JsonResponse { $count = 0; $users = $this->userRepository->findWhereIn('id', $massDestroyRequest->input('indices')); foreach ($users as $users) { if (auth()->guard('user')->user()->id == $users->id) { continue; } Event::dispatch('settings.user.update.before', $users->id); $this->userRepository->update([ 'status' => $massDestroyRequest->input('value'), ], $users->id); Event::dispatch('settings.user.update.after', $users->id); $count++; } if (! $count) { return response()->json([ 'message' => trans('admin::app.settings.users.index.mass-update-failed'), ], 400); } return response()->json([ 'message' => trans('admin::app.settings.users.index.mass-update-success'), ]); } /** * Mass Delete the specified resources. */ public function massDestroy(MassDestroyRequest $massDestroyRequest): JsonResponse { $count = 0; $users = $this->userRepository->findWhereIn('id', $massDestroyRequest->input('indices')); foreach ($users as $user) { if (auth()->guard('user')->user()->id == $user->id) { continue; } Event::dispatch('settings.user.delete.before', $user->id); $this->userRepository->delete($user->id); Event::dispatch('settings.user.delete.after', $user->id); $count++; } if (! $count) { return response()->json([ 'message' => trans('admin::app.settings.users.index.mass-delete-failed'), ], 400); } return response()->json([ 'message' => trans('admin::app.settings.users.index.mass-delete-success'), ]); } } ================================================ FILE: packages/Webkul/Admin/src/Http/Controllers/Settings/Warehouse/ActivityController.php ================================================ activityRepository ->leftJoin('warehouse_activities', 'activities.id', '=', 'warehouse_activities.activity_id') ->where('warehouse_activities.warehouse_id', $id) ->get(); return ActivityResource::collection($this->concatEmail($activities)); } /** * Store a newly created resource in storage. */ public function concatEmail($activities) { return $activities->sortByDesc('id')->sortByDesc('created_at'); } } ================================================ FILE: packages/Webkul/Admin/src/Http/Controllers/Settings/Warehouse/TagController.php ================================================ warehouseRepository->find($id); if (! $warehouse->tags->contains(request()->input('tag_id'))) { $warehouse->tags()->attach(request()->input('tag_id')); } Event::dispatch('warehouse.tag.create.after', $warehouse); return response()->json([ 'message' => trans('admin::app.warehouse.view.tags.create-success'), ]); } /** * Remove the specified resource from storage. * * @param int $warehouseId * @return Response */ public function detach($warehouseId) { Event::dispatch('warehouse.tag.delete.before', $warehouseId); $warehouse = $this->warehouseRepository->find($warehouseId); $warehouse->tags()->detach(request()->input('tag_id')); Event::dispatch('warehouse.tag.delete.after', $warehouse); return response()->json([ 'message' => trans('admin::app.leads.view.tags.destroy-success'), ]); } } ================================================ FILE: packages/Webkul/Admin/src/Http/Controllers/Settings/Warehouse/WarehouseController.php ================================================ request->add(['entity_type' => 'warehouses']); } /** * Display a listing of the resource. */ public function index(): View|JsonResponse { if (request()->ajax()) { return datagrid(WarehouseDataGrid::class)->process(); } return view('admin::settings.warehouses.index'); } /** * Search location results */ public function search(): JsonResponse { $results = $this->warehouseRepository ->pushCriteria(app(RequestCriteria::class)) ->all(); return response()->json($results); } /** * Display a listing of the product resource. */ public function products(int $id) { if (request()->ajax()) { return datagrid(ProductDataGrid::class)->process(); } $warehouse = $this->warehouseRepository->findOrFail($id); return view('admin::settings.warehouses.products', compact('warehouse')); } /** * Show the form for creating a new resource. */ public function create(): View { return view('admin::settings.warehouses.create'); } /** * Store a newly created resource in storage. */ public function store(AttributeForm $request): RedirectResponse { Event::dispatch('settings.warehouse.create.before'); $warehouse = $this->warehouseRepository->create($request->all()); Event::dispatch('settings.warehouse.create.after', $warehouse); session()->flash('success', trans('admin::app.settings.warehouses.index.create-success')); return redirect()->route('admin.settings.warehouses.index'); } /** * Show the form for viewing the specified resource. */ public function view(int $id): View { $warehouse = $this->warehouseRepository->findOrFail($id); return view('admin::settings.warehouses.view', compact('warehouse')); } /** * Show the form for editing the specified resource. * * @param int $id * @return View */ public function edit($id) { $warehouse = $this->warehouseRepository->findOrFail($id); return view('admin::settings.warehouses.edit', compact('warehouse')); } /** * Update the specified resource in storage. */ public function update(AttributeForm $request, int $id): RedirectResponse|JsonResponse { Event::dispatch('settings.warehouse.update.before', $id); $warehouse = $this->warehouseRepository->update($request->all(), $id); Event::dispatch('settings.warehouse.update.after', $warehouse); if (request()->ajax()) { return response()->json([ 'data' => $warehouse, 'message' => trans('admin::app.settings.warehouses.index.update-success'), ]); } session()->flash('success', trans('admin::app.settings.warehouses.index.update-success')); return redirect()->route('admin.settings.warehouses.index'); } /** * Remove the specified resource from storage. */ public function destroy(int $id): JsonResponse { $this->warehouseRepository->findOrFail($id); try { Event::dispatch('settings.warehouse.delete.before', $id); $this->warehouseRepository->delete($id); Event::dispatch('settings.warehouse.delete.after', $id); return response()->json([ 'message' => trans('admin::app.settings.warehouses.index.delete-success'), ], 200); } catch (\Exception $exception) { return response()->json([ 'message' => trans('admin::app.settings.warehouses.index.delete-success'), ], 400); } } } ================================================ FILE: packages/Webkul/Admin/src/Http/Controllers/Settings/WebFormController.php ================================================ ajax()) { return datagrid(WebFormDataGrid::class)->process(); } return view('admin::settings.web-forms.index'); } /** * Show the form for creating a new resource. */ public function create(): View { $tempAttributes = $this->attributeRepository->findWhereIn('entity_type', ['persons', 'leads']); $attributes = []; foreach ($tempAttributes as $attribute) { if ( $attribute->entity_type == 'persons' && in_array($attribute->code, ['name', 'emails', 'contact_numbers']) ) { $attributes['default'][] = $attribute; } else { $attributes['other'][] = $attribute; } } return view('admin::settings.web-forms.create', compact('attributes')); } /** * Store a newly created email templates in storage. */ public function store(): RedirectResponse { $this->validate(request(), [ 'title' => 'required', 'submit_button_label' => 'required', 'submit_success_action' => 'required', 'submit_success_content' => 'required', ]); Event::dispatch('settings.web_forms.create.before'); $data = request()->all(); $webForm = $this->webFormRepository->create($data); Event::dispatch('settings.web_forms.create.after', $webForm); session()->flash('success', trans('admin::app.settings.webforms.index.create-success')); return redirect()->route('admin.settings.web_forms.index'); } /** * Show the form for editing the specified email template. */ public function edit(int $id): View { $webForm = $this->webFormRepository->findOrFail($id); $attributes = $this->attributeRepository->findWhere([ ['entity_type', 'IN', ['persons', 'leads']], ['id', 'NOTIN', $webForm->attributes()->pluck('attribute_id')->toArray()], ]); return view('admin::settings.web-forms.edit', compact('webForm', 'attributes')); } /** * Update the specified email template in storage. */ public function update(int $id): RedirectResponse { $this->validate(request(), [ 'title' => 'required', 'submit_button_label' => 'required', 'submit_success_action' => 'required', 'submit_success_content' => 'required', ]); Event::dispatch('settings.web_forms.update.before', $id); $data = request()->all(); $webForm = $this->webFormRepository->update($data, $id); Event::dispatch('settings.web_forms.update.after', $webForm); session()->flash('success', trans('admin::app.settings.webforms.index.update-success')); return redirect()->route('admin.settings.web_forms.index'); } /** * Remove the specified email template from storage. */ public function destroy(int $id): JsonResponse { $webForm = $this->webFormRepository->findOrFail($id); try { Event::dispatch('settings.web_forms.delete.before', $id); $webForm->delete($id); Event::dispatch('settings.web_forms.delete.after', $id); return response()->json([ 'message' => trans('admin::app.settings.webforms.index.delete-success'), ]); } catch (\Exception $exception) { return response()->json([ 'message' => trans('admin::app.settings.webforms.index.delete-failed'), ], 400); } } } ================================================ FILE: packages/Webkul/Admin/src/Http/Controllers/Settings/WebhookController.php ================================================ ajax()) { return datagrid(WebhookDataGrid::class)->process(); } return view('admin::settings.webhook.index'); } /** * Show the form for creating a new resource. */ public function create(): View { return view('admin::settings.webhook.create'); } /** * Store the newly created resource in storage. */ public function store(WebhookRequest $webhookRequest): RedirectResponse { Event::dispatch('settings.webhook.create.before'); $webhook = $this->webhookRepository->create($webhookRequest->validated()); Event::dispatch('settings.webhook.create.after', $webhook); session()->flash('success', trans('admin::app.settings.webhooks.index.create-success')); return redirect()->route('admin.settings.webhooks.index'); } /** * Store the newly created resource in storage. */ public function edit(int $id): View { $webhook = $this->webhookRepository->findOrFail($id); return view('admin::settings.webhook.edit', compact('webhook')); } /** * Update the specified resource in storage. */ public function update(WebhookRequest $webhookRequest, int $id): RedirectResponse { Event::dispatch('settings.webhook.update.before', $id); $webhook = $this->webhookRepository->update($webhookRequest->validated(), $id); Event::dispatch('settings.webhook.update.after', $webhook); session()->flash('success', trans('admin::app.settings.webhooks.index.update-success')); return redirect()->route('admin.settings.webhooks.index'); } /** * Remove the specified resource from storage. */ public function destroy(int $id): JsonResponse { $webhook = $this->webhookRepository->findOrFail($id); Event::dispatch('settings.webhook.delete.before', $id); $webhook?->delete(); Event::dispatch('settings.webhook.delete.after', $id); return response()->json([ 'message' => trans('admin::app.settings.webhooks.index.delete-success'), ]); } } ================================================ FILE: packages/Webkul/Admin/src/Http/Controllers/Settings/WorkflowController.php ================================================ ajax()) { return datagrid(WorkflowDataGrid::class)->process(); } return view('admin::settings.workflows.index'); } /** * Show the form for creating a new resource. */ public function create(): View { return view('admin::settings.workflows.create'); } /** * Store a newly created workflow in storage. */ public function store(): RedirectResponse { $this->validate(request(), [ 'name' => 'required', ]); Event::dispatch('settings.workflow.create.before'); $workflow = $this->workflowRepository->create(request()->all()); Event::dispatch('settings.workflow.create.after', $workflow); session()->flash('success', trans('admin::app.settings.workflows.index.create-success')); return redirect()->route('admin.settings.workflows.index'); } /** * Show the form for editing the specified workflow. */ public function edit(int $id): View { $workflow = $this->workflowRepository->findOrFail($id); return view('admin::settings.workflows.edit', compact('workflow')); } /** * Update the specified workflow in storage. */ public function update(int $id): RedirectResponse { $this->validate(request(), [ 'name' => 'required', ]); Event::dispatch('settings.workflow.update.before', $id); $workflow = $this->workflowRepository->update(request()->all(), $id); Event::dispatch('settings.workflow.update.after', $workflow); session()->flash('success', trans('admin::app.settings.workflows.index.update-success')); return redirect()->route('admin.settings.workflows.index'); } /** * Remove the specified workflow from storage. */ public function destroy(int $id): JsonResponse { $workflow = $this->workflowRepository->findOrFail($id); try { Event::dispatch('settings.workflow.delete.before', $id); $workflow->delete($id); Event::dispatch('settings.workflow.delete.after', $id); return response()->json([ 'message' => trans('admin::app.settings.workflows.index.delete-success'), ], 200); } catch (\Exception $exception) { return response()->json([ 'message' => trans('admin::app.settings.workflows.index.delete-failed'), ], 400); } return response()->json([ 'message' => trans('admin::app.settings.workflows.index.delete-failed'), ], 400); } } ================================================ FILE: packages/Webkul/Admin/src/Http/Controllers/TinyMCEController.php ================================================ storeMedia(); if (! empty($media)) { return response()->json([ 'location' => $media['file_url'], ]); } return response()->json([]); } /** * Store media. */ public function storeMedia(): array { if (! request()->hasFile('file')) { return []; } $file = request()->file('file'); if (! $file instanceof UploadedFile) { return []; } $filename = md5($file->getClientOriginalName().time()).'.'.$file->getClientOriginalExtension(); $path = $file->storeAs($this->storagePath, $filename); $this->sanitizeSVG($path, $file); return [ 'file' => $path, 'file_name' => $file->getClientOriginalName(), 'file_url' => Storage::url($path), ]; } } ================================================ FILE: packages/Webkul/Admin/src/Http/Controllers/User/AccountController.php ================================================ guard('user')->user(); return view('admin::user.account.edit', compact('user')); } /** * Update the specified resource in storage. * * @return Response */ public function update() { $user = auth()->guard('user')->user(); $this->validate(request(), [ 'name' => 'required', 'email' => 'email|unique:users,email,'.$user->id, 'password' => 'nullable|min:6|confirmed', 'current_password' => 'required|min:6', 'image.*' => 'nullable|mimes:bmp,jpeg,jpg,png,webp', ]); $data = request()->only([ 'name', 'email', 'password', 'password_confirmation', 'current_password', 'image', ]); if (! Hash::check($data['current_password'], $user->password)) { session()->flash('warning', trans('admin::app.account.edit.invalid-password')); return redirect()->back(); } if (isset($data['role_id']) || isset($data['view_permission'])) { session()->flash('warning', trans('admin::app.user.account.permission-denied')); return redirect()->back(); } $isPasswordChanged = false; if (! $data['password']) { unset($data['password']); } else { $isPasswordChanged = true; $data['password'] = bcrypt($data['password']); } if (request()->hasFile('image')) { $data['image'] = current(request()->file('image'))->store('admins/'.$user->id); } else { if (! isset($data['image'])) { if (! empty($data['image'])) { Storage::delete($user->image); } $data['image'] = null; } else { $data['image'] = $user->image; } } $user->update($data); if ($isPasswordChanged) { Event::dispatch('user.account.update-password', $user); } session()->flash('success', trans('admin::app.account.edit.update-success')); return back(); } } ================================================ FILE: packages/Webkul/Admin/src/Http/Controllers/User/ForgotPasswordController.php ================================================ guard('user')->check()) { return redirect()->route('admin.dashboard.index'); } else { if (strpos(url()->previous(), 'user') !== false) { $intendedUrl = url()->previous(); } else { $intendedUrl = route('admin.dashboard.index'); } session()->put('url.intended', $intendedUrl); return view('admin::sessions.forgot-password'); } } /** * Store a newly created resource in storage. * * @return Response */ public function store() { try { $this->validate(request(), [ 'email' => 'required|email', ]); $response = $this->broker()->sendResetLink(request(['email']), function ($user, $token) { $user->notify(new UserResetPassword($token)); }); if ($response == Password::RESET_LINK_SENT) { session()->flash('success', trans('admin::app.users.forget-password.create.reset-link-sent')); return back(); } return back() ->withInput(request(['email'])) ->withErrors([ 'email' => trans('admin::app.users.forget-password.create.email-not-exist'), ]); } catch (\Exception $exception) { session()->flash('error', trans($exception->getMessage())); return redirect()->back(); } } /** * Get the broker to be used during password reset. * * @return PasswordBroker */ public function broker() { return Password::broker('users'); } } ================================================ FILE: packages/Webkul/Admin/src/Http/Controllers/User/ResetPasswordController.php ================================================ with([ 'token' => $token, 'email' => request('email'), ]); } /** * Store a newly created resource in storage. * * @return Response */ public function store() { try { $this->validate(request(), [ 'token' => 'required', 'email' => 'required|email', 'password' => 'required|confirmed|min:6', ]); $response = $this->broker()->reset( request(['email', 'password', 'password_confirmation', 'token']), function ($admin, $password) { $this->resetPassword($admin, $password); } ); if ($response == Password::PASSWORD_RESET) { return redirect()->route('admin.dashboard.index'); } return back() ->withInput(request(['email'])) ->withErrors([ 'email' => trans($response), ]); } catch (\Exception $exception) { session()->flash('error', trans($exception->getMessage())); return redirect()->back(); } } /** * Reset the given admin's password. * * @param CanResetPassword $admin * @param string $password * @return void */ protected function resetPassword($admin, $password) { $admin->password = Hash::make($password); $admin->setRememberToken(Str::random(60)); $admin->save(); event(new PasswordReset($admin)); auth()->guard('user')->login($admin); } /** * Get the broker to be used during password reset. * * @return PasswordBroker */ public function broker() { return Password::broker('users'); } } ================================================ FILE: packages/Webkul/Admin/src/Http/Controllers/User/SessionController.php ================================================ guard('user')->check()) { return redirect()->route('admin.dashboard.index'); } $previousUrl = url()->previous(); $intendedUrl = str_contains($previousUrl, 'admin') ? $previousUrl : route('admin.dashboard.index'); session()->put('url.intended', $intendedUrl); return view('admin::sessions.login'); } /** * Store a newly created resource in storage. */ public function store(): RedirectResponse { $this->validate(request(), [ 'email' => 'required|email', 'password' => 'required', ]); if (! auth()->guard('user')->attempt(request(['email', 'password']), request('remember'))) { session()->flash('error', trans('admin::app.users.login-error')); return redirect()->back(); } if (auth()->guard('user')->user()->status == 0) { session()->flash('warning', trans('admin::app.users.activate-warning')); auth()->guard('user')->logout(); return redirect()->route('admin.session.create'); } $menus = menu()->getItems('admin'); $availableNextMenu = $menus?->first(); if (! bouncer()->hasPermission('dashboard')) { if (is_null($availableNextMenu)) { session()->flash('error', trans('admin::app.users.not-permission')); auth()->guard('user')->logout(); return redirect()->route('admin.session.create'); } return redirect()->to($availableNextMenu->getUrl()); } $hasAccessToIntendedUrl = $this->canAccessIntendedUrl($menus, redirect()->getIntendedUrl()); if ($hasAccessToIntendedUrl) { return redirect()->intended(route('admin.dashboard.index')); } return redirect()->to($availableNextMenu->getUrl()); } /** * Remove the specified resource from storage. */ public function destroy(): RedirectResponse { auth()->guard('user')->logout(); return redirect()->route('admin.session.create'); } /** * Find menu item by URL. */ protected function canAccessIntendedUrl(Collection $menus, ?string $url): ?MenuItem { if (is_null($url)) { return null; } foreach ($menus as $menu) { if ($menu->getUrl() === $url) { return $menu; } if ($menu->haveChildren()) { $found = $this->canAccessIntendedUrl($menu->getChildren(), $url); if ($found) { return $found; } } } return null; } } ================================================ FILE: packages/Webkul/Admin/src/Http/Middleware/Bouncer.php ================================================ guard($guard)->check()) { return redirect()->route('admin.session.create'); } /** * If user status is changed by admin. Then session should be * logged out. */ if (! (bool) auth()->guard($guard)->user()->status) { auth()->guard($guard)->logout(); session()->flash('error', trans('admin::app.errors.401')); return redirect()->route('admin.session.create'); } /** * If somehow the user deleted all permissions, then it should be * auto logged out and need to contact the administrator again. */ if ($this->isPermissionsEmpty()) { auth()->guard($guard)->logout(); session()->flash('error', trans('admin::app.errors.401')); return redirect()->route('admin.session.create'); } return $next($request); } /** * Check for user, if they have empty permissions or not except admin. * * @return bool */ public function isPermissionsEmpty() { if (! $role = auth()->guard('user')->user()->role) { abort(401, 'This action is unauthorized.'); } if ($role->permission_type === 'all') { return false; } if ($role->permission_type !== 'all' && empty($role->permissions)) { return true; } $this->checkIfAuthorized(); return false; } /** * Check authorization. * * @return null */ public function checkIfAuthorized() { $roles = acl()->getRoles(); if (isset($roles[Route::currentRouteName()])) { bouncer()->allow($roles[Route::currentRouteName()]); } } } ================================================ FILE: packages/Webkul/Admin/src/Http/Middleware/Locale.php ================================================ app = $app; $this->request = $request; } /** * Handle an incoming request. * * @param Request $request * @return mixed */ public function handle($request, Closure $next) { app()->setLocale( core()->getConfigData('general.general.locale_settings.locale') ?: app()->getLocale() ); return $next($request); } } ================================================ FILE: packages/Webkul/Admin/src/Http/Middleware/SanitizeUrl.php ================================================ ajax()) { return $next($request); } $route = $request->route('route'); $sanitizedRoute = Str::of($route)->ascii()->lower()->replaceMatches('/[^a-z0-9_-]/', '')->__toString(); $request->route()->setParameter('route', $sanitizedRoute); /** * Whitelist acceptable route values to prevent unexpected input */ $allowedRoutes = [ SupportedFolderEnum::INBOX->value, SupportedFolderEnum::IMPORTANT->value, SupportedFolderEnum::STARRED->value, SupportedFolderEnum::DRAFT->value, SupportedFolderEnum::OUTBOX->value, SupportedFolderEnum::SENT->value, SupportedFolderEnum::SPAM->value, SupportedFolderEnum::TRASH->value, ]; if (! in_array($route, $allowedRoutes, true)) { abort(401, trans('admin::app.mail.invalid-route')); } return $next($request); } } ================================================ FILE: packages/Webkul/Admin/src/Http/Requests/AttributeForm.php ================================================ attributeRepository->scopeQuery(function ($query) { $query = $query->whereIn('code', array_keys(request()->all())) ->where('entity_type', request('entity_type')); if (request()->has('quick_add')) { $query = $query->where('quick_add', 1); } return $query; })->get(); foreach ($attributes as $attribute) { $validations = []; if ($attribute->type == 'boolean') { continue; } elseif ($attribute->type == 'address') { if (! $attribute->is_required) { continue; } $validations = [ $attribute->code.'.address' => 'required', $attribute->code.'.country' => 'required', $attribute->code.'.state' => 'required', $attribute->code.'.city' => 'required', $attribute->code.'.postcode' => 'required', ]; } elseif ($attribute->type == 'email') { $validations = [ $attribute->code => [$attribute->is_required ? 'required' : 'nullable'], $attribute->code.'.*.value' => [$attribute->is_required ? 'required' : 'nullable', 'email'], $attribute->code.'.*.label' => $attribute->is_required ? 'required' : 'nullable', ]; } elseif ($attribute->type == 'phone') { $validations = [ $attribute->code => [$attribute->is_required ? 'required' : 'nullable'], $attribute->code.'.*.value' => [$attribute->is_required ? 'required' : 'nullable'], $attribute->code.'.*.label' => $attribute->is_required ? 'required' : 'nullable', ]; } else { $validations[$attribute->code] = [$attribute->is_required ? 'required' : 'nullable']; if ($attribute->type == 'text' && $attribute->validation) { array_push($validations[$attribute->code], $attribute->validation == 'decimal' ? new Decimal : $attribute->validation ); } if ($attribute->type == 'price') { array_push($validations[$attribute->code], new Decimal); } if ($attribute->type == 'image' && ! request($attribute->code.'.delete')) { array_push($validations[$attribute->code], 'mimes:bmp,jpeg,jpg,png,webp'); } } if ($attribute->is_unique) { array_push($validations[in_array($attribute->type, ['email', 'phone']) ? $attribute->code.'.*.value' : $attribute->code ], function ($field, $value, $fail) use ($attribute) { if (! $this->attributeValueRepository->isValueUnique($this->id, $attribute->entity_type, $attribute, request($field))) { $fail('The value has already been taken.'); } }); } $this->rules = array_merge($this->rules, $validations); } return $this->rules; } } ================================================ FILE: packages/Webkul/Admin/src/Http/Requests/ConfigurationForm.php ================================================ input('keys', []))->mapWithKeys(function ($item) { $data = json_decode($item, true); return collect($data['fields'])->mapWithKeys(function ($field) use ($data) { $key = $data['key'].'.'.$field['name']; // Check delete key exist in the request if (! $this->has($key.'.delete')) { $validation = isset($field['validation']) && $field['validation'] ? $field['validation'] : 'nullable'; return [$key => $validation]; } return []; })->toArray(); })->toArray(); } } ================================================ FILE: packages/Webkul/Admin/src/Http/Requests/LeadForm.php ================================================ $entityType) { $attributes = $this->attributeRepository->scopeQuery(function ($query) use ($entityType) { $attributeCodes = $entityType == 'persons' ? array_keys(request('person') ?? []) : array_keys(request()->all()); $query = $query->whereIn('code', $attributeCodes) ->where('entity_type', $entityType); if (request()->has('quick_add')) { $query = $query->where('quick_add', 1); } return $query; })->get(); foreach ($attributes as $attribute) { if ($entityType == 'persons') { $attribute->code = 'person.'.$attribute->code; } $validations = []; if ($attribute->type == 'boolean') { continue; } elseif ($attribute->type == 'address') { if (! $attribute->is_required) { continue; } $validations = [ $attribute->code.'.address' => 'required', $attribute->code.'.country' => 'required', $attribute->code.'.state' => 'required', $attribute->code.'.city' => 'required', $attribute->code.'.postcode' => 'required', ]; } elseif ($attribute->type == 'email') { $validations = [ $attribute->code => [$attribute->is_required ? 'required' : 'nullable'], $attribute->code.'.*.value' => [$attribute->is_required ? 'required' : 'nullable', 'email'], $attribute->code.'.*.label' => $attribute->is_required ? 'required' : 'nullable', ]; } elseif ($attribute->type == 'phone') { $validations = [ $attribute->code => [$attribute->is_required ? 'required' : 'nullable'], $attribute->code.'.*.value' => [$attribute->is_required ? 'required' : 'nullable'], $attribute->code.'.*.label' => $attribute->is_required ? 'required' : 'nullable', ]; } else { $validations[$attribute->code] = [$attribute->is_required ? 'required' : 'nullable']; if ($attribute->type == 'text' && $attribute->validation) { array_push($validations[$attribute->code], $attribute->validation == 'decimal' ? new Decimal : $attribute->validation ); } if ($attribute->type == 'price') { array_push($validations[$attribute->code], new Decimal); } } if ($attribute->is_unique) { array_push($validations[in_array($attribute->type, ['email', 'phone']) ? $attribute->code.'.*.value' : $attribute->code ], function ($field, $value, $fail) use ($attribute, $entityType) { if (! $this->attributeValueRepository->isValueUnique( $entityType == 'persons' ? request('person.id') : $this->id, $attribute->entity_type, $attribute, request($field) ) ) { $fail('The value has already been taken.'); } }); } $this->rules = array_merge($this->rules, $validations); } } $this->rules['expected_close_date'] = [ 'date_format:Y-m-d', 'after:'.Carbon::yesterday()->format('Y-m-d'), ]; return [ ...$this->rules, 'products' => 'array', 'products.*.product_id' => 'sometimes|required|exists:products,id', 'products.*.name' => 'required_with:products.*.product_id', 'products.*.price' => 'required_with:products.*.product_id', 'products.*.quantity' => 'required_with:products.*.product_id', ]; } /** * Get the validation messages that apply to the request. */ public function messages(): array { return [ 'products.*.product_id.exists' => trans('admin::app.leads.selected-product-not-exist'), 'products.*.name.required_with' => trans('admin::app.leads.product-name-required'), 'products.*.price.required_with' => trans('admin::app.leads.product-price-required'), 'products.*.quantity.required_with' => trans('admin::app.leads.product-quantity-required'), ]; } } ================================================ FILE: packages/Webkul/Admin/src/Http/Requests/MassDestroyRequest.php ================================================ ['required', 'array'], 'indices.*' => ['integer'], ]; } } ================================================ FILE: packages/Webkul/Admin/src/Http/Requests/MassUpdateRequest.php ================================================ ['required', 'array'], 'indices.*' => ['integer'], 'value' => ['required'], ]; } } ================================================ FILE: packages/Webkul/Admin/src/Http/Requests/PipelineForm.php ================================================ validatorExtensions($validationFactory); } /** * Determine if the user is authorized to make this request. * * @return bool */ public function authorize() { return true; } /** * Get the validation rules that apply to the request. * * @return array */ public function rules() { if (request('id')) { return [ 'name' => 'required|unique:lead_pipelines,name,'.request('id'), 'stages.*.name' => 'unique_key', 'stages.*.code' => 'unique_key', ]; } return [ 'name' => 'required|unique:lead_pipelines,name', 'rotten_days' => 'required', 'stages.*.name' => 'unique_key', 'stages.*.code' => 'unique_key', ]; } /** * Get the error messages for the defined validation rules. * * @return array */ public function messages() { return [ 'stages.*.name.unique_key' => trans('admin::app.settings.pipelines.duplicate-name'), ]; } /** * Place all your validator extensions here. * * @return void */ public function validatorExtensions(ValidationFactory $validationFactory) { $validationFactory->extend( 'unique_key', function ($attribute, $value, $parameters) { $key = last(explode('.', $attribute)); $stages = collect(request()->get('stages'))->filter(function ($stage) use ($key, $value) { return $stage[$key] === $value; }); if ($stages->count() > 1) { return false; } return true; } ); } } ================================================ FILE: packages/Webkul/Admin/src/Http/Requests/UserForm.php ================================================ rules = [ 'name' => 'required', 'email' => 'email|unique:users,email', 'password' => 'nullable', 'password_confirmation' => 'nullable|required_with:password|same:password', 'status' => 'sometimes', 'role_id' => 'required', ]; if ($this->method() == 'PUT') { $this->rules['email'] = 'email|unique:users,email,'.$this->route('id'); } return $this->rules; } } ================================================ FILE: packages/Webkul/Admin/src/Http/Resources/ActivityFileResource.php ================================================ $this->id, 'name' => $this->name, 'path' => $this->path, 'url' => $this->url, 'created_at' => $this->created_at, 'updated_at' => $this->updated_at, ]; } } ================================================ FILE: packages/Webkul/Admin/src/Http/Resources/ActivityParticipantResource.php ================================================ $this->when($this->user, new UserResource($this->user)), 'person' => $this->when($this->person, new PersonResource($this->person)), ]; } } ================================================ FILE: packages/Webkul/Admin/src/Http/Resources/ActivityResource.php ================================================ $this->id, 'parent_id' => $this->parent_id ?? null, 'title' => $this->title, 'type' => $this->type, 'comment' => $this->comment, 'additional' => is_array($this->resource->additional) ? $this->resource->additional : json_decode($this->resource->additional, true), 'schedule_from' => $this->schedule_from, 'schedule_to' => $this->schedule_to, 'is_done' => $this->is_done, 'user' => new UserResource($this->user), 'files' => ActivityFileResource::collection($this->files), 'participants' => ActivityParticipantResource::collection($this->participants), 'location' => $this->location, 'created_at' => $this->created_at, 'updated_at' => $this->updated_at, ]; } } ================================================ FILE: packages/Webkul/Admin/src/Http/Resources/EmailAttachmentResource.php ================================================ $this->id, 'name' => $this->name, 'path' => $this->path, 'url' => $this->url, 'size' => $this->size, 'content_type' => $this->content_type, 'content_id' => $this->content_id, 'created_at' => $this->created_at, 'updated_at' => $this->updated_at, ]; } } ================================================ FILE: packages/Webkul/Admin/src/Http/Resources/EmailResource.php ================================================ $this->id, 'subject' => $this->subject, 'source' => $this->source, 'user_type' => $this->user_type, 'name' => $this->name, 'reply' => $this->reply, 'is_read' => $this->is_read, 'folders' => $this->folders, 'from' => $this->from, 'sender' => $this->sender, 'reply_to' => $this->reply_to, 'cc' => $this->cc, 'bcc' => $this->bcc, 'unique_id' => $this->unique_id, 'message_id' => $this->message_id, 'reference_ids' => $this->reference_ids, 'person' => new PersonResource($this->person), 'lead' => new LeadResource($this->lead), 'parent_id' => $this->parent_id, 'parent' => new EmailResource($this->parent), 'attachments' => EmailAttachmentResource::collection($this->attachments), 'created_at' => $this->created_at, 'updated_at' => $this->updated_at, ]; } } ================================================ FILE: packages/Webkul/Admin/src/Http/Resources/LeadResource.php ================================================ $this->id, 'title' => $this->title, 'lead_value' => $this->lead_value, 'formatted_lead_value' => core()->formatBasePrice($this->lead_value), 'status' => $this->status, 'expected_close_date' => $this->expected_close_date, 'rotten_days' => $this->rotten_days, 'closed_at' => $this->closed_at, 'created_at' => $this->created_at, 'updated_at' => $this->updated_at, 'person' => new PersonResource($this->person), 'user' => new UserResource($this->user), 'type' => new TypeResource($this->type), 'source' => new SourceResource($this->source), 'pipeline' => new PipelineResource($this->pipeline), 'stage' => new StageResource($this->stage), 'tags' => TagResource::collection($this->tags), ]; } } ================================================ FILE: packages/Webkul/Admin/src/Http/Resources/OrganizationResource.php ================================================ $this->id, 'name' => $this->name, 'address' => $this->address, 'created_at' => $this->created_at, 'updated_at' => $this->updated_at, ]; } } ================================================ FILE: packages/Webkul/Admin/src/Http/Resources/PersonResource.php ================================================ $this->id, 'name' => $this->name, 'emails' => $this->emails, 'contact_numbers' => $this->contact_numbers, 'organization' => new OrganizationResource($this->organization), 'created_at' => $this->created_at, 'updated_at' => $this->updated_at, ]; } } ================================================ FILE: packages/Webkul/Admin/src/Http/Resources/PipelineResource.php ================================================ $this->id, 'name' => $this->name, 'is_default' => $this->is_default, 'rotten_days' => $this->rotten_days, 'stages' => StageResource::collection($this->stages), 'created_at' => $this->created_at, 'updated_at' => $this->updated_at, ]; } } ================================================ FILE: packages/Webkul/Admin/src/Http/Resources/ProductResource.php ================================================ $this->id, 'name' => $this->name, 'description' => $this->description, 'sku' => $this->sku, 'price' => $this->price, 'created_at' => $this->created_at, 'updated_at' => $this->updated_at, ]; } } ================================================ FILE: packages/Webkul/Admin/src/Http/Resources/QuoteResource.php ================================================ $this->id, 'subject' => $this->subject, 'description' => $this->description, 'billing_address' => $this->billing_address, 'shipping_address' => $this->shipping_address, 'discount_percent' => $this->discount_percent, 'discount_amount' => $this->discount_amount, 'tax_amount' => $this->tax_amount, 'adjustment_amount' => $this->adjustment_amount, 'sub_total' => $this->sub_total, 'grand_total' => $this->grand_total, 'expired_at' => $this->expired_at, 'user' => new UserResource($this->user), 'person' => new PersonResource($this->person), 'leads' => LeadResource::collection($this->leads), ]; } } ================================================ FILE: packages/Webkul/Admin/src/Http/Resources/SourceResource.php ================================================ $this->id, 'name' => $this->name, 'created_at' => $this->created_at, 'updated_at' => $this->updated_at, ]; } } ================================================ FILE: packages/Webkul/Admin/src/Http/Resources/StageResource.php ================================================ $this->id, 'code' => $this->code, 'name' => $this->name, 'lead_value' => $this->lead_value, 'formatted_lead_value' => core()->formatBasePrice($this->lead_value), 'is_user_defined' => $this->is_user_defined, 'sort_order' => $this->sort_order, 'created_at' => $this->created_at, 'updated_at' => $this->updated_at, ]; } } ================================================ FILE: packages/Webkul/Admin/src/Http/Resources/TagResource.php ================================================ $this->id, 'name' => $this->name, 'color' => $this->color, 'user' => new UserResource($this->user), 'created_at' => $this->created_at, 'updated_at' => $this->updated_at, ]; } } ================================================ FILE: packages/Webkul/Admin/src/Http/Resources/TypeResource.php ================================================ $this->id, 'name' => $this->name, 'created_at' => $this->created_at, 'updated_at' => $this->updated_at, ]; } } ================================================ FILE: packages/Webkul/Admin/src/Http/Resources/UserResource.php ================================================ $this->id, 'name' => $this->name, 'email' => $this->email, 'image' => $this->image, 'created_at' => $this->created_at, 'updated_at' => $this->name, ]; } } ================================================ FILE: packages/Webkul/Admin/src/Http/helpers.php ================================================ make('bouncer'); } } ================================================ FILE: packages/Webkul/Admin/src/Listeners/Activity.php ================================================ input('lead_id')) { $lead = $this->leadRepository->find(request()->input('lead_id')); if (! $lead->activities->contains($activity->id)) { $lead->activities()->attach($activity->id); } } elseif (request()->input('person_id')) { $person = $this->personRepository->find(request()->input('person_id')); if (! $person->activities->contains($activity->id)) { $person->activities()->attach($activity->id); } } elseif (request()->input('warehouse_id')) { $warehouse = $this->warehouseRepository->find(request()->input('warehouse_id')); if (! $warehouse->activities->contains($activity->id)) { $warehouse->activities()->attach($activity->id); } } elseif (request()->input('product_id')) { $product = $this->productRepository->find(request()->input('product_id')); if (! $product->activities->contains($activity->id)) { $product->activities()->attach($activity->id); } } } } ================================================ FILE: packages/Webkul/Admin/src/Listeners/Lead.php ================================================ emailRepository->update([ 'lead_id' => $lead->id, ], request('email_id')); } } ================================================ FILE: packages/Webkul/Admin/src/Listeners/Person.php ================================================ emailRepository->update([ 'person_id' => $person->id, ], request('email_id')); } } ================================================ FILE: packages/Webkul/Admin/src/Notifications/Common.php ================================================ to($this->data['to']) ->subject($this->data['subject']) ->view('admin::emails.common.index', [ 'body' => $this->data['body'], ]); if (isset($this->data['attachments'])) { foreach ($this->data['attachments'] as $attachment) { $message->attachData($attachment['content'], $attachment['name'], [ 'mime' => $attachment['mime'], ]); } } return $message; } } ================================================ FILE: packages/Webkul/Admin/src/Notifications/User/Create.php ================================================ to($this->user->email) ->subject(trans('admin::app.emails.common.user.create-subject')) ->view('admin::emails.users.create', [ 'user_name' => $this->user->name, ]); } } ================================================ FILE: packages/Webkul/Admin/src/Notifications/User/UserResetPassword.php ================================================ token); } return (new MailMessage) ->view('admin::emails.users.forget-password', [ 'user_name' => $notifiable->name, 'token' => $this->token, ]); } } ================================================ FILE: packages/Webkul/Admin/src/Notifications/User/UserUpdatePassword.php ================================================ from(core()->getSenderEmailDetails()['email'], core()->getSenderEmailDetails()['name']) ->to($this->user->email, $this->user->name) ->subject(trans('shop::app.mail.update-password.subject')) ->view('shop::emails.users.update-password', ['user' => $this->user]); } } ================================================ FILE: packages/Webkul/Admin/src/Providers/AdminServiceProvider.php ================================================ aliasMiddleware('user', BouncerMiddleware::class); $router->aliasMiddleware('admin_locale', Locale::class); $router->aliasMiddleware('sanitize_url', SanitizeUrl::class); include __DIR__.'/../Http/helpers.php'; Route::middleware(['web', 'admin_locale', 'user']) ->prefix(config('app.admin_path')) ->group(__DIR__.'/../Routes/Admin/web.php'); Route::middleware(['web', 'admin_locale']) ->group(__DIR__.'/../Routes/Front/web.php'); $this->loadMigrationsFrom(__DIR__.'/../Database/Migrations'); $this->loadTranslationsFrom(__DIR__.'/../Resources/lang', 'admin'); $this->loadViewsFrom(__DIR__.'/../Resources/views', 'admin'); Blade::anonymousComponentPath(__DIR__.'/../Resources/views/components', 'admin'); $this->app->bind(ExceptionHandler::class, Handler::class); Relation::morphMap([ 'leads' => Lead::class, 'organizations' => Organization::class, 'persons' => Person::class, 'products' => Product::class, 'quotes' => Quote::class, 'warehouses' => Warehouse::class, ]); $this->app->register(EventServiceProvider::class); } /** * Register services. * * @return void */ public function register() { $this->registerFacades(); $this->registerConfig(); } /** * Register Bouncer as a singleton. */ protected function registerFacades(): void { $loader = AliasLoader::getInstance(); $loader->alias('Bouncer', \Webkul\Admin\Facades\Bouncer::class); $this->app->singleton('bouncer', function () { return new Bouncer; }); } /** * Register package config. */ protected function registerConfig(): void { $this->mergeConfigFrom(dirname(__DIR__).'/Config/acl.php', 'acl'); $this->mergeConfigFrom(dirname(__DIR__).'/Config/menu.php', 'menu.admin'); $this->mergeConfigFrom(dirname(__DIR__).'/Config/core_config.php', 'core_config'); $this->mergeConfigFrom(dirname(__DIR__).'/Config/attribute_lookups.php', 'attribute_lookups'); $this->mergeConfigFrom(dirname(__DIR__).'/Config/attribute_entity_types.php', 'attribute_entity_types'); } } ================================================ FILE: packages/Webkul/Admin/src/Providers/EventServiceProvider.php ================================================ [ 'Webkul\Admin\Listeners\Person@linkToEmail', ], 'lead.create.after' => [ 'Webkul\Admin\Listeners\Lead@linkToEmail', ], 'activity.create.after' => [ 'Webkul\Admin\Listeners\Activity@afterUpdateOrCreate', ], 'activity.update.after' => [ 'Webkul\Admin\Listeners\Activity@afterUpdateOrCreate', ], ]; } ================================================ FILE: packages/Webkul/Admin/src/Providers/ModuleServiceProvider.php ================================================ |string> */ public function rules() { return [ 'name' => 'required|string|max:255', 'entity_type' => 'required|string|max:255', 'description' => 'nullable|string|max:255', 'method' => 'required|string|max:255', 'end_point' => 'required|string|max:255', 'query_params' => 'nullable', 'headers' => 'nullable', 'payload_type' => [ 'required', 'string', 'max:255', Rule::in(['default', 'x-www-form-urlencoded', 'raw']), ], 'raw_payload_type' => [ 'string', 'max:255', Rule::in(['json', 'text']), ], 'payload' => 'nullable', ]; } } ================================================ FILE: packages/Webkul/Admin/src/Resources/assets/css/app.css ================================================ @tailwind base; @tailwind components; @tailwind utilities; /* -------------------------------- new css -------------------------------- */ @font-face { font-family: 'icomoon'; src: url('../fonts/icomoon.woff?w2trdd') format('woff'); font-weight: normal; font-style: normal; font-display: block; } [class^="icon-"], [class*=" icon-"] { /* use !important to prevent issues with browser extensions that change fonts */ font-family: 'icomoon' !important; speak: never; font-style: normal; font-weight: normal; font-variant: normal; text-transform: none; line-height: 1; /* Better Font Rendering =========== */ -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } @layer components { .icon-image:before { content: "\e956"; } .icon-bookmark:before { content: "\e92c"; } .icon-bookmark-active:before { content: "\e92d"; } .icon-stats-down:before { content: "\e94d"; } .icon-error:before { content: "\e951"; } .icon-info:before { content: "\e952"; } .icon-success:before { content: "\e953"; } .icon-warning:before { content: "\e954"; } .icon-stats-down:before { content: "\e94d"; } .icon-stats-up:before { content: "\e94f"; } .icon-attribute:before { content: "\e947"; } .icon-download:before { content: "\e948"; } .icon-settings-warehouse:before { content: "\e94e"; } .icon-move:before { content: "\e949"; } .icon-organization:before { content: "\e94a"; } .icon-role:before { content: "\e94b"; } .icon-user:before { content: "\e94c"; } .icon-system-generate:before { content: "\e950"; } .icon-settings-attributes:before { content: "\e93b"; } .icon-settings-flow:before { content: "\e93c"; } .icon-settings-group:before { content: "\e93d"; } .icon-settings-mail:before { content: "\e93e"; } .icon-settings-pipeline:before { content: "\e93f"; } .icon-settings-roles:before { content: "\e940"; } .icon-settings-sources:before { content: "\e941"; } .icon-settings-tag:before { content: "\e942"; } .icon-settings-type:before { content: "\e943"; } .icon-settings-user:before { content: "\e944"; } .icon-settings-webforms:before { content: "\e945"; } .icon-settings-webhooks:before { content: "\e946"; } .icon-attached-file:before { content: "\e932"; } .icon-forward:before { content: "\e933"; } .icon-location:before { content: "\e934"; } .icon-pin:before { content: "\e935"; } .icon-print:before { content: "\e936"; } .icon-reply-all:before { content: "\e937"; } .icon-reply:before { content: "\e938"; } .icon-rotten:before { content: "\e939"; } .icon-tag:before { content: "\e93a"; } .icon-list:before { content: "\e92e"; } .icon-enter:before { content: "\e92f"; } .icon-kanban:before { content: "\e930"; } .icon-tick:before { content: "\e931"; } .icon-eye-hide:before { content: "\e929"; } .icon-percentage:before { content: "\e92a"; } .icon-dollar:before { content: "\e92b"; } .icon-radio-selected:before { content: "\e924"; } .icon-radio-normal:before { content: "\e925"; } .icon-folder:before { content: "\e926"; } .icon-file:before { content: "\e927"; } .icon-eye:before { content: "\e928"; } .icon-notification:before { content: "\e900"; } .icon-configuration:before { content: "\e901"; } .icon-note:before { content: "\e902"; } .icon-edit:before { content: "\e903"; } .icon-menu:before { content: "\e955"; } .icon-calendar:before { content: "\e904"; } .icon-delete:before { content: "\e905"; } .icon-more:before { content: "\e906"; } .icon-checkbox-multiple:before { content: "\e907"; } .icon-checkbox-select:before { content: "\e908"; } .icon-checkbox-outline:before { content: "\e909"; } .icon-message:before { content: "\e90a"; } .icon-video:before { content: "\e90b"; } .icon-attachment:before { content: "\e90c"; } .icon-sent:before { content: "\e90d"; } .icon-call:before { content: "\e90e"; } .icon-meeting:before { content: "\e90f"; } .icon-light:before { content: "\e910"; } .icon-dark:before { content: "\e911"; } .icon-mail:before { content: "\e912"; } .icon-leads:before { content: "\e913"; } .icon-filter:before { content: "\e914"; } .icon-setting:before { content: "\e915"; } .icon-product:before { content: "\e916"; } .icon-contact:before { content: "\e917"; } .icon-activity:before { content: "\e918"; } .icon-perosnal:before { content: "\e919"; } .icon-quote:before { content: "\e91a"; } .icon-dashboard:before { content: "\e91b"; } .icon-cross-large:before { content: "\e91c"; } .icon-left-arrow:before { content: "\e91d"; } .icon-right-arrow:before { content: "\e91e"; } .icon-up-arrow:before { content: "\e91f"; } .icon-down-arrow:before { content: "\e920"; } .icon-search:before { content: "\e921"; } .icon-add:before { content: "\e922"; } .icon-add-2:before { content: "\e923"; } .label-active { @apply max-w-max rounded-md bg-emerald-200 px-3 py-1.5 text-xs font-medium text-emerald-900; } .label-inactive { @apply max-w-max rounded-md bg-red-200 px-3 py-1.5 text-xs font-medium text-red-800; } [dir="ltr"] .sidebar-rounded::after { @apply content-[''] w-[30px] h-[30px] top-0 right-[-30px] absolute bg-no-repeat pointer-events-none bg-[url("../images/corner-clip.svg")] transition-all; } [dir="rtl"] .sidebar-rounded::before { @apply content-[''] w-[30px] h-[30px] top-0 left-[-30px] absolute bg-no-repeat pointer-events-none bg-[url("../images/corner-clip.svg")] transition-all rotate-90; } .dark .sidebar-rounded::after { @apply bg-[url("../images/dark-corner-clip.svg")] } .dark .sidebar-rounded::before { @apply bg-[url("../images/dark-corner-clip.svg")] } .tox.tox-silver-sink.tox-tinymce-aux { z-index: 99999; } .stage::after { content: ''; position: absolute; top: 50%; right: -10px; width: 24px; height: 24px; z-index: 1; border-radius: 0 0 0 25px; transform: translateY(-50%) rotate(45deg); border-right: 4px solid #f3f4f6; border-top: 4px solid #f3f4f6; } .dark .stage::after { content: ''; position: absolute; top: 50%; right: -10px; width: 24px; height: 24px; z-index: 1; border-radius: 0 0 0 25px; transform: translateY(-50%) rotate(45deg); border-right: 4px solid #030712; border-top: 4px solid #030712; } [dir="rtl"] .stage::before { content: ''; position: absolute; top: 50%; left: -10px; width: 24px; height: 24px; z-index: 1; border-radius: 0 0 0 25px; transform: translateY(-50%) rotate(225deg); border-right: 4px solid #f3f4f6; border-top: 4px solid #f3f4f6; } [dir="rtl"] .stage::after { display: none; } .dark\:bg-gray-400:is(.dark *) { --tw-bg-opacity: 1 !important; background-color: #1f2937 !important; border: #111827 solid !important; color: #e5e7eb !important; } .dark\:bg-gray-800:is(.dark *) { --tw-bg-opacity: 1; background-color: rgb(16 24 39); } .shimmer { animation-duration: 2.2s; animation-fill-mode: forwards; animation-iteration-count: infinite; animation-name: skeleton; animation-timing-function: linear; background: linear-gradient( to right, #f6f6f6 8%, #f0f0f0 18%, #f6f6f6 33% ); background-size: 1250px 100%; } .light-shimmer-bg { background: linear-gradient( to right, #fafafa 8%, #f5f5f5 18%, #fafafa 33% ); background-size: 1250px 100%; } .dark .shimmer { background: linear-gradient( to right, #1f2937 8%, #1a2232 18%, #1f2937 33% ); } .primary-button { @apply bg-brandColor border border-brandColor cursor-pointer flex focus:opacity-[0.9] font-semibold gap-x-1 hover:opacity-[0.9] items-center place-content-center px-3 py-1.5 rounded-md text-gray-50 transition-all; } .secondary-button { @apply flex cursor-pointer place-content-center items-center gap-x-1 whitespace-nowrap rounded-md border-2 border-brandColor bg-white px-3 py-1.5 font-semibold text-brandColor transition-all hover:bg-[#eff6ff61] focus:bg-[#eff6ff61] dark:border-gray-400 dark:bg-gray-800 dark:text-white dark:hover:opacity-80; } .transparent-button { @apply flex cursor-pointer appearance-none place-content-center items-center gap-x-1 whitespace-nowrap rounded-md border-2 border-transparent px-3 py-1.5 font-semibold text-gray-600 transition-all marker:shadow hover:bg-gray-100 focus:bg-gray-100 dark:hover:bg-gray-950 dark:focus:bg-gray-950; } ::-webkit-scrollbar { width: 12px; } ::-webkit-scrollbar-track { background: #f1f1f1; } ::-webkit-scrollbar-thumb { background: #888; border-radius: 6px; } ::-webkit-scrollbar-thumb:hover { background: #555; } /* Firefox */ * { scrollbar-width: thin; scrollbar-color: #888 #f1f1f1; } ::selection { background-color: rgba(0, 68, 242, .2); } body { @apply bg-gray-100 text-sm text-gray-800; } button:disabled { @apply cursor-not-allowed opacity-50; } button:disabled:hover { @apply cursor-not-allowed opacity-50; } .draggable-ghost { opacity: 0.5; background: #e0e7ff; } html.dark [class^="icon-"], html.dark [class*=" icon-"]{ color: #d1d5db; } p { @apply text-[14px] !leading-[17px]; } input, textarea, select { @apply outline-none; } .journal-scroll::-webkit-scrollbar { width: 14px; cursor: pointer; display: none; } .journal-scroll::-webkit-scrollbar-track { background-color: #fff; cursor: pointer; border-radius: 12px; border: 1px solid #e9e9e9; } .journal-scroll::-webkit-scrollbar-thumb { cursor: pointer; background-color: #e9e9e9; border-radius: 12px; border: 3px solid transparent; background-clip: content-box; } .custom-select { -webkit-appearance: none; -moz-appearance: none; background: transparent; background-image: url("data:image/svg+xml;utf8,"); background-repeat: no-repeat; background-position-x: calc(100% - 10px); background-position-y: 50%; } .dark .custom-select{ background-image: url("data:image/svg+xml;utf8,"); } [dir="rtl"] .custom-select { background-position-x: calc(100% - (100% - 10px)); } .draggable-ghost { @apply border border-dashed !border-gray-300; } @keyframes skeleton { 0% { @apply bg-[-1250px_0]; } 100% { @apply bg-[1250px_0]; } } .required:after { @apply content-['*']; } } .tox .tox-toolbar__group:last-child button { padding: 6px 8px; background: #eff6ff; color: #2563EB; } .tox .tox-toolbar__group:last-child button:hover { background: #dbeafe; } .tox .tox-toolbar__group:last-child button[aria-disabled="true"] { @apply cursor-not-allowed opacity-50; } .tox .tox-toolbar__group:last-child button { @apply !bg-transparent; } .tox .tox-toolbar__group:last-child button:hover { @apply border-0 !bg-[#3389ec] text-white shadow-none; } .tox.tox-silver-sink.tox-tinymce-aux { z-index: 99999; } ================================================ FILE: packages/Webkul/Admin/src/Resources/assets/js/app.js ================================================ /** * This will track all the images and fonts for publishing. */ import.meta.glob(["../images/**", "../fonts/**"]); /** * Main vue bundler. */ import { createApp } from "vue/dist/vue.esm-bundler"; /** * Main root application registry. */ window.app = createApp({ data() { return { isMenuActive: false, hoveringMenu: '', }; }, created() { window.addEventListener('click', this.handleFocusOut); }, beforeDestroy() { window.removeEventListener('click', this.handleFocusOut); }, methods: { onSubmit() {}, onInvalidSubmit({ values, errors, results }) { setTimeout(() => { const errorKeys = Object.entries(errors) .map(([key, value]) => ({ key, value })) .filter(error => error["value"].length); let firstErrorElement = document.querySelector('[name="' + errorKeys[0]["key"] + '"]'); firstErrorElement.scrollIntoView({ behavior: "smooth", block: "center" }); }, 100); }, handleMouseOver(event) { if (this.isMenuActive) { return; } const parentElement = event.currentTarget.parentElement; if (parentElement.classList.contains('sidebar-collapsed')) { parentElement.classList.remove('sidebar-collapsed'); parentElement.classList.add('sidebar-not-collapsed'); } }, handleMouseLeave(event) { if (this.isMenuActive) { return; } const parentElement = event.currentTarget.parentElement; if (parentElement.classList.contains('sidebar-not-collapsed')) { parentElement.classList.remove('sidebar-not-collapsed'); parentElement.classList.add('sidebar-collapsed'); } }, handleFocusOut(event) { const sidebar = this.$refs.sidebar; if ( sidebar && !sidebar.contains(event.target) ) { this.isMenuActive = false; const parentElement = sidebar.parentElement; if (parentElement.classList.contains('sidebar-not-collapsed')) { parentElement.classList.remove('sidebar-not-collapsed'); parentElement.classList.add('sidebar-collapsed'); } } }, }, }); /** * Global plugins registration. */ import Admin from "./plugins/admin"; import Axios from "./plugins/axios"; import Emitter from "./plugins/emitter"; import Flatpickr from "./plugins/flatpickr"; import VeeValidate from "./plugins/vee-validate"; import CreateElement from "./plugins/createElement"; import Draggable from "./plugins/draggable"; import VueCal from "./plugins/vue-cal"; [ Admin, Axios, Emitter, CreateElement, Draggable, Flatpickr, VeeValidate, VueCal, ].forEach((plugin) => app.use(plugin)); /** * Global directives. */ import Debounce from "./directives/debounce"; import DOMPurify from "./directives/dompurify"; import ToolTip from "./directives/tooltip"; app.directive("debounce", Debounce); app.directive("safe-html", DOMPurify); app.directive("tooltip", ToolTip); export default app; ================================================ FILE: packages/Webkul/Admin/src/Resources/assets/js/chart.js ================================================ /** * Skipped minification because the original files appears to be already minified. * Original file: /npm/chart.js@4.4.0/dist/chart.umd.js * * Do NOT use SRI with dynamically generated files! More information: https://www.jsdelivr.com/using-sri-with-dynamic-files */ /*! * Chart.js v4.4.0 * https://www.chartjs.org * (c) 2023 Chart.js Contributors * Released under the MIT License */ !function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t="undefined"!=typeof globalThis?globalThis:t||self).Chart=e()}(this,(function(){"use strict";var t=Object.freeze({__proto__:null,get Colors(){return Go},get Decimation(){return Qo},get Filler(){return ma},get Legend(){return ya},get SubTitle(){return ka},get Title(){return Ma},get Tooltip(){return Ba}});function e(){}const i=(()=>{let t=0;return()=>t++})();function s(t){return null==t}function n(t){if(Array.isArray&&Array.isArray(t))return!0;const e=Object.prototype.toString.call(t);return"[object"===e.slice(0,7)&&"Array]"===e.slice(-6)}function o(t){return null!==t&&"[object Object]"===Object.prototype.toString.call(t)}function a(t){return("number"==typeof t||t instanceof Number)&&isFinite(+t)}function r(t,e){return a(t)?t:e}function l(t,e){return void 0===t?e:t}const h=(t,e)=>"string"==typeof t&&t.endsWith("%")?parseFloat(t)/100:+t/e,c=(t,e)=>"string"==typeof t&&t.endsWith("%")?parseFloat(t)/100*e:+t;function d(t,e,i){if(t&&"function"==typeof t.call)return t.apply(i,e)}function u(t,e,i,s){let a,r,l;if(n(t))if(r=t.length,s)for(a=r-1;a>=0;a--)e.call(i,t[a],a);else for(a=0;at,x:t=>t.x,y:t=>t.y};function v(t){const e=t.split("."),i=[];let s="";for(const t of e)s+=t,s.endsWith("\\")?s=s.slice(0,-1)+".":(i.push(s),s="");return i}function M(t,e){const i=y[e]||(y[e]=function(t){const e=v(t);return t=>{for(const i of e){if(""===i)break;t=t&&t[i]}return t}}(e));return i(t)}function w(t){return t.charAt(0).toUpperCase()+t.slice(1)}const k=t=>void 0!==t,S=t=>"function"==typeof t,P=(t,e)=>{if(t.size!==e.size)return!1;for(const i of t)if(!e.has(i))return!1;return!0};function D(t){return"mouseup"===t.type||"click"===t.type||"contextmenu"===t.type}const C=Math.PI,O=2*C,A=O+C,T=Number.POSITIVE_INFINITY,L=C/180,E=C/2,R=C/4,I=2*C/3,z=Math.log10,F=Math.sign;function V(t,e,i){return Math.abs(t-e)t-e)).pop(),e}function N(t){return!isNaN(parseFloat(t))&&isFinite(t)}function H(t,e){const i=Math.round(t);return i-e<=t&&i+e>=t}function j(t,e,i){let s,n,o;for(s=0,n=t.length;sl&&h=Math.min(e,i)-s&&t<=Math.max(e,i)+s}function et(t,e,i){i=i||(i=>t[i]1;)s=o+n>>1,i(s)?o=s:n=s;return{lo:o,hi:n}}const it=(t,e,i,s)=>et(t,i,s?s=>{const n=t[s][e];return nt[s][e]et(t,i,(s=>t[s][e]>=i));function nt(t,e,i){let s=0,n=t.length;for(;ss&&t[n-1]>i;)n--;return s>0||n{const i="_onData"+w(e),s=t[e];Object.defineProperty(t,e,{configurable:!0,enumerable:!1,value(...e){const n=s.apply(this,e);return t._chartjs.listeners.forEach((t=>{"function"==typeof t[i]&&t[i](...e)})),n}})})))}function rt(t,e){const i=t._chartjs;if(!i)return;const s=i.listeners,n=s.indexOf(e);-1!==n&&s.splice(n,1),s.length>0||(ot.forEach((e=>{delete t[e]})),delete t._chartjs)}function lt(t){const e=new Set(t);return e.size===t.length?t:Array.from(e)}const ht="undefined"==typeof window?function(t){return t()}:window.requestAnimationFrame;function ct(t,e){let i=[],s=!1;return function(...n){i=n,s||(s=!0,ht.call(window,(()=>{s=!1,t.apply(e,i)})))}}function dt(t,e){let i;return function(...s){return e?(clearTimeout(i),i=setTimeout(t,e,s)):t.apply(this,s),e}}const ut=t=>"start"===t?"left":"end"===t?"right":"center",ft=(t,e,i)=>"start"===t?e:"end"===t?i:(e+i)/2,gt=(t,e,i,s)=>t===(s?"left":"right")?i:"center"===t?(e+i)/2:e;function pt(t,e,i){const s=e.length;let n=0,o=s;if(t._sorted){const{iScale:a,_parsed:r}=t,l=a.axis,{min:h,max:c,minDefined:d,maxDefined:u}=a.getUserBounds();d&&(n=J(Math.min(it(r,l,h).lo,i?s:it(e,l,a.getPixelForValue(h)).lo),0,s-1)),o=u?J(Math.max(it(r,a.axis,c,!0).hi+1,i?0:it(e,l,a.getPixelForValue(c),!0).hi+1),n,s)-n:s-n}return{start:n,count:o}}function mt(t){const{xScale:e,yScale:i,_scaleRanges:s}=t,n={xmin:e.min,xmax:e.max,ymin:i.min,ymax:i.max};if(!s)return t._scaleRanges=n,!0;const o=s.xmin!==e.min||s.xmax!==e.max||s.ymin!==i.min||s.ymax!==i.max;return Object.assign(s,n),o}class bt{constructor(){this._request=null,this._charts=new Map,this._running=!1,this._lastDate=void 0}_notify(t,e,i,s){const n=e.listeners[s],o=e.duration;n.forEach((s=>s({chart:t,initial:e.initial,numSteps:o,currentStep:Math.min(i-e.start,o)})))}_refresh(){this._request||(this._running=!0,this._request=ht.call(window,(()=>{this._update(),this._request=null,this._running&&this._refresh()})))}_update(t=Date.now()){let e=0;this._charts.forEach(((i,s)=>{if(!i.running||!i.items.length)return;const n=i.items;let o,a=n.length-1,r=!1;for(;a>=0;--a)o=n[a],o._active?(o._total>i.duration&&(i.duration=o._total),o.tick(t),r=!0):(n[a]=n[n.length-1],n.pop());r&&(s.draw(),this._notify(s,i,t,"progress")),n.length||(i.running=!1,this._notify(s,i,t,"complete"),i.initial=!1),e+=n.length})),this._lastDate=t,0===e&&(this._running=!1)}_getAnims(t){const e=this._charts;let i=e.get(t);return i||(i={running:!1,initial:!0,items:[],listeners:{complete:[],progress:[]}},e.set(t,i)),i}listen(t,e,i){this._getAnims(t).listeners[e].push(i)}add(t,e){e&&e.length&&this._getAnims(t).items.push(...e)}has(t){return this._getAnims(t).items.length>0}start(t){const e=this._charts.get(t);e&&(e.running=!0,e.start=Date.now(),e.duration=e.items.reduce(((t,e)=>Math.max(t,e._duration)),0),this._refresh())}running(t){if(!this._running)return!1;const e=this._charts.get(t);return!!(e&&e.running&&e.items.length)}stop(t){const e=this._charts.get(t);if(!e||!e.items.length)return;const i=e.items;let s=i.length-1;for(;s>=0;--s)i[s].cancel();e.items=[],this._notify(t,e,Date.now(),"complete")}remove(t){return this._charts.delete(t)}}var xt=new bt; /*! * @kurkle/color v0.3.2 * https://github.com/kurkle/color#readme * (c) 2023 Jukka Kurkela * Released under the MIT License */function _t(t){return t+.5|0}const yt=(t,e,i)=>Math.max(Math.min(t,i),e);function vt(t){return yt(_t(2.55*t),0,255)}function Mt(t){return yt(_t(255*t),0,255)}function wt(t){return yt(_t(t/2.55)/100,0,1)}function kt(t){return yt(_t(100*t),0,100)}const St={0:0,1:1,2:2,3:3,4:4,5:5,6:6,7:7,8:8,9:9,A:10,B:11,C:12,D:13,E:14,F:15,a:10,b:11,c:12,d:13,e:14,f:15},Pt=[..."0123456789ABCDEF"],Dt=t=>Pt[15&t],Ct=t=>Pt[(240&t)>>4]+Pt[15&t],Ot=t=>(240&t)>>4==(15&t);function At(t){var e=(t=>Ot(t.r)&&Ot(t.g)&&Ot(t.b)&&Ot(t.a))(t)?Dt:Ct;return t?"#"+e(t.r)+e(t.g)+e(t.b)+((t,e)=>t<255?e(t):"")(t.a,e):void 0}const Tt=/^(hsla?|hwb|hsv)\(\s*([-+.e\d]+)(?:deg)?[\s,]+([-+.e\d]+)%[\s,]+([-+.e\d]+)%(?:[\s,]+([-+.e\d]+)(%)?)?\s*\)$/;function Lt(t,e,i){const s=e*Math.min(i,1-i),n=(e,n=(e+t/30)%12)=>i-s*Math.max(Math.min(n-3,9-n,1),-1);return[n(0),n(8),n(4)]}function Et(t,e,i){const s=(s,n=(s+t/60)%6)=>i-i*e*Math.max(Math.min(n,4-n,1),0);return[s(5),s(3),s(1)]}function Rt(t,e,i){const s=Lt(t,1,.5);let n;for(e+i>1&&(n=1/(e+i),e*=n,i*=n),n=0;n<3;n++)s[n]*=1-e-i,s[n]+=e;return s}function It(t){const e=t.r/255,i=t.g/255,s=t.b/255,n=Math.max(e,i,s),o=Math.min(e,i,s),a=(n+o)/2;let r,l,h;return n!==o&&(h=n-o,l=a>.5?h/(2-n-o):h/(n+o),r=function(t,e,i,s,n){return t===n?(e-i)/s+(e>16&255,o>>8&255,255&o]}return t}(),Ht.transparent=[0,0,0,0]);const e=Ht[t.toLowerCase()];return e&&{r:e[0],g:e[1],b:e[2],a:4===e.length?e[3]:255}}const $t=/^rgba?\(\s*([-+.\d]+)(%)?[\s,]+([-+.e\d]+)(%)?[\s,]+([-+.e\d]+)(%)?(?:[\s,/]+([-+.e\d]+)(%)?)?\s*\)$/;const Yt=t=>t<=.0031308?12.92*t:1.055*Math.pow(t,1/2.4)-.055,Ut=t=>t<=.04045?t/12.92:Math.pow((t+.055)/1.055,2.4);function Xt(t,e,i){if(t){let s=It(t);s[e]=Math.max(0,Math.min(s[e]+s[e]*i,0===e?360:1)),s=Ft(s),t.r=s[0],t.g=s[1],t.b=s[2]}}function qt(t,e){return t?Object.assign(e||{},t):t}function Kt(t){var e={r:0,g:0,b:0,a:255};return Array.isArray(t)?t.length>=3&&(e={r:t[0],g:t[1],b:t[2],a:255},t.length>3&&(e.a=Mt(t[3]))):(e=qt(t,{r:0,g:0,b:0,a:1})).a=Mt(e.a),e}function Gt(t){return"r"===t.charAt(0)?function(t){const e=$t.exec(t);let i,s,n,o=255;if(e){if(e[7]!==i){const t=+e[7];o=e[8]?vt(t):yt(255*t,0,255)}return i=+e[1],s=+e[3],n=+e[5],i=255&(e[2]?vt(i):yt(i,0,255)),s=255&(e[4]?vt(s):yt(s,0,255)),n=255&(e[6]?vt(n):yt(n,0,255)),{r:i,g:s,b:n,a:o}}}(t):Bt(t)}class Zt{constructor(t){if(t instanceof Zt)return t;const e=typeof t;let i;var s,n,o;"object"===e?i=Kt(t):"string"===e&&(o=(s=t).length,"#"===s[0]&&(4===o||5===o?n={r:255&17*St[s[1]],g:255&17*St[s[2]],b:255&17*St[s[3]],a:5===o?17*St[s[4]]:255}:7!==o&&9!==o||(n={r:St[s[1]]<<4|St[s[2]],g:St[s[3]]<<4|St[s[4]],b:St[s[5]]<<4|St[s[6]],a:9===o?St[s[7]]<<4|St[s[8]]:255})),i=n||jt(t)||Gt(t)),this._rgb=i,this._valid=!!i}get valid(){return this._valid}get rgb(){var t=qt(this._rgb);return t&&(t.a=wt(t.a)),t}set rgb(t){this._rgb=Kt(t)}rgbString(){return this._valid?(t=this._rgb)&&(t.a<255?`rgba(${t.r}, ${t.g}, ${t.b}, ${wt(t.a)})`:`rgb(${t.r}, ${t.g}, ${t.b})`):void 0;var t}hexString(){return this._valid?At(this._rgb):void 0}hslString(){return this._valid?function(t){if(!t)return;const e=It(t),i=e[0],s=kt(e[1]),n=kt(e[2]);return t.a<255?`hsla(${i}, ${s}%, ${n}%, ${wt(t.a)})`:`hsl(${i}, ${s}%, ${n}%)`}(this._rgb):void 0}mix(t,e){if(t){const i=this.rgb,s=t.rgb;let n;const o=e===n?.5:e,a=2*o-1,r=i.a-s.a,l=((a*r==-1?a:(a+r)/(1+a*r))+1)/2;n=1-l,i.r=255&l*i.r+n*s.r+.5,i.g=255&l*i.g+n*s.g+.5,i.b=255&l*i.b+n*s.b+.5,i.a=o*i.a+(1-o)*s.a,this.rgb=i}return this}interpolate(t,e){return t&&(this._rgb=function(t,e,i){const s=Ut(wt(t.r)),n=Ut(wt(t.g)),o=Ut(wt(t.b));return{r:Mt(Yt(s+i*(Ut(wt(e.r))-s))),g:Mt(Yt(n+i*(Ut(wt(e.g))-n))),b:Mt(Yt(o+i*(Ut(wt(e.b))-o))),a:t.a+i*(e.a-t.a)}}(this._rgb,t._rgb,e)),this}clone(){return new Zt(this.rgb)}alpha(t){return this._rgb.a=Mt(t),this}clearer(t){return this._rgb.a*=1-t,this}greyscale(){const t=this._rgb,e=_t(.3*t.r+.59*t.g+.11*t.b);return t.r=t.g=t.b=e,this}opaquer(t){return this._rgb.a*=1+t,this}negate(){const t=this._rgb;return t.r=255-t.r,t.g=255-t.g,t.b=255-t.b,this}lighten(t){return Xt(this._rgb,2,t),this}darken(t){return Xt(this._rgb,2,-t),this}saturate(t){return Xt(this._rgb,1,t),this}desaturate(t){return Xt(this._rgb,1,-t),this}rotate(t){return function(t,e){var i=It(t);i[0]=Vt(i[0]+e),i=Ft(i),t.r=i[0],t.g=i[1],t.b=i[2]}(this._rgb,t),this}}function Jt(t){if(t&&"object"==typeof t){const e=t.toString();return"[object CanvasPattern]"===e||"[object CanvasGradient]"===e}return!1}function Qt(t){return Jt(t)?t:new Zt(t)}function te(t){return Jt(t)?t:new Zt(t).saturate(.5).darken(.1).hexString()}const ee=["x","y","borderWidth","radius","tension"],ie=["color","borderColor","backgroundColor"];const se=new Map;function ne(t,e,i){return function(t,e){e=e||{};const i=t+JSON.stringify(e);let s=se.get(i);return s||(s=new Intl.NumberFormat(t,e),se.set(i,s)),s}(e,i).format(t)}const oe={values:t=>n(t)?t:""+t,numeric(t,e,i){if(0===t)return"0";const s=this.chart.options.locale;let n,o=t;if(i.length>1){const e=Math.max(Math.abs(i[0].value),Math.abs(i[i.length-1].value));(e<1e-4||e>1e15)&&(n="scientific"),o=function(t,e){let i=e.length>3?e[2].value-e[1].value:e[1].value-e[0].value;Math.abs(i)>=1&&t!==Math.floor(t)&&(i=t-Math.floor(t));return i}(t,i)}const a=z(Math.abs(o)),r=isNaN(a)?1:Math.max(Math.min(-1*Math.floor(a),20),0),l={notation:n,minimumFractionDigits:r,maximumFractionDigits:r};return Object.assign(l,this.options.ticks.format),ne(t,s,l)},logarithmic(t,e,i){if(0===t)return"0";const s=i[e].significand||t/Math.pow(10,Math.floor(z(t)));return[1,2,3,5,10,15].includes(s)||e>.8*i.length?oe.numeric.call(this,t,e,i):""}};var ae={formatters:oe};const re=Object.create(null),le=Object.create(null);function he(t,e){if(!e)return t;const i=e.split(".");for(let e=0,s=i.length;et.chart.platform.getDevicePixelRatio(),this.elements={},this.events=["mousemove","mouseout","click","touchstart","touchmove"],this.font={family:"'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",size:12,style:"normal",lineHeight:1.2,weight:null},this.hover={},this.hoverBackgroundColor=(t,e)=>te(e.backgroundColor),this.hoverBorderColor=(t,e)=>te(e.borderColor),this.hoverColor=(t,e)=>te(e.color),this.indexAxis="x",this.interaction={mode:"nearest",intersect:!0,includeInvisible:!1},this.maintainAspectRatio=!0,this.onHover=null,this.onClick=null,this.parsing=!0,this.plugins={},this.responsive=!0,this.scale=void 0,this.scales={},this.showLine=!0,this.drawActiveElementsOnTop=!0,this.describe(t),this.apply(e)}set(t,e){return ce(this,t,e)}get(t){return he(this,t)}describe(t,e){return ce(le,t,e)}override(t,e){return ce(re,t,e)}route(t,e,i,s){const n=he(this,t),a=he(this,i),r="_"+e;Object.defineProperties(n,{[r]:{value:n[e],writable:!0},[e]:{enumerable:!0,get(){const t=this[r],e=a[s];return o(t)?Object.assign({},e,t):l(t,e)},set(t){this[r]=t}}})}apply(t){t.forEach((t=>t(this)))}}var ue=new de({_scriptable:t=>!t.startsWith("on"),_indexable:t=>"events"!==t,hover:{_fallback:"interaction"},interaction:{_scriptable:!1,_indexable:!1}},[function(t){t.set("animation",{delay:void 0,duration:1e3,easing:"easeOutQuart",fn:void 0,from:void 0,loop:void 0,to:void 0,type:void 0}),t.describe("animation",{_fallback:!1,_indexable:!1,_scriptable:t=>"onProgress"!==t&&"onComplete"!==t&&"fn"!==t}),t.set("animations",{colors:{type:"color",properties:ie},numbers:{type:"number",properties:ee}}),t.describe("animations",{_fallback:"animation"}),t.set("transitions",{active:{animation:{duration:400}},resize:{animation:{duration:0}},show:{animations:{colors:{from:"transparent"},visible:{type:"boolean",duration:0}}},hide:{animations:{colors:{to:"transparent"},visible:{type:"boolean",easing:"linear",fn:t=>0|t}}}})},function(t){t.set("layout",{autoPadding:!0,padding:{top:0,right:0,bottom:0,left:0}})},function(t){t.set("scale",{display:!0,offset:!1,reverse:!1,beginAtZero:!1,bounds:"ticks",clip:!0,grace:0,grid:{display:!0,lineWidth:1,drawOnChartArea:!0,drawTicks:!0,tickLength:8,tickWidth:(t,e)=>e.lineWidth,tickColor:(t,e)=>e.color,offset:!1},border:{display:!0,dash:[],dashOffset:0,width:1},title:{display:!1,text:"",padding:{top:4,bottom:4}},ticks:{minRotation:0,maxRotation:50,mirror:!1,textStrokeWidth:0,textStrokeColor:"",padding:3,display:!0,autoSkip:!0,autoSkipPadding:3,labelOffset:0,callback:ae.formatters.values,minor:{},major:{},align:"center",crossAlign:"near",showLabelBackdrop:!1,backdropColor:"rgba(255, 255, 255, 0.75)",backdropPadding:2}}),t.route("scale.ticks","color","","color"),t.route("scale.grid","color","","borderColor"),t.route("scale.border","color","","borderColor"),t.route("scale.title","color","","color"),t.describe("scale",{_fallback:!1,_scriptable:t=>!t.startsWith("before")&&!t.startsWith("after")&&"callback"!==t&&"parser"!==t,_indexable:t=>"borderDash"!==t&&"tickBorderDash"!==t&&"dash"!==t}),t.describe("scales",{_fallback:"scale"}),t.describe("scale.ticks",{_scriptable:t=>"backdropPadding"!==t&&"callback"!==t,_indexable:t=>"backdropPadding"!==t})}]);function fe(){return"undefined"!=typeof window&&"undefined"!=typeof document}function ge(t){let e=t.parentNode;return e&&"[object ShadowRoot]"===e.toString()&&(e=e.host),e}function pe(t,e,i){let s;return"string"==typeof t?(s=parseInt(t,10),-1!==t.indexOf("%")&&(s=s/100*e.parentNode[i])):s=t,s}const me=t=>t.ownerDocument.defaultView.getComputedStyle(t,null);function be(t,e){return me(t).getPropertyValue(e)}const xe=["top","right","bottom","left"];function _e(t,e,i){const s={};i=i?"-"+i:"";for(let n=0;n<4;n++){const o=xe[n];s[o]=parseFloat(t[e+"-"+o+i])||0}return s.width=s.left+s.right,s.height=s.top+s.bottom,s}const ye=(t,e,i)=>(t>0||e>0)&&(!i||!i.shadowRoot);function ve(t,e){if("native"in t)return t;const{canvas:i,currentDevicePixelRatio:s}=e,n=me(i),o="border-box"===n.boxSizing,a=_e(n,"padding"),r=_e(n,"border","width"),{x:l,y:h,box:c}=function(t,e){const i=t.touches,s=i&&i.length?i[0]:t,{offsetX:n,offsetY:o}=s;let a,r,l=!1;if(ye(n,o,t.target))a=n,r=o;else{const t=e.getBoundingClientRect();a=s.clientX-t.left,r=s.clientY-t.top,l=!0}return{x:a,y:r,box:l}}(t,i),d=a.left+(c&&r.left),u=a.top+(c&&r.top);let{width:f,height:g}=e;return o&&(f-=a.width+r.width,g-=a.height+r.height),{x:Math.round((l-d)/f*i.width/s),y:Math.round((h-u)/g*i.height/s)}}const Me=t=>Math.round(10*t)/10;function we(t,e,i,s){const n=me(t),o=_e(n,"margin"),a=pe(n.maxWidth,t,"clientWidth")||T,r=pe(n.maxHeight,t,"clientHeight")||T,l=function(t,e,i){let s,n;if(void 0===e||void 0===i){const o=ge(t);if(o){const t=o.getBoundingClientRect(),a=me(o),r=_e(a,"border","width"),l=_e(a,"padding");e=t.width-l.width-r.width,i=t.height-l.height-r.height,s=pe(a.maxWidth,o,"clientWidth"),n=pe(a.maxHeight,o,"clientHeight")}else e=t.clientWidth,i=t.clientHeight}return{width:e,height:i,maxWidth:s||T,maxHeight:n||T}}(t,e,i);let{width:h,height:c}=l;if("content-box"===n.boxSizing){const t=_e(n,"border","width"),e=_e(n,"padding");h-=e.width+t.width,c-=e.height+t.height}h=Math.max(0,h-o.width),c=Math.max(0,s?h/s:c-o.height),h=Me(Math.min(h,a,l.maxWidth)),c=Me(Math.min(c,r,l.maxHeight)),h&&!c&&(c=Me(h/2));return(void 0!==e||void 0!==i)&&s&&l.height&&c>l.height&&(c=l.height,h=Me(Math.floor(c*s))),{width:h,height:c}}function ke(t,e,i){const s=e||1,n=Math.floor(t.height*s),o=Math.floor(t.width*s);t.height=Math.floor(t.height),t.width=Math.floor(t.width);const a=t.canvas;return a.style&&(i||!a.style.height&&!a.style.width)&&(a.style.height=`${t.height}px`,a.style.width=`${t.width}px`),(t.currentDevicePixelRatio!==s||a.height!==n||a.width!==o)&&(t.currentDevicePixelRatio=s,a.height=n,a.width=o,t.ctx.setTransform(s,0,0,s,0,0),!0)}const Se=function(){let t=!1;try{const e={get passive(){return t=!0,!1}};window.addEventListener("test",null,e),window.removeEventListener("test",null,e)}catch(t){}return t}();function Pe(t,e){const i=be(t,e),s=i&&i.match(/^(\d+)(\.\d+)?px$/);return s?+s[1]:void 0}function De(t){return!t||s(t.size)||s(t.family)?null:(t.style?t.style+" ":"")+(t.weight?t.weight+" ":"")+t.size+"px "+t.family}function Ce(t,e,i,s,n){let o=e[n];return o||(o=e[n]=t.measureText(n).width,i.push(n)),o>s&&(s=o),s}function Oe(t,e,i,s){let o=(s=s||{}).data=s.data||{},a=s.garbageCollect=s.garbageCollect||[];s.font!==e&&(o=s.data={},a=s.garbageCollect=[],s.font=e),t.save(),t.font=e;let r=0;const l=i.length;let h,c,d,u,f;for(h=0;hi.length){for(h=0;h0&&t.stroke()}}function Re(t,e,i){return i=i||.5,!e||t&&t.x>e.left-i&&t.xe.top-i&&t.y0&&""!==r.strokeColor;let c,d;for(t.save(),t.font=a.string,function(t,e){e.translation&&t.translate(e.translation[0],e.translation[1]),s(e.rotation)||t.rotate(e.rotation),e.color&&(t.fillStyle=e.color),e.textAlign&&(t.textAlign=e.textAlign),e.textBaseline&&(t.textBaseline=e.textBaseline)}(t,r),c=0;ct[0])){const o=i||t;void 0===s&&(s=ti("_fallback",t));const a={[Symbol.toStringTag]:"Object",_cacheable:!0,_scopes:t,_rootScopes:o,_fallback:s,_getTarget:n,override:i=>je([i,...t],e,o,s)};return new Proxy(a,{deleteProperty:(e,i)=>(delete e[i],delete e._keys,delete t[0][i],!0),get:(i,s)=>qe(i,s,(()=>function(t,e,i,s){let n;for(const o of e)if(n=ti(Ue(o,t),i),void 0!==n)return Xe(t,n)?Je(i,s,t,n):n}(s,e,t,i))),getOwnPropertyDescriptor:(t,e)=>Reflect.getOwnPropertyDescriptor(t._scopes[0],e),getPrototypeOf:()=>Reflect.getPrototypeOf(t[0]),has:(t,e)=>ei(t).includes(e),ownKeys:t=>ei(t),set(t,e,i){const s=t._storage||(t._storage=n());return t[e]=s[e]=i,delete t._keys,!0}})}function $e(t,e,i,s){const a={_cacheable:!1,_proxy:t,_context:e,_subProxy:i,_stack:new Set,_descriptors:Ye(t,s),setContext:e=>$e(t,e,i,s),override:n=>$e(t.override(n),e,i,s)};return new Proxy(a,{deleteProperty:(e,i)=>(delete e[i],delete t[i],!0),get:(t,e,i)=>qe(t,e,(()=>function(t,e,i){const{_proxy:s,_context:a,_subProxy:r,_descriptors:l}=t;let h=s[e];S(h)&&l.isScriptable(e)&&(h=function(t,e,i,s){const{_proxy:n,_context:o,_subProxy:a,_stack:r}=i;if(r.has(t))throw new Error("Recursion detected: "+Array.from(r).join("->")+"->"+t);r.add(t);let l=e(o,a||s);r.delete(t),Xe(t,l)&&(l=Je(n._scopes,n,t,l));return l}(e,h,t,i));n(h)&&h.length&&(h=function(t,e,i,s){const{_proxy:n,_context:a,_subProxy:r,_descriptors:l}=i;if(void 0!==a.index&&s(t))return e[a.index%e.length];if(o(e[0])){const i=e,s=n._scopes.filter((t=>t!==i));e=[];for(const o of i){const i=Je(s,n,t,o);e.push($e(i,a,r&&r[t],l))}}return e}(e,h,t,l.isIndexable));Xe(e,h)&&(h=$e(h,a,r&&r[e],l));return h}(t,e,i))),getOwnPropertyDescriptor:(e,i)=>e._descriptors.allKeys?Reflect.has(t,i)?{enumerable:!0,configurable:!0}:void 0:Reflect.getOwnPropertyDescriptor(t,i),getPrototypeOf:()=>Reflect.getPrototypeOf(t),has:(e,i)=>Reflect.has(t,i),ownKeys:()=>Reflect.ownKeys(t),set:(e,i,s)=>(t[i]=s,delete e[i],!0)})}function Ye(t,e={scriptable:!0,indexable:!0}){const{_scriptable:i=e.scriptable,_indexable:s=e.indexable,_allKeys:n=e.allKeys}=t;return{allKeys:n,scriptable:i,indexable:s,isScriptable:S(i)?i:()=>i,isIndexable:S(s)?s:()=>s}}const Ue=(t,e)=>t?t+w(e):e,Xe=(t,e)=>o(e)&&"adapters"!==t&&(null===Object.getPrototypeOf(e)||e.constructor===Object);function qe(t,e,i){if(Object.prototype.hasOwnProperty.call(t,e))return t[e];const s=i();return t[e]=s,s}function Ke(t,e,i){return S(t)?t(e,i):t}const Ge=(t,e)=>!0===t?e:"string"==typeof t?M(e,t):void 0;function Ze(t,e,i,s,n){for(const o of e){const e=Ge(i,o);if(e){t.add(e);const o=Ke(e._fallback,i,n);if(void 0!==o&&o!==i&&o!==s)return o}else if(!1===e&&void 0!==s&&i!==s)return null}return!1}function Je(t,e,i,s){const a=e._rootScopes,r=Ke(e._fallback,i,s),l=[...t,...a],h=new Set;h.add(s);let c=Qe(h,l,i,r||i,s);return null!==c&&((void 0===r||r===i||(c=Qe(h,l,r,c,s),null!==c))&&je(Array.from(h),[""],a,r,(()=>function(t,e,i){const s=t._getTarget();e in s||(s[e]={});const a=s[e];if(n(a)&&o(i))return i;return a||{}}(e,i,s))))}function Qe(t,e,i,s,n){for(;i;)i=Ze(t,e,i,s,n);return i}function ti(t,e){for(const i of e){if(!i)continue;const e=i[t];if(void 0!==e)return e}}function ei(t){let e=t._keys;return e||(e=t._keys=function(t){const e=new Set;for(const i of t)for(const t of Object.keys(i).filter((t=>!t.startsWith("_"))))e.add(t);return Array.from(e)}(t._scopes)),e}function ii(t,e,i,s){const{iScale:n}=t,{key:o="r"}=this._parsing,a=new Array(s);let r,l,h,c;for(r=0,l=s;re"x"===t?"y":"x";function ai(t,e,i,s){const n=t.skip?e:t,o=e,a=i.skip?e:i,r=q(o,n),l=q(a,o);let h=r/(r+l),c=l/(r+l);h=isNaN(h)?0:h,c=isNaN(c)?0:c;const d=s*h,u=s*c;return{previous:{x:o.x-d*(a.x-n.x),y:o.y-d*(a.y-n.y)},next:{x:o.x+u*(a.x-n.x),y:o.y+u*(a.y-n.y)}}}function ri(t,e="x"){const i=oi(e),s=t.length,n=Array(s).fill(0),o=Array(s);let a,r,l,h=ni(t,0);for(a=0;a!t.skip))),"monotone"===e.cubicInterpolationMode)ri(t,n);else{let i=s?t[t.length-1]:t[0];for(o=0,a=t.length;o0===t||1===t,di=(t,e,i)=>-Math.pow(2,10*(t-=1))*Math.sin((t-e)*O/i),ui=(t,e,i)=>Math.pow(2,-10*t)*Math.sin((t-e)*O/i)+1,fi={linear:t=>t,easeInQuad:t=>t*t,easeOutQuad:t=>-t*(t-2),easeInOutQuad:t=>(t/=.5)<1?.5*t*t:-.5*(--t*(t-2)-1),easeInCubic:t=>t*t*t,easeOutCubic:t=>(t-=1)*t*t+1,easeInOutCubic:t=>(t/=.5)<1?.5*t*t*t:.5*((t-=2)*t*t+2),easeInQuart:t=>t*t*t*t,easeOutQuart:t=>-((t-=1)*t*t*t-1),easeInOutQuart:t=>(t/=.5)<1?.5*t*t*t*t:-.5*((t-=2)*t*t*t-2),easeInQuint:t=>t*t*t*t*t,easeOutQuint:t=>(t-=1)*t*t*t*t+1,easeInOutQuint:t=>(t/=.5)<1?.5*t*t*t*t*t:.5*((t-=2)*t*t*t*t+2),easeInSine:t=>1-Math.cos(t*E),easeOutSine:t=>Math.sin(t*E),easeInOutSine:t=>-.5*(Math.cos(C*t)-1),easeInExpo:t=>0===t?0:Math.pow(2,10*(t-1)),easeOutExpo:t=>1===t?1:1-Math.pow(2,-10*t),easeInOutExpo:t=>ci(t)?t:t<.5?.5*Math.pow(2,10*(2*t-1)):.5*(2-Math.pow(2,-10*(2*t-1))),easeInCirc:t=>t>=1?t:-(Math.sqrt(1-t*t)-1),easeOutCirc:t=>Math.sqrt(1-(t-=1)*t),easeInOutCirc:t=>(t/=.5)<1?-.5*(Math.sqrt(1-t*t)-1):.5*(Math.sqrt(1-(t-=2)*t)+1),easeInElastic:t=>ci(t)?t:di(t,.075,.3),easeOutElastic:t=>ci(t)?t:ui(t,.075,.3),easeInOutElastic(t){const e=.1125;return ci(t)?t:t<.5?.5*di(2*t,e,.45):.5+.5*ui(2*t-1,e,.45)},easeInBack(t){const e=1.70158;return t*t*((e+1)*t-e)},easeOutBack(t){const e=1.70158;return(t-=1)*t*((e+1)*t+e)+1},easeInOutBack(t){let e=1.70158;return(t/=.5)<1?t*t*((1+(e*=1.525))*t-e)*.5:.5*((t-=2)*t*((1+(e*=1.525))*t+e)+2)},easeInBounce:t=>1-fi.easeOutBounce(1-t),easeOutBounce(t){const e=7.5625,i=2.75;return t<1/i?e*t*t:t<2/i?e*(t-=1.5/i)*t+.75:t<2.5/i?e*(t-=2.25/i)*t+.9375:e*(t-=2.625/i)*t+.984375},easeInOutBounce:t=>t<.5?.5*fi.easeInBounce(2*t):.5*fi.easeOutBounce(2*t-1)+.5};function gi(t,e,i,s){return{x:t.x+i*(e.x-t.x),y:t.y+i*(e.y-t.y)}}function pi(t,e,i,s){return{x:t.x+i*(e.x-t.x),y:"middle"===s?i<.5?t.y:e.y:"after"===s?i<1?t.y:e.y:i>0?e.y:t.y}}function mi(t,e,i,s){const n={x:t.cp2x,y:t.cp2y},o={x:e.cp1x,y:e.cp1y},a=gi(t,n,i),r=gi(n,o,i),l=gi(o,e,i),h=gi(a,r,i),c=gi(r,l,i);return gi(h,c,i)}const bi=/^(normal|(\d+(?:\.\d+)?)(px|em|%)?)$/,xi=/^(normal|italic|initial|inherit|unset|(oblique( -?[0-9]?[0-9]deg)?))$/;function _i(t,e){const i=(""+t).match(bi);if(!i||"normal"===i[1])return 1.2*e;switch(t=+i[2],i[3]){case"px":return t;case"%":t/=100}return e*t}const yi=t=>+t||0;function vi(t,e){const i={},s=o(e),n=s?Object.keys(e):e,a=o(t)?s?i=>l(t[i],t[e[i]]):e=>t[e]:()=>t;for(const t of n)i[t]=yi(a(t));return i}function Mi(t){return vi(t,{top:"y",right:"x",bottom:"y",left:"x"})}function wi(t){return vi(t,["topLeft","topRight","bottomLeft","bottomRight"])}function ki(t){const e=Mi(t);return e.width=e.left+e.right,e.height=e.top+e.bottom,e}function Si(t,e){t=t||{},e=e||ue.font;let i=l(t.size,e.size);"string"==typeof i&&(i=parseInt(i,10));let s=l(t.style,e.style);s&&!(""+s).match(xi)&&(console.warn('Invalid font style specified: "'+s+'"'),s=void 0);const n={family:l(t.family,e.family),lineHeight:_i(l(t.lineHeight,e.lineHeight),i),size:i,style:s,weight:l(t.weight,e.weight),string:""};return n.string=De(n),n}function Pi(t,e,i,s){let o,a,r,l=!0;for(o=0,a=t.length;oi&&0===t?0:t+e;return{min:a(s,-Math.abs(o)),max:a(n,o)}}function Ci(t,e){return Object.assign(Object.create(t),e)}function Oi(t,e,i){return t?function(t,e){return{x:i=>t+t+e-i,setWidth(t){e=t},textAlign:t=>"center"===t?t:"right"===t?"left":"right",xPlus:(t,e)=>t-e,leftForLtr:(t,e)=>t-e}}(e,i):{x:t=>t,setWidth(t){},textAlign:t=>t,xPlus:(t,e)=>t+e,leftForLtr:(t,e)=>t}}function Ai(t,e){let i,s;"ltr"!==e&&"rtl"!==e||(i=t.canvas.style,s=[i.getPropertyValue("direction"),i.getPropertyPriority("direction")],i.setProperty("direction",e,"important"),t.prevTextDirection=s)}function Ti(t,e){void 0!==e&&(delete t.prevTextDirection,t.canvas.style.setProperty("direction",e[0],e[1]))}function Li(t){return"angle"===t?{between:Z,compare:K,normalize:G}:{between:tt,compare:(t,e)=>t-e,normalize:t=>t}}function Ei({start:t,end:e,count:i,loop:s,style:n}){return{start:t%i,end:e%i,loop:s&&(e-t+1)%i==0,style:n}}function Ri(t,e,i){if(!i)return[t];const{property:s,start:n,end:o}=i,a=e.length,{compare:r,between:l,normalize:h}=Li(s),{start:c,end:d,loop:u,style:f}=function(t,e,i){const{property:s,start:n,end:o}=i,{between:a,normalize:r}=Li(s),l=e.length;let h,c,{start:d,end:u,loop:f}=t;if(f){for(d+=l,u+=l,h=0,c=l;hx||l(n,b,p)&&0!==r(n,b),v=()=>!x||0===r(o,p)||l(o,b,p);for(let t=c,i=c;t<=d;++t)m=e[t%a],m.skip||(p=h(m[s]),p!==b&&(x=l(p,n,o),null===_&&y()&&(_=0===r(p,n)?t:i),null!==_&&v()&&(g.push(Ei({start:_,end:t,loop:u,count:a,style:f})),_=null),i=t,b=p));return null!==_&&g.push(Ei({start:_,end:d,loop:u,count:a,style:f})),g}function Ii(t,e){const i=[],s=t.segments;for(let n=0;nn&&t[o%e].skip;)o--;return o%=e,{start:n,end:o}}(i,n,o,s);if(!0===s)return Fi(t,[{start:a,end:r,loop:o}],i,e);return Fi(t,function(t,e,i,s){const n=t.length,o=[];let a,r=e,l=t[e];for(a=e+1;a<=i;++a){const i=t[a%n];i.skip||i.stop?l.skip||(s=!1,o.push({start:e%n,end:(a-1)%n,loop:s}),e=r=i.stop?a:null):(r=a,l.skip&&(e=a)),l=i}return null!==r&&o.push({start:e%n,end:r%n,loop:s}),o}(i,a,r{t[a](e[i],n)&&(o.push({element:t,datasetIndex:s,index:l}),r=r||t.inRange(e.x,e.y,n))})),s&&!r?[]:o}var Xi={evaluateInteractionItems:Hi,modes:{index(t,e,i,s){const n=ve(e,t),o=i.axis||"x",a=i.includeInvisible||!1,r=i.intersect?ji(t,n,o,s,a):Yi(t,n,o,!1,s,a),l=[];return r.length?(t.getSortedVisibleDatasetMetas().forEach((t=>{const e=r[0].index,i=t.data[e];i&&!i.skip&&l.push({element:i,datasetIndex:t.index,index:e})})),l):[]},dataset(t,e,i,s){const n=ve(e,t),o=i.axis||"xy",a=i.includeInvisible||!1;let r=i.intersect?ji(t,n,o,s,a):Yi(t,n,o,!1,s,a);if(r.length>0){const e=r[0].datasetIndex,i=t.getDatasetMeta(e).data;r=[];for(let t=0;tji(t,ve(e,t),i.axis||"xy",s,i.includeInvisible||!1),nearest(t,e,i,s){const n=ve(e,t),o=i.axis||"xy",a=i.includeInvisible||!1;return Yi(t,n,o,i.intersect,s,a)},x:(t,e,i,s)=>Ui(t,ve(e,t),"x",i.intersect,s),y:(t,e,i,s)=>Ui(t,ve(e,t),"y",i.intersect,s)}};const qi=["left","top","right","bottom"];function Ki(t,e){return t.filter((t=>t.pos===e))}function Gi(t,e){return t.filter((t=>-1===qi.indexOf(t.pos)&&t.box.axis===e))}function Zi(t,e){return t.sort(((t,i)=>{const s=e?i:t,n=e?t:i;return s.weight===n.weight?s.index-n.index:s.weight-n.weight}))}function Ji(t,e){const i=function(t){const e={};for(const i of t){const{stack:t,pos:s,stackWeight:n}=i;if(!t||!qi.includes(s))continue;const o=e[t]||(e[t]={count:0,placed:0,weight:0,size:0});o.count++,o.weight+=n}return e}(t),{vBoxMaxWidth:s,hBoxMaxHeight:n}=e;let o,a,r;for(o=0,a=t.length;o{s[t]=Math.max(e[t],i[t])})),s}return s(t?["left","right"]:["top","bottom"])}function ss(t,e,i,s){const n=[];let o,a,r,l,h,c;for(o=0,a=t.length,h=0;ot.box.fullSize)),!0),s=Zi(Ki(e,"left"),!0),n=Zi(Ki(e,"right")),o=Zi(Ki(e,"top"),!0),a=Zi(Ki(e,"bottom")),r=Gi(e,"x"),l=Gi(e,"y");return{fullSize:i,leftAndTop:s.concat(o),rightAndBottom:n.concat(l).concat(a).concat(r),chartArea:Ki(e,"chartArea"),vertical:s.concat(n).concat(l),horizontal:o.concat(a).concat(r)}}(t.boxes),l=r.vertical,h=r.horizontal;u(t.boxes,(t=>{"function"==typeof t.beforeLayout&&t.beforeLayout()}));const c=l.reduce(((t,e)=>e.box.options&&!1===e.box.options.display?t:t+1),0)||1,d=Object.freeze({outerWidth:e,outerHeight:i,padding:n,availableWidth:o,availableHeight:a,vBoxMaxWidth:o/2/c,hBoxMaxHeight:a/2}),f=Object.assign({},n);ts(f,ki(s));const g=Object.assign({maxPadding:f,w:o,h:a,x:n.left,y:n.top},n),p=Ji(l.concat(h),d);ss(r.fullSize,g,d,p),ss(l,g,d,p),ss(h,g,d,p)&&ss(l,g,d,p),function(t){const e=t.maxPadding;function i(i){const s=Math.max(e[i]-t[i],0);return t[i]+=s,s}t.y+=i("top"),t.x+=i("left"),i("right"),i("bottom")}(g),os(r.leftAndTop,g,d,p),g.x+=g.w,g.y+=g.h,os(r.rightAndBottom,g,d,p),t.chartArea={left:g.left,top:g.top,right:g.left+g.w,bottom:g.top+g.h,height:g.h,width:g.w},u(r.chartArea,(e=>{const i=e.box;Object.assign(i,t.chartArea),i.update(g.w,g.h,{left:0,top:0,right:0,bottom:0})}))}};class rs{acquireContext(t,e){}releaseContext(t){return!1}addEventListener(t,e,i){}removeEventListener(t,e,i){}getDevicePixelRatio(){return 1}getMaximumSize(t,e,i,s){return e=Math.max(0,e||t.width),i=i||t.height,{width:e,height:Math.max(0,s?Math.floor(e/s):i)}}isAttached(t){return!0}updateConfig(t){}}class ls extends rs{acquireContext(t){return t&&t.getContext&&t.getContext("2d")||null}updateConfig(t){t.options.animation=!1}}const hs="$chartjs",cs={touchstart:"mousedown",touchmove:"mousemove",touchend:"mouseup",pointerenter:"mouseenter",pointerdown:"mousedown",pointermove:"mousemove",pointerup:"mouseup",pointerleave:"mouseout",pointerout:"mouseout"},ds=t=>null===t||""===t;const us=!!Se&&{passive:!0};function fs(t,e,i){t.canvas.removeEventListener(e,i,us)}function gs(t,e){for(const i of t)if(i===e||i.contains(e))return!0}function ps(t,e,i){const s=t.canvas,n=new MutationObserver((t=>{let e=!1;for(const i of t)e=e||gs(i.addedNodes,s),e=e&&!gs(i.removedNodes,s);e&&i()}));return n.observe(document,{childList:!0,subtree:!0}),n}function ms(t,e,i){const s=t.canvas,n=new MutationObserver((t=>{let e=!1;for(const i of t)e=e||gs(i.removedNodes,s),e=e&&!gs(i.addedNodes,s);e&&i()}));return n.observe(document,{childList:!0,subtree:!0}),n}const bs=new Map;let xs=0;function _s(){const t=window.devicePixelRatio;t!==xs&&(xs=t,bs.forEach(((e,i)=>{i.currentDevicePixelRatio!==t&&e()})))}function ys(t,e,i){const s=t.canvas,n=s&&ge(s);if(!n)return;const o=ct(((t,e)=>{const s=n.clientWidth;i(t,e),s{const e=t[0],i=e.contentRect.width,s=e.contentRect.height;0===i&&0===s||o(i,s)}));return a.observe(n),function(t,e){bs.size||window.addEventListener("resize",_s),bs.set(t,e)}(t,o),a}function vs(t,e,i){i&&i.disconnect(),"resize"===e&&function(t){bs.delete(t),bs.size||window.removeEventListener("resize",_s)}(t)}function Ms(t,e,i){const s=t.canvas,n=ct((e=>{null!==t.ctx&&i(function(t,e){const i=cs[t.type]||t.type,{x:s,y:n}=ve(t,e);return{type:i,chart:e,native:t,x:void 0!==s?s:null,y:void 0!==n?n:null}}(e,t))}),t);return function(t,e,i){t.addEventListener(e,i,us)}(s,e,n),n}class ws extends rs{acquireContext(t,e){const i=t&&t.getContext&&t.getContext("2d");return i&&i.canvas===t?(function(t,e){const i=t.style,s=t.getAttribute("height"),n=t.getAttribute("width");if(t[hs]={initial:{height:s,width:n,style:{display:i.display,height:i.height,width:i.width}}},i.display=i.display||"block",i.boxSizing=i.boxSizing||"border-box",ds(n)){const e=Pe(t,"width");void 0!==e&&(t.width=e)}if(ds(s))if(""===t.style.height)t.height=t.width/(e||2);else{const e=Pe(t,"height");void 0!==e&&(t.height=e)}}(t,e),i):null}releaseContext(t){const e=t.canvas;if(!e[hs])return!1;const i=e[hs].initial;["height","width"].forEach((t=>{const n=i[t];s(n)?e.removeAttribute(t):e.setAttribute(t,n)}));const n=i.style||{};return Object.keys(n).forEach((t=>{e.style[t]=n[t]})),e.width=e.width,delete e[hs],!0}addEventListener(t,e,i){this.removeEventListener(t,e);const s=t.$proxies||(t.$proxies={}),n={attach:ps,detach:ms,resize:ys}[e]||Ms;s[e]=n(t,e,i)}removeEventListener(t,e){const i=t.$proxies||(t.$proxies={}),s=i[e];if(!s)return;({attach:vs,detach:vs,resize:vs}[e]||fs)(t,e,s),i[e]=void 0}getDevicePixelRatio(){return window.devicePixelRatio}getMaximumSize(t,e,i,s){return we(t,e,i,s)}isAttached(t){const e=ge(t);return!(!e||!e.isConnected)}}function ks(t){return!fe()||"undefined"!=typeof OffscreenCanvas&&t instanceof OffscreenCanvas?ls:ws}var Ss=Object.freeze({__proto__:null,BasePlatform:rs,BasicPlatform:ls,DomPlatform:ws,_detectPlatform:ks});const Ps="transparent",Ds={boolean:(t,e,i)=>i>.5?e:t,color(t,e,i){const s=Qt(t||Ps),n=s.valid&&Qt(e||Ps);return n&&n.valid?n.mix(s,i).hexString():e},number:(t,e,i)=>t+(e-t)*i};class Cs{constructor(t,e,i,s){const n=e[i];s=Pi([t.to,s,n,t.from]);const o=Pi([t.from,n,s]);this._active=!0,this._fn=t.fn||Ds[t.type||typeof o],this._easing=fi[t.easing]||fi.linear,this._start=Math.floor(Date.now()+(t.delay||0)),this._duration=this._total=Math.floor(t.duration),this._loop=!!t.loop,this._target=e,this._prop=i,this._from=o,this._to=s,this._promises=void 0}active(){return this._active}update(t,e,i){if(this._active){this._notify(!1);const s=this._target[this._prop],n=i-this._start,o=this._duration-n;this._start=i,this._duration=Math.floor(Math.max(o,t.duration)),this._total+=n,this._loop=!!t.loop,this._to=Pi([t.to,e,s,t.from]),this._from=Pi([t.from,s,e])}}cancel(){this._active&&(this.tick(Date.now()),this._active=!1,this._notify(!1))}tick(t){const e=t-this._start,i=this._duration,s=this._prop,n=this._from,o=this._loop,a=this._to;let r;if(this._active=n!==a&&(o||e1?2-r:r,r=this._easing(Math.min(1,Math.max(0,r))),this._target[s]=this._fn(n,a,r))}wait(){const t=this._promises||(this._promises=[]);return new Promise(((e,i)=>{t.push({res:e,rej:i})}))}_notify(t){const e=t?"res":"rej",i=this._promises||[];for(let t=0;t{const a=t[s];if(!o(a))return;const r={};for(const t of e)r[t]=a[t];(n(a.properties)&&a.properties||[s]).forEach((t=>{t!==s&&i.has(t)||i.set(t,r)}))}))}_animateOptions(t,e){const i=e.options,s=function(t,e){if(!e)return;let i=t.options;if(!i)return void(t.options=e);i.$shared&&(t.options=i=Object.assign({},i,{$shared:!1,$animations:{}}));return i}(t,i);if(!s)return[];const n=this._createAnimations(s,i);return i.$shared&&function(t,e){const i=[],s=Object.keys(e);for(let e=0;e{t.options=i}),(()=>{})),n}_createAnimations(t,e){const i=this._properties,s=[],n=t.$animations||(t.$animations={}),o=Object.keys(e),a=Date.now();let r;for(r=o.length-1;r>=0;--r){const l=o[r];if("$"===l.charAt(0))continue;if("options"===l){s.push(...this._animateOptions(t,e));continue}const h=e[l];let c=n[l];const d=i.get(l);if(c){if(d&&c.active()){c.update(d,h,a);continue}c.cancel()}d&&d.duration?(n[l]=c=new Cs(d,t,l,h),s.push(c)):t[l]=h}return s}update(t,e){if(0===this._properties.size)return void Object.assign(t,e);const i=this._createAnimations(t,e);return i.length?(xt.add(this._chart,i),!0):void 0}}function As(t,e){const i=t&&t.options||{},s=i.reverse,n=void 0===i.min?e:0,o=void 0===i.max?e:0;return{start:s?o:n,end:s?n:o}}function Ts(t,e){const i=[],s=t._getSortedDatasetMetas(e);let n,o;for(n=0,o=s.length;n0||!i&&e<0)return n.index}return null}function zs(t,e){const{chart:i,_cachedMeta:s}=t,n=i._stacks||(i._stacks={}),{iScale:o,vScale:a,index:r}=s,l=o.axis,h=a.axis,c=function(t,e,i){return`${t.id}.${e.id}.${i.stack||i.type}`}(o,a,s),d=e.length;let u;for(let t=0;ti[t].axis===e)).shift()}function Vs(t,e){const i=t.controller.index,s=t.vScale&&t.vScale.axis;if(s){e=e||t._parsed;for(const t of e){const e=t._stacks;if(!e||void 0===e[s]||void 0===e[s][i])return;delete e[s][i],void 0!==e[s]._visualValues&&void 0!==e[s]._visualValues[i]&&delete e[s]._visualValues[i]}}}const Bs=t=>"reset"===t||"none"===t,Ws=(t,e)=>e?t:Object.assign({},t);class Ns{static defaults={};static datasetElementType=null;static dataElementType=null;constructor(t,e){this.chart=t,this._ctx=t.ctx,this.index=e,this._cachedDataOpts={},this._cachedMeta=this.getMeta(),this._type=this._cachedMeta.type,this.options=void 0,this._parsing=!1,this._data=void 0,this._objectData=void 0,this._sharedOptions=void 0,this._drawStart=void 0,this._drawCount=void 0,this.enableOptionSharing=!1,this.supportsDecimation=!1,this.$context=void 0,this._syncList=[],this.datasetElementType=new.target.datasetElementType,this.dataElementType=new.target.dataElementType,this.initialize()}initialize(){const t=this._cachedMeta;this.configure(),this.linkScales(),t._stacked=Es(t.vScale,t),this.addElements(),this.options.fill&&!this.chart.isPluginEnabled("filler")&&console.warn("Tried to use the 'fill' option without the 'Filler' plugin enabled. Please import and register the 'Filler' plugin and make sure it is not disabled in the options")}updateIndex(t){this.index!==t&&Vs(this._cachedMeta),this.index=t}linkScales(){const t=this.chart,e=this._cachedMeta,i=this.getDataset(),s=(t,e,i,s)=>"x"===t?e:"r"===t?s:i,n=e.xAxisID=l(i.xAxisID,Fs(t,"x")),o=e.yAxisID=l(i.yAxisID,Fs(t,"y")),a=e.rAxisID=l(i.rAxisID,Fs(t,"r")),r=e.indexAxis,h=e.iAxisID=s(r,n,o,a),c=e.vAxisID=s(r,o,n,a);e.xScale=this.getScaleForId(n),e.yScale=this.getScaleForId(o),e.rScale=this.getScaleForId(a),e.iScale=this.getScaleForId(h),e.vScale=this.getScaleForId(c)}getDataset(){return this.chart.data.datasets[this.index]}getMeta(){return this.chart.getDatasetMeta(this.index)}getScaleForId(t){return this.chart.scales[t]}_getOtherScale(t){const e=this._cachedMeta;return t===e.iScale?e.vScale:e.iScale}reset(){this._update("reset")}_destroy(){const t=this._cachedMeta;this._data&&rt(this._data,this),t._stacked&&Vs(t)}_dataCheck(){const t=this.getDataset(),e=t.data||(t.data=[]),i=this._data;if(o(e))this._data=function(t){const e=Object.keys(t),i=new Array(e.length);let s,n,o;for(s=0,n=e.length;s0&&i._parsed[t-1];if(!1===this._parsing)i._parsed=s,i._sorted=!0,d=s;else{d=n(s[t])?this.parseArrayData(i,s,t,e):o(s[t])?this.parseObjectData(i,s,t,e):this.parsePrimitiveData(i,s,t,e);const a=()=>null===c[l]||f&&c[l]t&&!e.hidden&&e._stacked&&{keys:Ts(i,!0),values:null})(e,i,this.chart),h={min:Number.POSITIVE_INFINITY,max:Number.NEGATIVE_INFINITY},{min:c,max:d}=function(t){const{min:e,max:i,minDefined:s,maxDefined:n}=t.getUserBounds();return{min:s?e:Number.NEGATIVE_INFINITY,max:n?i:Number.POSITIVE_INFINITY}}(r);let u,f;function g(){f=s[u];const e=f[r.axis];return!a(f[t.axis])||c>e||d=0;--u)if(!g()){this.updateRangeFromParsed(h,t,f,l);break}return h}getAllParsedValues(t){const e=this._cachedMeta._parsed,i=[];let s,n,o;for(s=0,n=e.length;s=0&&tthis.getContext(i,s,e)),c);return f.$shared&&(f.$shared=r,n[o]=Object.freeze(Ws(f,r))),f}_resolveAnimations(t,e,i){const s=this.chart,n=this._cachedDataOpts,o=`animation-${e}`,a=n[o];if(a)return a;let r;if(!1!==s.options.animation){const s=this.chart.config,n=s.datasetAnimationScopeKeys(this._type,e),o=s.getOptionScopes(this.getDataset(),n);r=s.createResolver(o,this.getContext(t,i,e))}const l=new Os(s,r&&r.animations);return r&&r._cacheable&&(n[o]=Object.freeze(l)),l}getSharedOptions(t){if(t.$shared)return this._sharedOptions||(this._sharedOptions=Object.assign({},t))}includeOptions(t,e){return!e||Bs(t)||this.chart._animationsDisabled}_getSharedOptions(t,e){const i=this.resolveDataElementOptions(t,e),s=this._sharedOptions,n=this.getSharedOptions(i),o=this.includeOptions(e,n)||n!==s;return this.updateSharedOptions(n,e,i),{sharedOptions:n,includeOptions:o}}updateElement(t,e,i,s){Bs(s)?Object.assign(t,i):this._resolveAnimations(e,s).update(t,i)}updateSharedOptions(t,e,i){t&&!Bs(e)&&this._resolveAnimations(void 0,e).update(t,i)}_setStyle(t,e,i,s){t.active=s;const n=this.getStyle(e,s);this._resolveAnimations(e,i,s).update(t,{options:!s&&this.getSharedOptions(n)||n})}removeHoverStyle(t,e,i){this._setStyle(t,i,"active",!1)}setHoverStyle(t,e,i){this._setStyle(t,i,"active",!0)}_removeDatasetHoverStyle(){const t=this._cachedMeta.dataset;t&&this._setStyle(t,void 0,"active",!1)}_setDatasetHoverStyle(){const t=this._cachedMeta.dataset;t&&this._setStyle(t,void 0,"active",!0)}_resyncElements(t){const e=this._data,i=this._cachedMeta.data;for(const[t,e,i]of this._syncList)this[t](e,i);this._syncList=[];const s=i.length,n=e.length,o=Math.min(n,s);o&&this.parse(0,o),n>s?this._insertElements(s,n-s,t):n{for(t.length+=e,a=t.length-1;a>=o;a--)t[a]=t[a-e]};for(r(n),a=t;a{s[t]=i[t]&&i[t].active()?i[t]._to:this[t]})),s}}function js(t,e){const i=t.options.ticks,n=function(t){const e=t.options.offset,i=t._tickSize(),s=t._length/i+(e?0:1),n=t._maxLength/i;return Math.floor(Math.min(s,n))}(t),o=Math.min(i.maxTicksLimit||n,n),a=i.major.enabled?function(t){const e=[];let i,s;for(i=0,s=t.length;io)return function(t,e,i,s){let n,o=0,a=i[0];for(s=Math.ceil(s),n=0;nn)return e}return Math.max(n,1)}(a,e,o);if(r>0){let t,i;const n=r>1?Math.round((h-l)/(r-1)):null;for($s(e,c,d,s(n)?0:l-n,l),t=0,i=r-1;t"top"===e||"left"===e?t[e]+i:t[e]-i,Us=(t,e)=>Math.min(e||t,t);function Xs(t,e){const i=[],s=t.length/e,n=t.length;let o=0;for(;oa+r)))return h}function Ks(t){return t.drawTicks?t.tickLength:0}function Gs(t,e){if(!t.display)return 0;const i=Si(t.font,e),s=ki(t.padding);return(n(t.text)?t.text.length:1)*i.lineHeight+s.height}function Zs(t,e,i){let s=ut(t);return(i&&"right"!==e||!i&&"right"===e)&&(s=(t=>"left"===t?"right":"right"===t?"left":t)(s)),s}class Js extends Hs{constructor(t){super(),this.id=t.id,this.type=t.type,this.options=void 0,this.ctx=t.ctx,this.chart=t.chart,this.top=void 0,this.bottom=void 0,this.left=void 0,this.right=void 0,this.width=void 0,this.height=void 0,this._margins={left:0,right:0,top:0,bottom:0},this.maxWidth=void 0,this.maxHeight=void 0,this.paddingTop=void 0,this.paddingBottom=void 0,this.paddingLeft=void 0,this.paddingRight=void 0,this.axis=void 0,this.labelRotation=void 0,this.min=void 0,this.max=void 0,this._range=void 0,this.ticks=[],this._gridLineItems=null,this._labelItems=null,this._labelSizes=null,this._length=0,this._maxLength=0,this._longestTextCache={},this._startPixel=void 0,this._endPixel=void 0,this._reversePixels=!1,this._userMax=void 0,this._userMin=void 0,this._suggestedMax=void 0,this._suggestedMin=void 0,this._ticksLength=0,this._borderValue=0,this._cache={},this._dataLimitsCached=!1,this.$context=void 0}init(t){this.options=t.setContext(this.getContext()),this.axis=t.axis,this._userMin=this.parse(t.min),this._userMax=this.parse(t.max),this._suggestedMin=this.parse(t.suggestedMin),this._suggestedMax=this.parse(t.suggestedMax)}parse(t,e){return t}getUserBounds(){let{_userMin:t,_userMax:e,_suggestedMin:i,_suggestedMax:s}=this;return t=r(t,Number.POSITIVE_INFINITY),e=r(e,Number.NEGATIVE_INFINITY),i=r(i,Number.POSITIVE_INFINITY),s=r(s,Number.NEGATIVE_INFINITY),{min:r(t,i),max:r(e,s),minDefined:a(t),maxDefined:a(e)}}getMinMax(t){let e,{min:i,max:s,minDefined:n,maxDefined:o}=this.getUserBounds();if(n&&o)return{min:i,max:s};const a=this.getMatchingVisibleMetas();for(let r=0,l=a.length;rs?s:i,s=n&&i>s?i:s,{min:r(i,r(s,i)),max:r(s,r(i,s))}}getPadding(){return{left:this.paddingLeft||0,top:this.paddingTop||0,right:this.paddingRight||0,bottom:this.paddingBottom||0}}getTicks(){return this.ticks}getLabels(){const t=this.chart.data;return this.options.labels||(this.isHorizontal()?t.xLabels:t.yLabels)||t.labels||[]}getLabelItems(t=this.chart.chartArea){return this._labelItems||(this._labelItems=this._computeLabelItems(t))}beforeLayout(){this._cache={},this._dataLimitsCached=!1}beforeUpdate(){d(this.options.beforeUpdate,[this])}update(t,e,i){const{beginAtZero:s,grace:n,ticks:o}=this.options,a=o.sampleSize;this.beforeUpdate(),this.maxWidth=t,this.maxHeight=e,this._margins=i=Object.assign({left:0,right:0,top:0,bottom:0},i),this.ticks=null,this._labelSizes=null,this._gridLineItems=null,this._labelItems=null,this.beforeSetDimensions(),this.setDimensions(),this.afterSetDimensions(),this._maxLength=this.isHorizontal()?this.width+i.left+i.right:this.height+i.top+i.bottom,this._dataLimitsCached||(this.beforeDataLimits(),this.determineDataLimits(),this.afterDataLimits(),this._range=Di(this,n,s),this._dataLimitsCached=!0),this.beforeBuildTicks(),this.ticks=this.buildTicks()||[],this.afterBuildTicks();const r=a=n||i<=1||!this.isHorizontal())return void(this.labelRotation=s);const h=this._getLabelSizes(),c=h.widest.width,d=h.highest.height,u=J(this.chart.width-c,0,this.maxWidth);o=t.offset?this.maxWidth/i:u/(i-1),c+6>o&&(o=u/(i-(t.offset?.5:1)),a=this.maxHeight-Ks(t.grid)-e.padding-Gs(t.title,this.chart.options.font),r=Math.sqrt(c*c+d*d),l=Y(Math.min(Math.asin(J((h.highest.height+6)/o,-1,1)),Math.asin(J(a/r,-1,1))-Math.asin(J(d/r,-1,1)))),l=Math.max(s,Math.min(n,l))),this.labelRotation=l}afterCalculateLabelRotation(){d(this.options.afterCalculateLabelRotation,[this])}afterAutoSkip(){}beforeFit(){d(this.options.beforeFit,[this])}fit(){const t={width:0,height:0},{chart:e,options:{ticks:i,title:s,grid:n}}=this,o=this._isVisible(),a=this.isHorizontal();if(o){const o=Gs(s,e.options.font);if(a?(t.width=this.maxWidth,t.height=Ks(n)+o):(t.height=this.maxHeight,t.width=Ks(n)+o),i.display&&this.ticks.length){const{first:e,last:s,widest:n,highest:o}=this._getLabelSizes(),r=2*i.padding,l=$(this.labelRotation),h=Math.cos(l),c=Math.sin(l);if(a){const e=i.mirror?0:c*n.width+h*o.height;t.height=Math.min(this.maxHeight,t.height+e+r)}else{const e=i.mirror?0:h*n.width+c*o.height;t.width=Math.min(this.maxWidth,t.width+e+r)}this._calculatePadding(e,s,c,h)}}this._handleMargins(),a?(this.width=this._length=e.width-this._margins.left-this._margins.right,this.height=t.height):(this.width=t.width,this.height=this._length=e.height-this._margins.top-this._margins.bottom)}_calculatePadding(t,e,i,s){const{ticks:{align:n,padding:o},position:a}=this.options,r=0!==this.labelRotation,l="top"!==a&&"x"===this.axis;if(this.isHorizontal()){const a=this.getPixelForTick(0)-this.left,h=this.right-this.getPixelForTick(this.ticks.length-1);let c=0,d=0;r?l?(c=s*t.width,d=i*e.height):(c=i*t.height,d=s*e.width):"start"===n?d=e.width:"end"===n?c=t.width:"inner"!==n&&(c=t.width/2,d=e.width/2),this.paddingLeft=Math.max((c-a+o)*this.width/(this.width-a),0),this.paddingRight=Math.max((d-h+o)*this.width/(this.width-h),0)}else{let i=e.height/2,s=t.height/2;"start"===n?(i=0,s=t.height):"end"===n&&(i=e.height,s=0),this.paddingTop=i+o,this.paddingBottom=s+o}}_handleMargins(){this._margins&&(this._margins.left=Math.max(this.paddingLeft,this._margins.left),this._margins.top=Math.max(this.paddingTop,this._margins.top),this._margins.right=Math.max(this.paddingRight,this._margins.right),this._margins.bottom=Math.max(this.paddingBottom,this._margins.bottom))}afterFit(){d(this.options.afterFit,[this])}isHorizontal(){const{axis:t,position:e}=this.options;return"top"===e||"bottom"===e||"x"===t}isFullSize(){return this.options.fullSize}_convertTicksToLabels(t){let e,i;for(this.beforeTickToLabelConversion(),this.generateTickLabels(t),e=0,i=t.length;e{const i=t.gc,s=i.length/2;let n;if(s>e){for(n=0;n({width:r[t]||0,height:l[t]||0});return{first:P(0),last:P(e-1),widest:P(k),highest:P(S),widths:r,heights:l}}getLabelForValue(t){return t}getPixelForValue(t,e){return NaN}getValueForPixel(t){}getPixelForTick(t){const e=this.ticks;return t<0||t>e.length-1?null:this.getPixelForValue(e[t].value)}getPixelForDecimal(t){this._reversePixels&&(t=1-t);const e=this._startPixel+t*this._length;return Q(this._alignToPixels?Ae(this.chart,e,0):e)}getDecimalForPixel(t){const e=(t-this._startPixel)/this._length;return this._reversePixels?1-e:e}getBasePixel(){return this.getPixelForValue(this.getBaseValue())}getBaseValue(){const{min:t,max:e}=this;return t<0&&e<0?e:t>0&&e>0?t:0}getContext(t){const e=this.ticks||[];if(t>=0&&ta*s?a/i:r/s:r*s0}_computeGridLineItems(t){const e=this.axis,i=this.chart,s=this.options,{grid:n,position:a,border:r}=s,h=n.offset,c=this.isHorizontal(),d=this.ticks.length+(h?1:0),u=Ks(n),f=[],g=r.setContext(this.getContext()),p=g.display?g.width:0,m=p/2,b=function(t){return Ae(i,t,p)};let x,_,y,v,M,w,k,S,P,D,C,O;if("top"===a)x=b(this.bottom),w=this.bottom-u,S=x-m,D=b(t.top)+m,O=t.bottom;else if("bottom"===a)x=b(this.top),D=t.top,O=b(t.bottom)-m,w=x+m,S=this.top+u;else if("left"===a)x=b(this.right),M=this.right-u,k=x-m,P=b(t.left)+m,C=t.right;else if("right"===a)x=b(this.left),P=t.left,C=b(t.right)-m,M=x+m,k=this.left+u;else if("x"===e){if("center"===a)x=b((t.top+t.bottom)/2+.5);else if(o(a)){const t=Object.keys(a)[0],e=a[t];x=b(this.chart.scales[t].getPixelForValue(e))}D=t.top,O=t.bottom,w=x+m,S=w+u}else if("y"===e){if("center"===a)x=b((t.left+t.right)/2);else if(o(a)){const t=Object.keys(a)[0],e=a[t];x=b(this.chart.scales[t].getPixelForValue(e))}M=x-m,k=M-u,P=t.left,C=t.right}const A=l(s.ticks.maxTicksLimit,d),T=Math.max(1,Math.ceil(d/A));for(_=0;_e.value===t));if(i>=0){return e.setContext(this.getContext(i)).lineWidth}return 0}drawGrid(t){const e=this.options.grid,i=this.ctx,s=this._gridLineItems||(this._gridLineItems=this._computeGridLineItems(t));let n,o;const a=(t,e,s)=>{s.width&&s.color&&(i.save(),i.lineWidth=s.width,i.strokeStyle=s.color,i.setLineDash(s.borderDash||[]),i.lineDashOffset=s.borderDashOffset,i.beginPath(),i.moveTo(t.x,t.y),i.lineTo(e.x,e.y),i.stroke(),i.restore())};if(e.display)for(n=0,o=s.length;n{this.drawBackground(),this.drawGrid(t),this.drawTitle()}},{z:s,draw:()=>{this.drawBorder()}},{z:e,draw:t=>{this.drawLabels(t)}}]:[{z:e,draw:t=>{this.draw(t)}}]}getMatchingVisibleMetas(t){const e=this.chart.getSortedVisibleDatasetMetas(),i=this.axis+"AxisID",s=[];let n,o;for(n=0,o=e.length;n{const s=i.split("."),n=s.pop(),o=[t].concat(s).join("."),a=e[i].split("."),r=a.pop(),l=a.join(".");ue.route(o,n,l,r)}))}(e,t.defaultRoutes);t.descriptors&&ue.describe(e,t.descriptors)}(t,o,i),this.override&&ue.override(t.id,t.overrides)),o}get(t){return this.items[t]}unregister(t){const e=this.items,i=t.id,s=this.scope;i in e&&delete e[i],s&&i in ue[s]&&(delete ue[s][i],this.override&&delete re[i])}}class tn{constructor(){this.controllers=new Qs(Ns,"datasets",!0),this.elements=new Qs(Hs,"elements"),this.plugins=new Qs(Object,"plugins"),this.scales=new Qs(Js,"scales"),this._typedRegistries=[this.controllers,this.scales,this.elements]}add(...t){this._each("register",t)}remove(...t){this._each("unregister",t)}addControllers(...t){this._each("register",t,this.controllers)}addElements(...t){this._each("register",t,this.elements)}addPlugins(...t){this._each("register",t,this.plugins)}addScales(...t){this._each("register",t,this.scales)}getController(t){return this._get(t,this.controllers,"controller")}getElement(t){return this._get(t,this.elements,"element")}getPlugin(t){return this._get(t,this.plugins,"plugin")}getScale(t){return this._get(t,this.scales,"scale")}removeControllers(...t){this._each("unregister",t,this.controllers)}removeElements(...t){this._each("unregister",t,this.elements)}removePlugins(...t){this._each("unregister",t,this.plugins)}removeScales(...t){this._each("unregister",t,this.scales)}_each(t,e,i){[...e].forEach((e=>{const s=i||this._getRegistryForType(e);i||s.isForType(e)||s===this.plugins&&e.id?this._exec(t,s,e):u(e,(e=>{const s=i||this._getRegistryForType(e);this._exec(t,s,e)}))}))}_exec(t,e,i){const s=w(t);d(i["before"+s],[],i),e[t](i),d(i["after"+s],[],i)}_getRegistryForType(t){for(let e=0;et.filter((t=>!e.some((e=>t.plugin.id===e.plugin.id))));this._notify(s(e,i),t,"stop"),this._notify(s(i,e),t,"start")}}function nn(t,e){return e||!1!==t?!0===t?{}:t:null}function on(t,{plugin:e,local:i},s,n){const o=t.pluginScopeKeys(e),a=t.getOptionScopes(s,o);return i&&e.defaults&&a.push(e.defaults),t.createResolver(a,n,[""],{scriptable:!1,indexable:!1,allKeys:!0})}function an(t,e){const i=ue.datasets[t]||{};return((e.datasets||{})[t]||{}).indexAxis||e.indexAxis||i.indexAxis||"x"}function rn(t){if("x"===t||"y"===t||"r"===t)return t}function ln(t,...e){if(rn(t))return t;for(const s of e){const e=s.axis||("top"===(i=s.position)||"bottom"===i?"x":"left"===i||"right"===i?"y":void 0)||t.length>1&&rn(t[0].toLowerCase());if(e)return e}var i;throw new Error(`Cannot determine type of '${t}' axis. Please provide 'axis' or 'position' option.`)}function hn(t,e,i){if(i[e+"AxisID"]===t)return{axis:e}}function cn(t,e){const i=re[t.type]||{scales:{}},s=e.scales||{},n=an(t.type,e),a=Object.create(null);return Object.keys(s).forEach((e=>{const r=s[e];if(!o(r))return console.error(`Invalid scale configuration for scale: ${e}`);if(r._proxy)return console.warn(`Ignoring resolver passed as options for scale: ${e}`);const l=ln(e,r,function(t,e){if(e.data&&e.data.datasets){const i=e.data.datasets.filter((e=>e.xAxisID===t||e.yAxisID===t));if(i.length)return hn(t,"x",i[0])||hn(t,"y",i[0])}return{}}(e,t),ue.scales[r.type]),h=function(t,e){return t===e?"_index_":"_value_"}(l,n),c=i.scales||{};a[e]=x(Object.create(null),[{axis:l},r,c[l],c[h]])})),t.data.datasets.forEach((i=>{const n=i.type||t.type,o=i.indexAxis||an(n,e),r=(re[n]||{}).scales||{};Object.keys(r).forEach((t=>{const e=function(t,e){let i=t;return"_index_"===t?i=e:"_value_"===t&&(i="x"===e?"y":"x"),i}(t,o),n=i[e+"AxisID"]||e;a[n]=a[n]||Object.create(null),x(a[n],[{axis:e},s[n],r[t]])}))})),Object.keys(a).forEach((t=>{const e=a[t];x(e,[ue.scales[e.type],ue.scale])})),a}function dn(t){const e=t.options||(t.options={});e.plugins=l(e.plugins,{}),e.scales=cn(t,e)}function un(t){return(t=t||{}).datasets=t.datasets||[],t.labels=t.labels||[],t}const fn=new Map,gn=new Set;function pn(t,e){let i=fn.get(t);return i||(i=e(),fn.set(t,i),gn.add(i)),i}const mn=(t,e,i)=>{const s=M(e,i);void 0!==s&&t.add(s)};class bn{constructor(t){this._config=function(t){return(t=t||{}).data=un(t.data),dn(t),t}(t),this._scopeCache=new Map,this._resolverCache=new Map}get platform(){return this._config.platform}get type(){return this._config.type}set type(t){this._config.type=t}get data(){return this._config.data}set data(t){this._config.data=un(t)}get options(){return this._config.options}set options(t){this._config.options=t}get plugins(){return this._config.plugins}update(){const t=this._config;this.clearCache(),dn(t)}clearCache(){this._scopeCache.clear(),this._resolverCache.clear()}datasetScopeKeys(t){return pn(t,(()=>[[`datasets.${t}`,""]]))}datasetAnimationScopeKeys(t,e){return pn(`${t}.transition.${e}`,(()=>[[`datasets.${t}.transitions.${e}`,`transitions.${e}`],[`datasets.${t}`,""]]))}datasetElementScopeKeys(t,e){return pn(`${t}-${e}`,(()=>[[`datasets.${t}.elements.${e}`,`datasets.${t}`,`elements.${e}`,""]]))}pluginScopeKeys(t){const e=t.id;return pn(`${this.type}-plugin-${e}`,(()=>[[`plugins.${e}`,...t.additionalOptionScopes||[]]]))}_cachedScopes(t,e){const i=this._scopeCache;let s=i.get(t);return s&&!e||(s=new Map,i.set(t,s)),s}getOptionScopes(t,e,i){const{options:s,type:n}=this,o=this._cachedScopes(t,i),a=o.get(e);if(a)return a;const r=new Set;e.forEach((e=>{t&&(r.add(t),e.forEach((e=>mn(r,t,e)))),e.forEach((t=>mn(r,s,t))),e.forEach((t=>mn(r,re[n]||{},t))),e.forEach((t=>mn(r,ue,t))),e.forEach((t=>mn(r,le,t)))}));const l=Array.from(r);return 0===l.length&&l.push(Object.create(null)),gn.has(e)&&o.set(e,l),l}chartOptionScopes(){const{options:t,type:e}=this;return[t,re[e]||{},ue.datasets[e]||{},{type:e},ue,le]}resolveNamedOptions(t,e,i,s=[""]){const o={$shared:!0},{resolver:a,subPrefixes:r}=xn(this._resolverCache,t,s);let l=a;if(function(t,e){const{isScriptable:i,isIndexable:s}=Ye(t);for(const o of e){const e=i(o),a=s(o),r=(a||e)&&t[o];if(e&&(S(r)||_n(r))||a&&n(r))return!0}return!1}(a,e)){o.$shared=!1;l=$e(a,i=S(i)?i():i,this.createResolver(t,i,r))}for(const t of e)o[t]=l[t];return o}createResolver(t,e,i=[""],s){const{resolver:n}=xn(this._resolverCache,t,i);return o(e)?$e(n,e,void 0,s):n}}function xn(t,e,i){let s=t.get(e);s||(s=new Map,t.set(e,s));const n=i.join();let o=s.get(n);if(!o){o={resolver:je(e,i),subPrefixes:i.filter((t=>!t.toLowerCase().includes("hover")))},s.set(n,o)}return o}const _n=t=>o(t)&&Object.getOwnPropertyNames(t).reduce(((e,i)=>e||S(t[i])),!1);const yn=["top","bottom","left","right","chartArea"];function vn(t,e){return"top"===t||"bottom"===t||-1===yn.indexOf(t)&&"x"===e}function Mn(t,e){return function(i,s){return i[t]===s[t]?i[e]-s[e]:i[t]-s[t]}}function wn(t){const e=t.chart,i=e.options.animation;e.notifyPlugins("afterRender"),d(i&&i.onComplete,[t],e)}function kn(t){const e=t.chart,i=e.options.animation;d(i&&i.onProgress,[t],e)}function Sn(t){return fe()&&"string"==typeof t?t=document.getElementById(t):t&&t.length&&(t=t[0]),t&&t.canvas&&(t=t.canvas),t}const Pn={},Dn=t=>{const e=Sn(t);return Object.values(Pn).filter((t=>t.canvas===e)).pop()};function Cn(t,e,i){const s=Object.keys(t);for(const n of s){const s=+n;if(s>=e){const o=t[n];delete t[n],(i>0||s>e)&&(t[s+i]=o)}}}function On(t,e,i){return t.options.clip?t[i]:e[i]}class An{static defaults=ue;static instances=Pn;static overrides=re;static registry=en;static version="4.4.0";static getChart=Dn;static register(...t){en.add(...t),Tn()}static unregister(...t){en.remove(...t),Tn()}constructor(t,e){const s=this.config=new bn(e),n=Sn(t),o=Dn(n);if(o)throw new Error("Canvas is already in use. Chart with ID '"+o.id+"' must be destroyed before the canvas with ID '"+o.canvas.id+"' can be reused.");const a=s.createResolver(s.chartOptionScopes(),this.getContext());this.platform=new(s.platform||ks(n)),this.platform.updateConfig(s);const r=this.platform.acquireContext(n,a.aspectRatio),l=r&&r.canvas,h=l&&l.height,c=l&&l.width;this.id=i(),this.ctx=r,this.canvas=l,this.width=c,this.height=h,this._options=a,this._aspectRatio=this.aspectRatio,this._layers=[],this._metasets=[],this._stacks=void 0,this.boxes=[],this.currentDevicePixelRatio=void 0,this.chartArea=void 0,this._active=[],this._lastEvent=void 0,this._listeners={},this._responsiveListeners=void 0,this._sortedMetasets=[],this.scales={},this._plugins=new sn,this.$proxies={},this._hiddenIndices={},this.attached=!1,this._animationsDisabled=void 0,this.$context=void 0,this._doResize=dt((t=>this.update(t)),a.resizeDelay||0),this._dataChanges=[],Pn[this.id]=this,r&&l?(xt.listen(this,"complete",wn),xt.listen(this,"progress",kn),this._initialize(),this.attached&&this.update()):console.error("Failed to create chart: can't acquire context from the given item")}get aspectRatio(){const{options:{aspectRatio:t,maintainAspectRatio:e},width:i,height:n,_aspectRatio:o}=this;return s(t)?e&&o?o:n?i/n:null:t}get data(){return this.config.data}set data(t){this.config.data=t}get options(){return this._options}set options(t){this.config.options=t}get registry(){return en}_initialize(){return this.notifyPlugins("beforeInit"),this.options.responsive?this.resize():ke(this,this.options.devicePixelRatio),this.bindEvents(),this.notifyPlugins("afterInit"),this}clear(){return Te(this.canvas,this.ctx),this}stop(){return xt.stop(this),this}resize(t,e){xt.running(this)?this._resizeBeforeDraw={width:t,height:e}:this._resize(t,e)}_resize(t,e){const i=this.options,s=this.canvas,n=i.maintainAspectRatio&&this.aspectRatio,o=this.platform.getMaximumSize(s,t,e,n),a=i.devicePixelRatio||this.platform.getDevicePixelRatio(),r=this.width?"resize":"attach";this.width=o.width,this.height=o.height,this._aspectRatio=this.aspectRatio,ke(this,a,!0)&&(this.notifyPlugins("resize",{size:o}),d(i.onResize,[this,o],this),this.attached&&this._doResize(r)&&this.render())}ensureScalesHaveIDs(){u(this.options.scales||{},((t,e)=>{t.id=e}))}buildOrUpdateScales(){const t=this.options,e=t.scales,i=this.scales,s=Object.keys(i).reduce(((t,e)=>(t[e]=!1,t)),{});let n=[];e&&(n=n.concat(Object.keys(e).map((t=>{const i=e[t],s=ln(t,i),n="r"===s,o="x"===s;return{options:i,dposition:n?"chartArea":o?"bottom":"left",dtype:n?"radialLinear":o?"category":"linear"}})))),u(n,(e=>{const n=e.options,o=n.id,a=ln(o,n),r=l(n.type,e.dtype);void 0!==n.position&&vn(n.position,a)===vn(e.dposition)||(n.position=e.dposition),s[o]=!0;let h=null;if(o in i&&i[o].type===r)h=i[o];else{h=new(en.getScale(r))({id:o,type:r,ctx:this.ctx,chart:this}),i[h.id]=h}h.init(n,t)})),u(s,((t,e)=>{t||delete i[e]})),u(i,(t=>{as.configure(this,t,t.options),as.addBox(this,t)}))}_updateMetasets(){const t=this._metasets,e=this.data.datasets.length,i=t.length;if(t.sort(((t,e)=>t.index-e.index)),i>e){for(let t=e;te.length&&delete this._stacks,t.forEach(((t,i)=>{0===e.filter((e=>e===t._dataset)).length&&this._destroyDatasetMeta(i)}))}buildOrUpdateControllers(){const t=[],e=this.data.datasets;let i,s;for(this._removeUnreferencedMetasets(),i=0,s=e.length;i{this.getDatasetMeta(e).controller.reset()}),this)}reset(){this._resetElements(),this.notifyPlugins("reset")}update(t){const e=this.config;e.update();const i=this._options=e.createResolver(e.chartOptionScopes(),this.getContext()),s=this._animationsDisabled=!i.animation;if(this._updateScales(),this._checkEventBindings(),this._updateHiddenIndices(),this._plugins.invalidate(),!1===this.notifyPlugins("beforeUpdate",{mode:t,cancelable:!0}))return;const n=this.buildOrUpdateControllers();this.notifyPlugins("beforeElementsUpdate");let o=0;for(let t=0,e=this.data.datasets.length;t{t.reset()})),this._updateDatasets(t),this.notifyPlugins("afterUpdate",{mode:t}),this._layers.sort(Mn("z","_idx"));const{_active:a,_lastEvent:r}=this;r?this._eventHandler(r,!0):a.length&&this._updateHoverStyles(a,a,!0),this.render()}_updateScales(){u(this.scales,(t=>{as.removeBox(this,t)})),this.ensureScalesHaveIDs(),this.buildOrUpdateScales()}_checkEventBindings(){const t=this.options,e=new Set(Object.keys(this._listeners)),i=new Set(t.events);P(e,i)&&!!this._responsiveListeners===t.responsive||(this.unbindEvents(),this.bindEvents())}_updateHiddenIndices(){const{_hiddenIndices:t}=this,e=this._getUniformDataChanges()||[];for(const{method:i,start:s,count:n}of e){Cn(t,s,"_removeElements"===i?-n:n)}}_getUniformDataChanges(){const t=this._dataChanges;if(!t||!t.length)return;this._dataChanges=[];const e=this.data.datasets.length,i=e=>new Set(t.filter((t=>t[0]===e)).map(((t,e)=>e+","+t.splice(1).join(",")))),s=i(0);for(let t=1;tt.split(","))).map((t=>({method:t[1],start:+t[2],count:+t[3]})))}_updateLayout(t){if(!1===this.notifyPlugins("beforeLayout",{cancelable:!0}))return;as.update(this,this.width,this.height,t);const e=this.chartArea,i=e.width<=0||e.height<=0;this._layers=[],u(this.boxes,(t=>{i&&"chartArea"===t.position||(t.configure&&t.configure(),this._layers.push(...t._layers()))}),this),this._layers.forEach(((t,e)=>{t._idx=e})),this.notifyPlugins("afterLayout")}_updateDatasets(t){if(!1!==this.notifyPlugins("beforeDatasetsUpdate",{mode:t,cancelable:!0})){for(let t=0,e=this.data.datasets.length;t=0;--e)this._drawDataset(t[e]);this.notifyPlugins("afterDatasetsDraw")}_drawDataset(t){const e=this.ctx,i=t._clip,s=!i.disabled,n=function(t,e){const{xScale:i,yScale:s}=t;return i&&s?{left:On(i,e,"left"),right:On(i,e,"right"),top:On(s,e,"top"),bottom:On(s,e,"bottom")}:e}(t,this.chartArea),o={meta:t,index:t.index,cancelable:!0};!1!==this.notifyPlugins("beforeDatasetDraw",o)&&(s&&Ie(e,{left:!1===i.left?0:n.left-i.left,right:!1===i.right?this.width:n.right+i.right,top:!1===i.top?0:n.top-i.top,bottom:!1===i.bottom?this.height:n.bottom+i.bottom}),t.controller.draw(),s&&ze(e),o.cancelable=!1,this.notifyPlugins("afterDatasetDraw",o))}isPointInArea(t){return Re(t,this.chartArea,this._minPadding)}getElementsAtEventForMode(t,e,i,s){const n=Xi.modes[e];return"function"==typeof n?n(this,t,i,s):[]}getDatasetMeta(t){const e=this.data.datasets[t],i=this._metasets;let s=i.filter((t=>t&&t._dataset===e)).pop();return s||(s={type:null,data:[],dataset:null,controller:null,hidden:null,xAxisID:null,yAxisID:null,order:e&&e.order||0,index:t,_dataset:e,_parsed:[],_sorted:!1},i.push(s)),s}getContext(){return this.$context||(this.$context=Ci(null,{chart:this,type:"chart"}))}getVisibleDatasetCount(){return this.getSortedVisibleDatasetMetas().length}isDatasetVisible(t){const e=this.data.datasets[t];if(!e)return!1;const i=this.getDatasetMeta(t);return"boolean"==typeof i.hidden?!i.hidden:!e.hidden}setDatasetVisibility(t,e){this.getDatasetMeta(t).hidden=!e}toggleDataVisibility(t){this._hiddenIndices[t]=!this._hiddenIndices[t]}getDataVisibility(t){return!this._hiddenIndices[t]}_updateVisibility(t,e,i){const s=i?"show":"hide",n=this.getDatasetMeta(t),o=n.controller._resolveAnimations(void 0,s);k(e)?(n.data[e].hidden=!i,this.update()):(this.setDatasetVisibility(t,i),o.update(n,{visible:i}),this.update((e=>e.datasetIndex===t?s:void 0)))}hide(t,e){this._updateVisibility(t,e,!1)}show(t,e){this._updateVisibility(t,e,!0)}_destroyDatasetMeta(t){const e=this._metasets[t];e&&e.controller&&e.controller._destroy(),delete this._metasets[t]}_stop(){let t,e;for(this.stop(),xt.remove(this),t=0,e=this.data.datasets.length;t{e.addEventListener(this,i,s),t[i]=s},s=(t,e,i)=>{t.offsetX=e,t.offsetY=i,this._eventHandler(t)};u(this.options.events,(t=>i(t,s)))}bindResponsiveEvents(){this._responsiveListeners||(this._responsiveListeners={});const t=this._responsiveListeners,e=this.platform,i=(i,s)=>{e.addEventListener(this,i,s),t[i]=s},s=(i,s)=>{t[i]&&(e.removeEventListener(this,i,s),delete t[i])},n=(t,e)=>{this.canvas&&this.resize(t,e)};let o;const a=()=>{s("attach",a),this.attached=!0,this.resize(),i("resize",n),i("detach",o)};o=()=>{this.attached=!1,s("resize",n),this._stop(),this._resize(0,0),i("attach",a)},e.isAttached(this.canvas)?a():o()}unbindEvents(){u(this._listeners,((t,e)=>{this.platform.removeEventListener(this,e,t)})),this._listeners={},u(this._responsiveListeners,((t,e)=>{this.platform.removeEventListener(this,e,t)})),this._responsiveListeners=void 0}updateHoverStyle(t,e,i){const s=i?"set":"remove";let n,o,a,r;for("dataset"===e&&(n=this.getDatasetMeta(t[0].datasetIndex),n.controller["_"+s+"DatasetHoverStyle"]()),a=0,r=t.length;a{const i=this.getDatasetMeta(t);if(!i)throw new Error("No dataset found at index "+t);return{datasetIndex:t,element:i.data[e],index:e}}));!f(i,e)&&(this._active=i,this._lastEvent=null,this._updateHoverStyles(i,e))}notifyPlugins(t,e,i){return this._plugins.notify(this,t,e,i)}isPluginEnabled(t){return 1===this._plugins._cache.filter((e=>e.plugin.id===t)).length}_updateHoverStyles(t,e,i){const s=this.options.hover,n=(t,e)=>t.filter((t=>!e.some((e=>t.datasetIndex===e.datasetIndex&&t.index===e.index)))),o=n(e,t),a=i?t:n(t,e);o.length&&this.updateHoverStyle(o,s.mode,!1),a.length&&s.mode&&this.updateHoverStyle(a,s.mode,!0)}_eventHandler(t,e){const i={event:t,replay:e,cancelable:!0,inChartArea:this.isPointInArea(t)},s=e=>(e.options.events||this.options.events).includes(t.native.type);if(!1===this.notifyPlugins("beforeEvent",i,s))return;const n=this._handleEvent(t,e,i.inChartArea);return i.cancelable=!1,this.notifyPlugins("afterEvent",i,s),(n||i.changed)&&this.render(),this}_handleEvent(t,e,i){const{_active:s=[],options:n}=this,o=e,a=this._getActiveElements(t,s,i,o),r=D(t),l=function(t,e,i,s){return i&&"mouseout"!==t.type?s?e:t:null}(t,this._lastEvent,i,r);i&&(this._lastEvent=null,d(n.onHover,[t,a,this],this),r&&d(n.onClick,[t,a,this],this));const h=!f(a,s);return(h||e)&&(this._active=a,this._updateHoverStyles(a,s,e)),this._lastEvent=l,h}_getActiveElements(t,e,i,s){if("mouseout"===t.type)return[];if(!i)return e;const n=this.options.hover;return this.getElementsAtEventForMode(t,n.mode,n,s)}}function Tn(){return u(An.instances,(t=>t._plugins.invalidate()))}function Ln(){throw new Error("This method is not implemented: Check that a complete date adapter is provided.")}class En{static override(t){Object.assign(En.prototype,t)}options;constructor(t){this.options=t||{}}init(){}formats(){return Ln()}parse(){return Ln()}format(){return Ln()}add(){return Ln()}diff(){return Ln()}startOf(){return Ln()}endOf(){return Ln()}}var Rn={_date:En};function In(t){const e=t.iScale,i=function(t,e){if(!t._cache.$bar){const i=t.getMatchingVisibleMetas(e);let s=[];for(let e=0,n=i.length;et-e)))}return t._cache.$bar}(e,t.type);let s,n,o,a,r=e._length;const l=()=>{32767!==o&&-32768!==o&&(k(a)&&(r=Math.min(r,Math.abs(o-a)||r)),a=o)};for(s=0,n=i.length;sMath.abs(r)&&(l=r,h=a),e[i.axis]=h,e._custom={barStart:l,barEnd:h,start:n,end:o,min:a,max:r}}(t,e,i,s):e[i.axis]=i.parse(t,s),e}function Fn(t,e,i,s){const n=t.iScale,o=t.vScale,a=n.getLabels(),r=n===o,l=[];let h,c,d,u;for(h=i,c=i+s;ht.x,i="left",s="right"):(e=t.base"spacing"!==t,_indexable:t=>"spacing"!==t&&!t.startsWith("borderDash")&&!t.startsWith("hoverBorderDash")};static overrides={aspectRatio:1,plugins:{legend:{labels:{generateLabels(t){const e=t.data;if(e.labels.length&&e.datasets.length){const{labels:{pointStyle:i,color:s}}=t.legend.options;return e.labels.map(((e,n)=>{const o=t.getDatasetMeta(0).controller.getStyle(n);return{text:e,fillStyle:o.backgroundColor,strokeStyle:o.borderColor,fontColor:s,lineWidth:o.borderWidth,pointStyle:i,hidden:!t.getDataVisibility(n),index:n}}))}return[]}},onClick(t,e,i){i.chart.toggleDataVisibility(e.index),i.chart.update()}}}};constructor(t,e){super(t,e),this.enableOptionSharing=!0,this.innerRadius=void 0,this.outerRadius=void 0,this.offsetX=void 0,this.offsetY=void 0}linkScales(){}parse(t,e){const i=this.getDataset().data,s=this._cachedMeta;if(!1===this._parsing)s._parsed=i;else{let n,a,r=t=>+i[t];if(o(i[t])){const{key:t="value"}=this._parsing;r=e=>+M(i[e],t)}for(n=t,a=t+e;nZ(t,r,l,!0)?1:Math.max(e,e*i,s,s*i),g=(t,e,s)=>Z(t,r,l,!0)?-1:Math.min(e,e*i,s,s*i),p=f(0,h,d),m=f(E,c,u),b=g(C,h,d),x=g(C+E,c,u);s=(p-b)/2,n=(m-x)/2,o=-(p+b)/2,a=-(m+x)/2}return{ratioX:s,ratioY:n,offsetX:o,offsetY:a}}(u,d,r),b=(i.width-o)/f,x=(i.height-o)/g,_=Math.max(Math.min(b,x)/2,0),y=c(this.options.radius,_),v=(y-Math.max(y*r,0))/this._getVisibleDatasetWeightTotal();this.offsetX=p*y,this.offsetY=m*y,s.total=this.calculateTotal(),this.outerRadius=y-v*this._getRingWeightOffset(this.index),this.innerRadius=Math.max(this.outerRadius-v*l,0),this.updateElements(n,0,n.length,t)}_circumference(t,e){const i=this.options,s=this._cachedMeta,n=this._getCircumference();return e&&i.animation.animateRotate||!this.chart.getDataVisibility(t)||null===s._parsed[t]||s.data[t].hidden?0:this.calculateCircumference(s._parsed[t]*n/O)}updateElements(t,e,i,s){const n="reset"===s,o=this.chart,a=o.chartArea,r=o.options.animation,l=(a.left+a.right)/2,h=(a.top+a.bottom)/2,c=n&&r.animateScale,d=c?0:this.innerRadius,u=c?0:this.outerRadius,{sharedOptions:f,includeOptions:g}=this._getSharedOptions(e,s);let p,m=this._getRotation();for(p=0;p0&&!isNaN(t)?O*(Math.abs(t)/e):0}getLabelAndValue(t){const e=this._cachedMeta,i=this.chart,s=i.data.labels||[],n=ne(e._parsed[t],i.options.locale);return{label:s[t]||"",value:n}}getMaxBorderWidth(t){let e=0;const i=this.chart;let s,n,o,a,r;if(!t)for(s=0,n=i.data.datasets.length;s{const o=t.getDatasetMeta(0).controller.getStyle(n);return{text:e,fillStyle:o.backgroundColor,strokeStyle:o.borderColor,fontColor:s,lineWidth:o.borderWidth,pointStyle:i,hidden:!t.getDataVisibility(n),index:n}}))}return[]}},onClick(t,e,i){i.chart.toggleDataVisibility(e.index),i.chart.update()}}},scales:{r:{type:"radialLinear",angleLines:{display:!1},beginAtZero:!0,grid:{circular:!0},pointLabels:{display:!1},startAngle:0}}};constructor(t,e){super(t,e),this.innerRadius=void 0,this.outerRadius=void 0}getLabelAndValue(t){const e=this._cachedMeta,i=this.chart,s=i.data.labels||[],n=ne(e._parsed[t].r,i.options.locale);return{label:s[t]||"",value:n}}parseObjectData(t,e,i,s){return ii.bind(this)(t,e,i,s)}update(t){const e=this._cachedMeta.data;this._updateRadius(),this.updateElements(e,0,e.length,t)}getMinMax(){const t=this._cachedMeta,e={min:Number.POSITIVE_INFINITY,max:Number.NEGATIVE_INFINITY};return t.data.forEach(((t,i)=>{const s=this.getParsed(i).r;!isNaN(s)&&this.chart.getDataVisibility(i)&&(se.max&&(e.max=s))})),e}_updateRadius(){const t=this.chart,e=t.chartArea,i=t.options,s=Math.min(e.right-e.left,e.bottom-e.top),n=Math.max(s/2,0),o=(n-Math.max(i.cutoutPercentage?n/100*i.cutoutPercentage:1,0))/t.getVisibleDatasetCount();this.outerRadius=n-o*this.index,this.innerRadius=this.outerRadius-o}updateElements(t,e,i,s){const n="reset"===s,o=this.chart,a=o.options.animation,r=this._cachedMeta.rScale,l=r.xCenter,h=r.yCenter,c=r.getIndexAngle(0)-.5*C;let d,u=c;const f=360/this.countVisibleElements();for(d=0;d{!isNaN(this.getParsed(i).r)&&this.chart.getDataVisibility(i)&&e++})),e}_computeAngle(t,e,i){return this.chart.getDataVisibility(t)?$(this.resolveDataElementOptions(t,e).angle||i):0}}var Yn=Object.freeze({__proto__:null,BarController:class extends Ns{static id="bar";static defaults={datasetElementType:!1,dataElementType:"bar",categoryPercentage:.8,barPercentage:.9,grouped:!0,animations:{numbers:{type:"number",properties:["x","y","base","width","height"]}}};static overrides={scales:{_index_:{type:"category",offset:!0,grid:{offset:!0}},_value_:{type:"linear",beginAtZero:!0}}};parsePrimitiveData(t,e,i,s){return Fn(t,e,i,s)}parseArrayData(t,e,i,s){return Fn(t,e,i,s)}parseObjectData(t,e,i,s){const{iScale:n,vScale:o}=t,{xAxisKey:a="x",yAxisKey:r="y"}=this._parsing,l="x"===n.axis?a:r,h="x"===o.axis?a:r,c=[];let d,u,f,g;for(d=i,u=i+s;dt.controller.options.grouped)),o=i.options.stacked,a=[],r=t=>{const i=t.controller.getParsed(e),n=i&&i[t.vScale.axis];if(s(n)||isNaN(n))return!0};for(const i of n)if((void 0===e||!r(i))&&((!1===o||-1===a.indexOf(i.stack)||void 0===o&&void 0===i.stack)&&a.push(i.stack),i.index===t))break;return a.length||a.push(void 0),a}_getStackCount(t){return this._getStacks(void 0,t).length}_getStackIndex(t,e,i){const s=this._getStacks(t,i),n=void 0!==e?s.indexOf(e):-1;return-1===n?s.length-1:n}_getRuler(){const t=this.options,e=this._cachedMeta,i=e.iScale,s=[];let n,o;for(n=0,o=e.data.length;n=i?1:-1)}(u,e,r)*a,f===r&&(b-=u/2);const t=e.getPixelForDecimal(0),s=e.getPixelForDecimal(1),o=Math.min(t,s),h=Math.max(t,s);b=Math.max(Math.min(b,h),o),d=b+u,i&&!c&&(l._stacks[e.axis]._visualValues[n]=e.getValueForPixel(d)-e.getValueForPixel(b))}if(b===e.getPixelForValue(r)){const t=F(u)*e.getLineWidthForValue(r)/2;b+=t,u-=t}return{size:u,base:b,head:d,center:d+u/2}}_calculateBarIndexPixels(t,e){const i=e.scale,n=this.options,o=n.skipNull,a=l(n.maxBarThickness,1/0);let r,h;if(e.grouped){const i=o?this._getStackCount(t):e.stackCount,l="flex"===n.barThickness?function(t,e,i,s){const n=e.pixels,o=n[t];let a=t>0?n[t-1]:null,r=t=0;--i)e=Math.max(e,t[i].size(this.resolveDataElementOptions(i))/2);return e>0&&e}getLabelAndValue(t){const e=this._cachedMeta,i=this.chart.data.labels||[],{xScale:s,yScale:n}=e,o=this.getParsed(t),a=s.getLabelForValue(o.x),r=n.getLabelForValue(o.y),l=o._custom;return{label:i[t]||"",value:"("+a+", "+r+(l?", "+l:"")+")"}}update(t){const e=this._cachedMeta.data;this.updateElements(e,0,e.length,t)}updateElements(t,e,i,s){const n="reset"===s,{iScale:o,vScale:a}=this._cachedMeta,{sharedOptions:r,includeOptions:l}=this._getSharedOptions(e,s),h=o.axis,c=a.axis;for(let d=e;d0&&this.getParsed(e-1);for(let i=0;i<_;++i){const g=t[i],_=b?g:{};if(i=x){_.skip=!0;continue}const v=this.getParsed(i),M=s(v[f]),w=_[u]=a.getPixelForValue(v[u],i),k=_[f]=o||M?r.getBasePixel():r.getPixelForValue(l?this.applyStack(r,v,l):v[f],i);_.skip=isNaN(w)||isNaN(k)||M,_.stop=i>0&&Math.abs(v[u]-y[u])>m,p&&(_.parsed=v,_.raw=h.data[i]),d&&(_.options=c||this.resolveDataElementOptions(i,g.active?"active":n)),b||this.updateElement(g,i,_,n),y=v}}getMaxOverflow(){const t=this._cachedMeta,e=t.dataset,i=e.options&&e.options.borderWidth||0,s=t.data||[];if(!s.length)return i;const n=s[0].size(this.resolveDataElementOptions(0)),o=s[s.length-1].size(this.resolveDataElementOptions(s.length-1));return Math.max(i,n,o)/2}draw(){const t=this._cachedMeta;t.dataset.updateControlPoints(this.chart.chartArea,t.iScale.axis),super.draw()}},PieController:class extends jn{static id="pie";static defaults={cutout:0,rotation:0,circumference:360,radius:"100%"}},PolarAreaController:$n,RadarController:class extends Ns{static id="radar";static defaults={datasetElementType:"line",dataElementType:"point",indexAxis:"r",showLine:!0,elements:{line:{fill:"start"}}};static overrides={aspectRatio:1,scales:{r:{type:"radialLinear"}}};getLabelAndValue(t){const e=this._cachedMeta.vScale,i=this.getParsed(t);return{label:e.getLabels()[t],value:""+e.getLabelForValue(i[e.axis])}}parseObjectData(t,e,i,s){return ii.bind(this)(t,e,i,s)}update(t){const e=this._cachedMeta,i=e.dataset,s=e.data||[],n=e.iScale.getLabels();if(i.points=s,"resize"!==t){const e=this.resolveDatasetElementOptions(t);this.options.showLine||(e.borderWidth=0);const o={_loop:!0,_fullLoop:n.length===s.length,options:e};this.updateElement(i,void 0,o,t)}this.updateElements(s,0,s.length,t)}updateElements(t,e,i,s){const n=this._cachedMeta.rScale,o="reset"===s;for(let a=e;a0&&this.getParsed(e-1);for(let c=e;c0&&Math.abs(i[f]-_[f])>b,m&&(p.parsed=i,p.raw=h.data[c]),u&&(p.options=d||this.resolveDataElementOptions(c,e.active?"active":n)),x||this.updateElement(e,c,p,n),_=i}this.updateSharedOptions(d,n,c)}getMaxOverflow(){const t=this._cachedMeta,e=t.data||[];if(!this.options.showLine){let t=0;for(let i=e.length-1;i>=0;--i)t=Math.max(t,e[i].size(this.resolveDataElementOptions(i))/2);return t>0&&t}const i=t.dataset,s=i.options&&i.options.borderWidth||0;if(!e.length)return s;const n=e[0].size(this.resolveDataElementOptions(0)),o=e[e.length-1].size(this.resolveDataElementOptions(e.length-1));return Math.max(s,n,o)/2}}});function Un(t,e,i,s){const n=vi(t.options.borderRadius,["outerStart","outerEnd","innerStart","innerEnd"]);const o=(i-e)/2,a=Math.min(o,s*e/2),r=t=>{const e=(i-Math.min(o,t))*s/2;return J(t,0,Math.min(o,e))};return{outerStart:r(n.outerStart),outerEnd:r(n.outerEnd),innerStart:J(n.innerStart,0,a),innerEnd:J(n.innerEnd,0,a)}}function Xn(t,e,i,s){return{x:i+t*Math.cos(e),y:s+t*Math.sin(e)}}function qn(t,e,i,s,n,o){const{x:a,y:r,startAngle:l,pixelMargin:h,innerRadius:c}=e,d=Math.max(e.outerRadius+s+i-h,0),u=c>0?c+s+i+h:0;let f=0;const g=n-l;if(s){const t=((c>0?c-s:0)+(d>0?d-s:0))/2;f=(g-(0!==t?g*t/(t+s):g))/2}const p=(g-Math.max(.001,g*d-i/C)/d)/2,m=l+p+f,b=n-p-f,{outerStart:x,outerEnd:_,innerStart:y,innerEnd:v}=Un(e,u,d,b-m),M=d-x,w=d-_,k=m+x/M,S=b-_/w,P=u+y,D=u+v,O=m+y/P,A=b-v/D;if(t.beginPath(),o){const e=(k+S)/2;if(t.arc(a,r,d,k,e),t.arc(a,r,d,e,S),_>0){const e=Xn(w,S,a,r);t.arc(e.x,e.y,_,S,b+E)}const i=Xn(D,b,a,r);if(t.lineTo(i.x,i.y),v>0){const e=Xn(D,A,a,r);t.arc(e.x,e.y,v,b+E,A+Math.PI)}const s=(b-v/u+(m+y/u))/2;if(t.arc(a,r,u,b-v/u,s,!0),t.arc(a,r,u,s,m+y/u,!0),y>0){const e=Xn(P,O,a,r);t.arc(e.x,e.y,y,O+Math.PI,m-E)}const n=Xn(M,m,a,r);if(t.lineTo(n.x,n.y),x>0){const e=Xn(M,k,a,r);t.arc(e.x,e.y,x,m-E,k)}}else{t.moveTo(a,r);const e=Math.cos(k)*d+a,i=Math.sin(k)*d+r;t.lineTo(e,i);const s=Math.cos(S)*d+a,n=Math.sin(S)*d+r;t.lineTo(s,n)}t.closePath()}function Kn(t,e,i,s,n){const{fullCircles:o,startAngle:a,circumference:r,options:l}=e,{borderWidth:h,borderJoinStyle:c,borderDash:d,borderDashOffset:u}=l,f="inner"===l.borderAlign;if(!h)return;t.setLineDash(d||[]),t.lineDashOffset=u,f?(t.lineWidth=2*h,t.lineJoin=c||"round"):(t.lineWidth=h,t.lineJoin=c||"bevel");let g=e.endAngle;if(o){qn(t,e,i,s,g,n);for(let e=0;en?(h=n/l,t.arc(o,a,l,i+h,s-h,!0)):t.arc(o,a,n,i+E,s-E),t.closePath(),t.clip()}(t,e,g),o||(qn(t,e,i,s,g,n),t.stroke())}function Gn(t,e,i=e){t.lineCap=l(i.borderCapStyle,e.borderCapStyle),t.setLineDash(l(i.borderDash,e.borderDash)),t.lineDashOffset=l(i.borderDashOffset,e.borderDashOffset),t.lineJoin=l(i.borderJoinStyle,e.borderJoinStyle),t.lineWidth=l(i.borderWidth,e.borderWidth),t.strokeStyle=l(i.borderColor,e.borderColor)}function Zn(t,e,i){t.lineTo(i.x,i.y)}function Jn(t,e,i={}){const s=t.length,{start:n=0,end:o=s-1}=i,{start:a,end:r}=e,l=Math.max(n,a),h=Math.min(o,r),c=nr&&o>r;return{count:s,start:l,loop:e.loop,ilen:h(a+(h?r-t:t))%o,_=()=>{f!==g&&(t.lineTo(m,g),t.lineTo(m,f),t.lineTo(m,p))};for(l&&(d=n[x(0)],t.moveTo(d.x,d.y)),c=0;c<=r;++c){if(d=n[x(c)],d.skip)continue;const e=d.x,i=d.y,s=0|e;s===u?(ig&&(g=i),m=(b*m+e)/++b):(_(),t.lineTo(e,i),u=s,b=0,f=g=i),p=i}_()}function eo(t){const e=t.options,i=e.borderDash&&e.borderDash.length;return!(t._decimated||t._loop||e.tension||"monotone"===e.cubicInterpolationMode||e.stepped||i)?to:Qn}const io="function"==typeof Path2D;function so(t,e,i,s){io&&!e.options.segment?function(t,e,i,s){let n=e._path;n||(n=e._path=new Path2D,e.path(n,i,s)&&n.closePath()),Gn(t,e.options),t.stroke(n)}(t,e,i,s):function(t,e,i,s){const{segments:n,options:o}=e,a=eo(e);for(const r of n)Gn(t,o,r.style),t.beginPath(),a(t,e,r,{start:i,end:i+s-1})&&t.closePath(),t.stroke()}(t,e,i,s)}class no extends Hs{static id="line";static defaults={borderCapStyle:"butt",borderDash:[],borderDashOffset:0,borderJoinStyle:"miter",borderWidth:3,capBezierPoints:!0,cubicInterpolationMode:"default",fill:!1,spanGaps:!1,stepped:!1,tension:0};static defaultRoutes={backgroundColor:"backgroundColor",borderColor:"borderColor"};static descriptors={_scriptable:!0,_indexable:t=>"borderDash"!==t&&"fill"!==t};constructor(t){super(),this.animated=!0,this.options=void 0,this._chart=void 0,this._loop=void 0,this._fullLoop=void 0,this._path=void 0,this._points=void 0,this._segments=void 0,this._decimated=!1,this._pointsUpdated=!1,this._datasetIndex=void 0,t&&Object.assign(this,t)}updateControlPoints(t,e){const i=this.options;if((i.tension||"monotone"===i.cubicInterpolationMode)&&!i.stepped&&!this._pointsUpdated){const s=i.spanGaps?this._loop:this._fullLoop;hi(this._points,i,t,s,e),this._pointsUpdated=!0}}set points(t){this._points=t,delete this._segments,delete this._path,this._pointsUpdated=!1}get points(){return this._points}get segments(){return this._segments||(this._segments=zi(this,this.options.segment))}first(){const t=this.segments,e=this.points;return t.length&&e[t[0].start]}last(){const t=this.segments,e=this.points,i=t.length;return i&&e[t[i-1].end]}interpolate(t,e){const i=this.options,s=t[e],n=this.points,o=Ii(this,{property:e,start:s,end:s});if(!o.length)return;const a=[],r=function(t){return t.stepped?pi:t.tension||"monotone"===t.cubicInterpolationMode?mi:gi}(i);let l,h;for(l=0,h=o.length;l"borderDash"!==t};circumference;endAngle;fullCircles;innerRadius;outerRadius;pixelMargin;startAngle;constructor(t){super(),this.options=void 0,this.circumference=void 0,this.startAngle=void 0,this.endAngle=void 0,this.innerRadius=void 0,this.outerRadius=void 0,this.pixelMargin=0,this.fullCircles=0,t&&Object.assign(this,t)}inRange(t,e,i){const s=this.getProps(["x","y"],i),{angle:n,distance:o}=X(s,{x:t,y:e}),{startAngle:a,endAngle:r,innerRadius:h,outerRadius:c,circumference:d}=this.getProps(["startAngle","endAngle","innerRadius","outerRadius","circumference"],i),u=(this.options.spacing+this.options.borderWidth)/2,f=l(d,r-a)>=O||Z(n,a,r),g=tt(o,h+u,c+u);return f&&g}getCenterPoint(t){const{x:e,y:i,startAngle:s,endAngle:n,innerRadius:o,outerRadius:a}=this.getProps(["x","y","startAngle","endAngle","innerRadius","outerRadius"],t),{offset:r,spacing:l}=this.options,h=(s+n)/2,c=(o+a+l+r)/2;return{x:e+Math.cos(h)*c,y:i+Math.sin(h)*c}}tooltipPosition(t){return this.getCenterPoint(t)}draw(t){const{options:e,circumference:i}=this,s=(e.offset||0)/4,n=(e.spacing||0)/2,o=e.circular;if(this.pixelMargin="inner"===e.borderAlign?.33:0,this.fullCircles=i>O?Math.floor(i/O):0,0===i||this.innerRadius<0||this.outerRadius<0)return;t.save();const a=(this.startAngle+this.endAngle)/2;t.translate(Math.cos(a)*s,Math.sin(a)*s);const r=s*(1-Math.sin(Math.min(C,i||0)));t.fillStyle=e.backgroundColor,t.strokeStyle=e.borderColor,function(t,e,i,s,n){const{fullCircles:o,startAngle:a,circumference:r}=e;let l=e.endAngle;if(o){qn(t,e,i,s,l,n);for(let e=0;e("string"==typeof e?(i=t.push(e)-1,s.unshift({index:i,label:e})):isNaN(e)&&(i=null),i))(t,e,i,s);return n!==t.lastIndexOf(e)?i:n}function po(t){const e=this.getLabels();return t>=0&&ts=e?s:t,a=t=>n=i?n:t;if(t){const t=F(s),e=F(n);t<0&&e<0?a(0):t>0&&e>0&&o(0)}if(s===n){let e=0===n?1:Math.abs(.05*n);a(n+e),t||o(s-e)}this.min=s,this.max=n}getTickLimit(){const t=this.options.ticks;let e,{maxTicksLimit:i,stepSize:s}=t;return s?(e=Math.ceil(this.max/s)-Math.floor(this.min/s)+1,e>1e3&&(console.warn(`scales.${this.id}.ticks.stepSize: ${s} would result generating up to ${e} ticks. Limiting to 1000.`),e=1e3)):(e=this.computeTickLimit(),i=i||11),i&&(e=Math.min(i,e)),e}computeTickLimit(){return Number.POSITIVE_INFINITY}buildTicks(){const t=this.options,e=t.ticks;let i=this.getTickLimit();i=Math.max(2,i);const n=function(t,e){const i=[],{bounds:n,step:o,min:a,max:r,precision:l,count:h,maxTicks:c,maxDigits:d,includeBounds:u}=t,f=o||1,g=c-1,{min:p,max:m}=e,b=!s(a),x=!s(r),_=!s(h),y=(m-p)/(d+1);let v,M,w,k,S=B((m-p)/g/f)*f;if(S<1e-14&&!b&&!x)return[{value:p},{value:m}];k=Math.ceil(m/S)-Math.floor(p/S),k>g&&(S=B(k*S/g/f)*f),s(l)||(v=Math.pow(10,l),S=Math.ceil(S*v)/v),"ticks"===n?(M=Math.floor(p/S)*S,w=Math.ceil(m/S)*S):(M=p,w=m),b&&x&&o&&H((r-a)/o,S/1e3)?(k=Math.round(Math.min((r-a)/S,c)),S=(r-a)/k,M=a,w=r):_?(M=b?a:M,w=x?r:w,k=h-1,S=(w-M)/k):(k=(w-M)/S,k=V(k,Math.round(k),S/1e3)?Math.round(k):Math.ceil(k));const P=Math.max(U(S),U(M));v=Math.pow(10,s(l)?P:l),M=Math.round(M*v)/v,w=Math.round(w*v)/v;let D=0;for(b&&(u&&M!==a?(i.push({value:a}),Mr)break;i.push({value:t})}return x&&u&&w!==r?i.length&&V(i[i.length-1].value,r,mo(r,y,t))?i[i.length-1].value=r:i.push({value:r}):x&&w!==r||i.push({value:w}),i}({maxTicks:i,bounds:t.bounds,min:t.min,max:t.max,precision:e.precision,step:e.stepSize,count:e.count,maxDigits:this._maxDigits(),horizontal:this.isHorizontal(),minRotation:e.minRotation||0,includeBounds:!1!==e.includeBounds},this._range||this);return"ticks"===t.bounds&&j(n,this,"value"),t.reverse?(n.reverse(),this.start=this.max,this.end=this.min):(this.start=this.min,this.end=this.max),n}configure(){const t=this.ticks;let e=this.min,i=this.max;if(super.configure(),this.options.offset&&t.length){const s=(i-e)/Math.max(t.length-1,1)/2;e-=s,i+=s}this._startValue=e,this._endValue=i,this._valueRange=i-e}getLabelForValue(t){return ne(t,this.chart.options.locale,this.options.ticks.format)}}class xo extends bo{static id="linear";static defaults={ticks:{callback:ae.formatters.numeric}};determineDataLimits(){const{min:t,max:e}=this.getMinMax(!0);this.min=a(t)?t:0,this.max=a(e)?e:1,this.handleTickRangeOptions()}computeTickLimit(){const t=this.isHorizontal(),e=t?this.width:this.height,i=$(this.options.ticks.minRotation),s=(t?Math.sin(i):Math.cos(i))||.001,n=this._resolveTickFontOptions(0);return Math.ceil(e/Math.min(40,n.lineHeight/s))}getPixelForValue(t){return null===t?NaN:this.getPixelForDecimal((t-this._startValue)/this._valueRange)}getValueForPixel(t){return this._startValue+this.getDecimalForPixel(t)*this._valueRange}}const _o=t=>Math.floor(z(t)),yo=(t,e)=>Math.pow(10,_o(t)+e);function vo(t){return 1===t/Math.pow(10,_o(t))}function Mo(t,e,i){const s=Math.pow(10,i),n=Math.floor(t/s);return Math.ceil(e/s)-n}function wo(t,{min:e,max:i}){e=r(t.min,e);const s=[],n=_o(e);let o=function(t,e){let i=_o(e-t);for(;Mo(t,e,i)>10;)i++;for(;Mo(t,e,i)<10;)i--;return Math.min(i,_o(t))}(e,i),a=o<0?Math.pow(10,Math.abs(o)):1;const l=Math.pow(10,o),h=n>o?Math.pow(10,n):0,c=Math.round((e-h)*a)/a,d=Math.floor((e-h)/l/10)*l*10;let u=Math.floor((c-d)/Math.pow(10,o)),f=r(t.min,Math.round((h+d+u*Math.pow(10,o))*a)/a);for(;f=10?u=u<15?15:20:u++,u>=20&&(o++,u=2,a=o>=0?1:a),f=Math.round((h+d+u*Math.pow(10,o))*a)/a;const g=r(t.max,f);return s.push({value:g,major:vo(g),significand:u}),s}class ko extends Js{static id="logarithmic";static defaults={ticks:{callback:ae.formatters.logarithmic,major:{enabled:!0}}};constructor(t){super(t),this.start=void 0,this.end=void 0,this._startValue=void 0,this._valueRange=0}parse(t,e){const i=bo.prototype.parse.apply(this,[t,e]);if(0!==i)return a(i)&&i>0?i:null;this._zero=!0}determineDataLimits(){const{min:t,max:e}=this.getMinMax(!0);this.min=a(t)?Math.max(0,t):null,this.max=a(e)?Math.max(0,e):null,this.options.beginAtZero&&(this._zero=!0),this._zero&&this.min!==this._suggestedMin&&!a(this._userMin)&&(this.min=t===yo(this.min,0)?yo(this.min,-1):yo(this.min,0)),this.handleTickRangeOptions()}handleTickRangeOptions(){const{minDefined:t,maxDefined:e}=this.getUserBounds();let i=this.min,s=this.max;const n=e=>i=t?i:e,o=t=>s=e?s:t;i===s&&(i<=0?(n(1),o(10)):(n(yo(i,-1)),o(yo(s,1)))),i<=0&&n(yo(s,-1)),s<=0&&o(yo(i,1)),this.min=i,this.max=s}buildTicks(){const t=this.options,e=wo({min:this._userMin,max:this._userMax},this);return"ticks"===t.bounds&&j(e,this,"value"),t.reverse?(e.reverse(),this.start=this.max,this.end=this.min):(this.start=this.min,this.end=this.max),e}getLabelForValue(t){return void 0===t?"0":ne(t,this.chart.options.locale,this.options.ticks.format)}configure(){const t=this.min;super.configure(),this._startValue=z(t),this._valueRange=z(this.max)-z(t)}getPixelForValue(t){return void 0!==t&&0!==t||(t=this.min),null===t||isNaN(t)?NaN:this.getPixelForDecimal(t===this.min?0:(z(t)-this._startValue)/this._valueRange)}getValueForPixel(t){const e=this.getDecimalForPixel(t);return Math.pow(10,this._startValue+e*this._valueRange)}}function So(t){const e=t.ticks;if(e.display&&t.display){const t=ki(e.backdropPadding);return l(e.font&&e.font.size,ue.font.size)+t.height}return 0}function Po(t,e,i,s,n){return t===s||t===n?{start:e-i/2,end:e+i/2}:tn?{start:e-i,end:e}:{start:e,end:e+i}}function Do(t){const e={l:t.left+t._padding.left,r:t.right-t._padding.right,t:t.top+t._padding.top,b:t.bottom-t._padding.bottom},i=Object.assign({},e),s=[],o=[],a=t._pointLabels.length,r=t.options.pointLabels,l=r.centerPointLabels?C/a:0;for(let u=0;ue.r&&(r=(s.end-e.r)/o,t.r=Math.max(t.r,e.r+r)),n.starte.b&&(l=(n.end-e.b)/a,t.b=Math.max(t.b,e.b+l))}function Oo(t,e,i){const s=t.drawingArea,{extra:n,additionalAngle:o,padding:a,size:r}=i,l=t.getPointPosition(e,s+n+a,o),h=Math.round(Y(G(l.angle+E))),c=function(t,e,i){90===i||270===i?t-=e/2:(i>270||i<90)&&(t-=e);return t}(l.y,r.h,h),d=function(t){if(0===t||180===t)return"center";if(t<180)return"left";return"right"}(h),u=function(t,e,i){"right"===i?t-=e:"center"===i&&(t-=e/2);return t}(l.x,r.w,d);return{visible:!0,x:l.x,y:c,textAlign:d,left:u,top:c,right:u+r.w,bottom:c+r.h}}function Ao(t,e){if(!e)return!0;const{left:i,top:s,right:n,bottom:o}=t;return!(Re({x:i,y:s},e)||Re({x:i,y:o},e)||Re({x:n,y:s},e)||Re({x:n,y:o},e))}function To(t,e,i){const{left:n,top:o,right:a,bottom:r}=i,{backdropColor:l}=e;if(!s(l)){const i=wi(e.borderRadius),s=ki(e.backdropPadding);t.fillStyle=l;const h=n-s.left,c=o-s.top,d=a-n+s.width,u=r-o+s.height;Object.values(i).some((t=>0!==t))?(t.beginPath(),He(t,{x:h,y:c,w:d,h:u,radius:i}),t.fill()):t.fillRect(h,c,d,u)}}function Lo(t,e,i,s){const{ctx:n}=t;if(i)n.arc(t.xCenter,t.yCenter,e,0,O);else{let i=t.getPointPosition(0,e);n.moveTo(i.x,i.y);for(let o=1;ot,padding:5,centerPointLabels:!1}};static defaultRoutes={"angleLines.color":"borderColor","pointLabels.color":"color","ticks.color":"color"};static descriptors={angleLines:{_fallback:"grid"}};constructor(t){super(t),this.xCenter=void 0,this.yCenter=void 0,this.drawingArea=void 0,this._pointLabels=[],this._pointLabelItems=[]}setDimensions(){const t=this._padding=ki(So(this.options)/2),e=this.width=this.maxWidth-t.width,i=this.height=this.maxHeight-t.height;this.xCenter=Math.floor(this.left+e/2+t.left),this.yCenter=Math.floor(this.top+i/2+t.top),this.drawingArea=Math.floor(Math.min(e,i)/2)}determineDataLimits(){const{min:t,max:e}=this.getMinMax(!1);this.min=a(t)&&!isNaN(t)?t:0,this.max=a(e)&&!isNaN(e)?e:0,this.handleTickRangeOptions()}computeTickLimit(){return Math.ceil(this.drawingArea/So(this.options))}generateTickLabels(t){bo.prototype.generateTickLabels.call(this,t),this._pointLabels=this.getLabels().map(((t,e)=>{const i=d(this.options.pointLabels.callback,[t,e],this);return i||0===i?i:""})).filter(((t,e)=>this.chart.getDataVisibility(e)))}fit(){const t=this.options;t.display&&t.pointLabels.display?Do(this):this.setCenterPoint(0,0,0,0)}setCenterPoint(t,e,i,s){this.xCenter+=Math.floor((t-e)/2),this.yCenter+=Math.floor((i-s)/2),this.drawingArea-=Math.min(this.drawingArea/2,Math.max(t,e,i,s))}getIndexAngle(t){return G(t*(O/(this._pointLabels.length||1))+$(this.options.startAngle||0))}getDistanceFromCenterForValue(t){if(s(t))return NaN;const e=this.drawingArea/(this.max-this.min);return this.options.reverse?(this.max-t)*e:(t-this.min)*e}getValueForDistanceFromCenter(t){if(s(t))return NaN;const e=t/(this.drawingArea/(this.max-this.min));return this.options.reverse?this.max-e:this.min+e}getPointLabelContext(t){const e=this._pointLabels||[];if(t>=0&&t=0;n--){const e=t._pointLabelItems[n];if(!e.visible)continue;const o=s.setContext(t.getPointLabelContext(n));To(i,o,e);const a=Si(o.font),{x:r,y:l,textAlign:h}=e;Ne(i,t._pointLabels[n],r,l+a.lineHeight/2,a,{color:o.color,textAlign:h,textBaseline:"middle"})}}(this,o),s.display&&this.ticks.forEach(((t,e)=>{if(0!==e){r=this.getDistanceFromCenterForValue(t.value);const i=this.getContext(e),a=s.setContext(i),l=n.setContext(i);!function(t,e,i,s,n){const o=t.ctx,a=e.circular,{color:r,lineWidth:l}=e;!a&&!s||!r||!l||i<0||(o.save(),o.strokeStyle=r,o.lineWidth=l,o.setLineDash(n.dash),o.lineDashOffset=n.dashOffset,o.beginPath(),Lo(t,i,a,s),o.closePath(),o.stroke(),o.restore())}(this,a,r,o,l)}})),i.display){for(t.save(),a=o-1;a>=0;a--){const s=i.setContext(this.getPointLabelContext(a)),{color:n,lineWidth:o}=s;o&&n&&(t.lineWidth=o,t.strokeStyle=n,t.setLineDash(s.borderDash),t.lineDashOffset=s.borderDashOffset,r=this.getDistanceFromCenterForValue(e.ticks.reverse?this.min:this.max),l=this.getPointPosition(a,r),t.beginPath(),t.moveTo(this.xCenter,this.yCenter),t.lineTo(l.x,l.y),t.stroke())}t.restore()}}drawBorder(){}drawLabels(){const t=this.ctx,e=this.options,i=e.ticks;if(!i.display)return;const s=this.getIndexAngle(0);let n,o;t.save(),t.translate(this.xCenter,this.yCenter),t.rotate(s),t.textAlign="center",t.textBaseline="middle",this.ticks.forEach(((s,a)=>{if(0===a&&!e.reverse)return;const r=i.setContext(this.getContext(a)),l=Si(r.font);if(n=this.getDistanceFromCenterForValue(this.ticks[a].value),r.showLabelBackdrop){t.font=l.string,o=t.measureText(s.label).width,t.fillStyle=r.backdropColor;const e=ki(r.backdropPadding);t.fillRect(-o/2-e.left,-n-l.size/2-e.top,o+e.width,l.size+e.height)}Ne(t,s.label,0,-n,l,{color:r.color,strokeColor:r.textStrokeColor,strokeWidth:r.textStrokeWidth})})),t.restore()}drawTitle(){}}const Ro={millisecond:{common:!0,size:1,steps:1e3},second:{common:!0,size:1e3,steps:60},minute:{common:!0,size:6e4,steps:60},hour:{common:!0,size:36e5,steps:24},day:{common:!0,size:864e5,steps:30},week:{common:!1,size:6048e5,steps:4},month:{common:!0,size:2628e6,steps:12},quarter:{common:!1,size:7884e6,steps:4},year:{common:!0,size:3154e7}},Io=Object.keys(Ro);function zo(t,e){return t-e}function Fo(t,e){if(s(e))return null;const i=t._adapter,{parser:n,round:o,isoWeekday:r}=t._parseOpts;let l=e;return"function"==typeof n&&(l=n(l)),a(l)||(l="string"==typeof n?i.parse(l,n):i.parse(l)),null===l?null:(o&&(l="week"!==o||!N(r)&&!0!==r?i.startOf(l,o):i.startOf(l,"isoWeek",r)),+l)}function Vo(t,e,i,s){const n=Io.length;for(let o=Io.indexOf(t);o=e?i[s]:i[n]]=!0}}else t[e]=!0}function Wo(t,e,i){const s=[],n={},o=e.length;let a,r;for(a=0;a=0&&(e[l].major=!0);return e}(t,s,n,i):s}class No extends Js{static id="time";static defaults={bounds:"data",adapters:{},time:{parser:!1,unit:!1,round:!1,isoWeekday:!1,minUnit:"millisecond",displayFormats:{}},ticks:{source:"auto",callback:!1,major:{enabled:!1}}};constructor(t){super(t),this._cache={data:[],labels:[],all:[]},this._unit="day",this._majorUnit=void 0,this._offsets={},this._normalized=!1,this._parseOpts=void 0}init(t,e={}){const i=t.time||(t.time={}),s=this._adapter=new Rn._date(t.adapters.date);s.init(e),x(i.displayFormats,s.formats()),this._parseOpts={parser:i.parser,round:i.round,isoWeekday:i.isoWeekday},super.init(t),this._normalized=e.normalized}parse(t,e){return void 0===t?null:Fo(this,t)}beforeLayout(){super.beforeLayout(),this._cache={data:[],labels:[],all:[]}}determineDataLimits(){const t=this.options,e=this._adapter,i=t.time.unit||"day";let{min:s,max:n,minDefined:o,maxDefined:r}=this.getUserBounds();function l(t){o||isNaN(t.min)||(s=Math.min(s,t.min)),r||isNaN(t.max)||(n=Math.max(n,t.max))}o&&r||(l(this._getLabelBounds()),"ticks"===t.bounds&&"labels"===t.ticks.source||l(this.getMinMax(!1))),s=a(s)&&!isNaN(s)?s:+e.startOf(Date.now(),i),n=a(n)&&!isNaN(n)?n:+e.endOf(Date.now(),i)+1,this.min=Math.min(s,n-1),this.max=Math.max(s+1,n)}_getLabelBounds(){const t=this.getLabelTimestamps();let e=Number.POSITIVE_INFINITY,i=Number.NEGATIVE_INFINITY;return t.length&&(e=t[0],i=t[t.length-1]),{min:e,max:i}}buildTicks(){const t=this.options,e=t.time,i=t.ticks,s="labels"===i.source?this.getLabelTimestamps():this._generate();"ticks"===t.bounds&&s.length&&(this.min=this._userMin||s[0],this.max=this._userMax||s[s.length-1]);const n=this.min,o=nt(s,n,this.max);return this._unit=e.unit||(i.autoSkip?Vo(e.minUnit,this.min,this.max,this._getLabelCapacity(n)):function(t,e,i,s,n){for(let o=Io.length-1;o>=Io.indexOf(i);o--){const i=Io[o];if(Ro[i].common&&t._adapter.diff(n,s,i)>=e-1)return i}return Io[i?Io.indexOf(i):0]}(this,o.length,e.minUnit,this.min,this.max)),this._majorUnit=i.major.enabled&&"year"!==this._unit?function(t){for(let e=Io.indexOf(t)+1,i=Io.length;e+t.value)))}initOffsets(t=[]){let e,i,s=0,n=0;this.options.offset&&t.length&&(e=this.getDecimalForValue(t[0]),s=1===t.length?1-e:(this.getDecimalForValue(t[1])-e)/2,i=this.getDecimalForValue(t[t.length-1]),n=1===t.length?i:(i-this.getDecimalForValue(t[t.length-2]))/2);const o=t.length<3?.5:.25;s=J(s,0,o),n=J(n,0,o),this._offsets={start:s,end:n,factor:1/(s+1+n)}}_generate(){const t=this._adapter,e=this.min,i=this.max,s=this.options,n=s.time,o=n.unit||Vo(n.minUnit,e,i,this._getLabelCapacity(e)),a=l(s.ticks.stepSize,1),r="week"===o&&n.isoWeekday,h=N(r)||!0===r,c={};let d,u,f=e;if(h&&(f=+t.startOf(f,"isoWeek",r)),f=+t.startOf(f,h?"day":o),t.diff(i,e,o)>1e5*a)throw new Error(e+" and "+i+" are too far apart with stepSize of "+a+" "+o);const g="data"===s.ticks.source&&this.getDataTimestamps();for(d=f,u=0;d+t))}getLabelForValue(t){const e=this._adapter,i=this.options.time;return i.tooltipFormat?e.format(t,i.tooltipFormat):e.format(t,i.displayFormats.datetime)}format(t,e){const i=this.options.time.displayFormats,s=this._unit,n=e||i[s];return this._adapter.format(t,n)}_tickFormatFunction(t,e,i,s){const n=this.options,o=n.ticks.callback;if(o)return d(o,[t,e,i],this);const a=n.time.displayFormats,r=this._unit,l=this._majorUnit,h=r&&a[r],c=l&&a[l],u=i[e],f=l&&c&&u&&u.major;return this._adapter.format(t,s||(f?c:h))}generateTickLabels(t){let e,i,s;for(e=0,i=t.length;e0?a:1}getDataTimestamps(){let t,e,i=this._cache.data||[];if(i.length)return i;const s=this.getMatchingVisibleMetas();if(this._normalized&&s.length)return this._cache.data=s[0].controller.getAllParsedValues(this);for(t=0,e=s.length;t=t[r].pos&&e<=t[l].pos&&({lo:r,hi:l}=it(t,"pos",e)),({pos:s,time:o}=t[r]),({pos:n,time:a}=t[l])):(e>=t[r].time&&e<=t[l].time&&({lo:r,hi:l}=it(t,"time",e)),({time:s,pos:o}=t[r]),({time:n,pos:a}=t[l]));const h=n-s;return h?o+(a-o)*(e-s)/h:o}var jo=Object.freeze({__proto__:null,CategoryScale:class extends Js{static id="category";static defaults={ticks:{callback:po}};constructor(t){super(t),this._startValue=void 0,this._valueRange=0,this._addedLabels=[]}init(t){const e=this._addedLabels;if(e.length){const t=this.getLabels();for(const{index:i,label:s}of e)t[i]===s&&t.splice(i,1);this._addedLabels=[]}super.init(t)}parse(t,e){if(s(t))return null;const i=this.getLabels();return((t,e)=>null===t?null:J(Math.round(t),0,e))(e=isFinite(e)&&i[e]===t?e:go(i,t,l(e,t),this._addedLabels),i.length-1)}determineDataLimits(){const{minDefined:t,maxDefined:e}=this.getUserBounds();let{min:i,max:s}=this.getMinMax(!0);"ticks"===this.options.bounds&&(t||(i=0),e||(s=this.getLabels().length-1)),this.min=i,this.max=s}buildTicks(){const t=this.min,e=this.max,i=this.options.offset,s=[];let n=this.getLabels();n=0===t&&e===n.length-1?n:n.slice(t,e+1),this._valueRange=Math.max(n.length-(i?0:1),1),this._startValue=this.min-(i?.5:0);for(let i=t;i<=e;i++)s.push({value:i});return s}getLabelForValue(t){return po.call(this,t)}configure(){super.configure(),this.isHorizontal()||(this._reversePixels=!this._reversePixels)}getPixelForValue(t){return"number"!=typeof t&&(t=this.parse(t)),null===t?NaN:this.getPixelForDecimal((t-this._startValue)/this._valueRange)}getPixelForTick(t){const e=this.ticks;return t<0||t>e.length-1?null:this.getPixelForValue(e[t].value)}getValueForPixel(t){return Math.round(this._startValue+this.getDecimalForPixel(t)*this._valueRange)}getBasePixel(){return this.bottom}},LinearScale:xo,LogarithmicScale:ko,RadialLinearScale:Eo,TimeScale:No,TimeSeriesScale:class extends No{static id="timeseries";static defaults=No.defaults;constructor(t){super(t),this._table=[],this._minPos=void 0,this._tableRange=void 0}initOffsets(){const t=this._getTimestampsForTable(),e=this._table=this.buildLookupTable(t);this._minPos=Ho(e,this.min),this._tableRange=Ho(e,this.max)-this._minPos,super.initOffsets(t)}buildLookupTable(t){const{min:e,max:i}=this,s=[],n=[];let o,a,r,l,h;for(o=0,a=t.length;o=e&&l<=i&&s.push(l);if(s.length<2)return[{time:e,pos:0},{time:i,pos:1}];for(o=0,a=s.length;ot-e))}_getTimestampsForTable(){let t=this._cache.all||[];if(t.length)return t;const e=this.getDataTimestamps(),i=this.getLabelTimestamps();return t=e.length&&i.length?this.normalize(e.concat(i)):e.length?e:i,t=this._cache.all=t,t}getDecimalForValue(t){return(Ho(this._table,t)-this._minPos)/this._tableRange}getValueForPixel(t){const e=this._offsets,i=this.getDecimalForPixel(t)/e.factor-e.end;return Ho(this._table,i*this._tableRange+this._minPos,!0)}}});const $o=["rgb(54, 162, 235)","rgb(255, 99, 132)","rgb(255, 159, 64)","rgb(255, 205, 86)","rgb(75, 192, 192)","rgb(153, 102, 255)","rgb(201, 203, 207)"],Yo=$o.map((t=>t.replace("rgb(","rgba(").replace(")",", 0.5)")));function Uo(t){return $o[t%$o.length]}function Xo(t){return Yo[t%Yo.length]}function qo(t){let e=0;return(i,s)=>{const n=t.getDatasetMeta(s).controller;n instanceof jn?e=function(t,e){return t.backgroundColor=t.data.map((()=>Uo(e++))),e}(i,e):n instanceof $n?e=function(t,e){return t.backgroundColor=t.data.map((()=>Xo(e++))),e}(i,e):n&&(e=function(t,e){return t.borderColor=Uo(e),t.backgroundColor=Xo(e),++e}(i,e))}}function Ko(t){let e;for(e in t)if(t[e].borderColor||t[e].backgroundColor)return!0;return!1}var Go={id:"colors",defaults:{enabled:!0,forceOverride:!1},beforeLayout(t,e,i){if(!i.enabled)return;const{data:{datasets:s},options:n}=t.config,{elements:o}=n;if(!i.forceOverride&&(Ko(s)||(a=n)&&(a.borderColor||a.backgroundColor)||o&&Ko(o)))return;var a;const r=qo(t);s.forEach(r)}};function Zo(t){if(t._decimated){const e=t._data;delete t._decimated,delete t._data,Object.defineProperty(t,"data",{configurable:!0,enumerable:!0,writable:!0,value:e})}}function Jo(t){t.data.datasets.forEach((t=>{Zo(t)}))}var Qo={id:"decimation",defaults:{algorithm:"min-max",enabled:!1},beforeElementsUpdate:(t,e,i)=>{if(!i.enabled)return void Jo(t);const n=t.width;t.data.datasets.forEach(((e,o)=>{const{_data:a,indexAxis:r}=e,l=t.getDatasetMeta(o),h=a||e.data;if("y"===Pi([r,t.options.indexAxis]))return;if(!l.controller.supportsDecimation)return;const c=t.scales[l.xAxisID];if("linear"!==c.type&&"time"!==c.type)return;if(t.options.parsing)return;let{start:d,count:u}=function(t,e){const i=e.length;let s,n=0;const{iScale:o}=t,{min:a,max:r,minDefined:l,maxDefined:h}=o.getUserBounds();return l&&(n=J(it(e,o.axis,a).lo,0,i-1)),s=h?J(it(e,o.axis,r).hi+1,n,i)-n:i-n,{start:n,count:s}}(l,h);if(u<=(i.threshold||4*n))return void Zo(e);let f;switch(s(a)&&(e._data=h,delete e.data,Object.defineProperty(e,"data",{configurable:!0,enumerable:!0,get:function(){return this._decimated},set:function(t){this._data=t}})),i.algorithm){case"lttb":f=function(t,e,i,s,n){const o=n.samples||s;if(o>=i)return t.slice(e,e+i);const a=[],r=(i-2)/(o-2);let l=0;const h=e+i-1;let c,d,u,f,g,p=e;for(a[l++]=t[p],c=0;cu&&(u=f,d=t[s],g=s);a[l++]=d,p=g}return a[l++]=t[h],a}(h,d,u,n,i);break;case"min-max":f=function(t,e,i,n){let o,a,r,l,h,c,d,u,f,g,p=0,m=0;const b=[],x=e+i-1,_=t[e].x,y=t[x].x-_;for(o=e;og&&(g=l,d=o),p=(m*p+a.x)/++m;else{const i=o-1;if(!s(c)&&!s(d)){const e=Math.min(c,d),s=Math.max(c,d);e!==u&&e!==i&&b.push({...t[e],x:p}),s!==u&&s!==i&&b.push({...t[s],x:p})}o>0&&i!==u&&b.push(t[i]),b.push(a),h=e,m=0,f=g=l,c=d=u=o}}return b}(h,d,u,n);break;default:throw new Error(`Unsupported decimation algorithm '${i.algorithm}'`)}e._decimated=f}))},destroy(t){Jo(t)}};function ta(t,e,i,s){if(s)return;let n=e[t],o=i[t];return"angle"===t&&(n=G(n),o=G(o)),{property:t,start:n,end:o}}function ea(t,e,i){for(;e>t;e--){const t=i[e];if(!isNaN(t.x)&&!isNaN(t.y))break}return e}function ia(t,e,i,s){return t&&e?s(t[i],e[i]):t?t[i]:e?e[i]:0}function sa(t,e){let i=[],s=!1;return n(t)?(s=!0,i=t):i=function(t,e){const{x:i=null,y:s=null}=t||{},n=e.points,o=[];return e.segments.forEach((({start:t,end:e})=>{e=ea(t,e,n);const a=n[t],r=n[e];null!==s?(o.push({x:a.x,y:s}),o.push({x:r.x,y:s})):null!==i&&(o.push({x:i,y:a.y}),o.push({x:i,y:r.y}))})),o}(t,e),i.length?new no({points:i,options:{tension:0},_loop:s,_fullLoop:s}):null}function na(t){return t&&!1!==t.fill}function oa(t,e,i){let s=t[e].fill;const n=[e];let o;if(!i)return s;for(;!1!==s&&-1===n.indexOf(s);){if(!a(s))return s;if(o=t[s],!o)return!1;if(o.visible)return s;n.push(s),s=o.fill}return!1}function aa(t,e,i){const s=function(t){const e=t.options,i=e.fill;let s=l(i&&i.target,i);void 0===s&&(s=!!e.backgroundColor);if(!1===s||null===s)return!1;if(!0===s)return"origin";return s}(t);if(o(s))return!isNaN(s.value)&&s;let n=parseFloat(s);return a(n)&&Math.floor(n)===n?function(t,e,i,s){"-"!==t&&"+"!==t||(i=e+i);if(i===e||i<0||i>=s)return!1;return i}(s[0],e,n,i):["origin","start","end","stack","shape"].indexOf(s)>=0&&s}function ra(t,e,i){const s=[];for(let n=0;n=0;--e){const i=n[e].$filler;i&&(i.line.updateControlPoints(o,i.axis),s&&i.fill&&da(t.ctx,i,o))}},beforeDatasetsDraw(t,e,i){if("beforeDatasetsDraw"!==i.drawTime)return;const s=t.getSortedVisibleDatasetMetas();for(let e=s.length-1;e>=0;--e){const i=s[e].$filler;na(i)&&da(t.ctx,i,t.chartArea)}},beforeDatasetDraw(t,e,i){const s=e.meta.$filler;na(s)&&"beforeDatasetDraw"===i.drawTime&&da(t.ctx,s,t.chartArea)},defaults:{propagate:!0,drawTime:"beforeDatasetDraw"}};const ba=(t,e)=>{let{boxHeight:i=e,boxWidth:s=e}=t;return t.usePointStyle&&(i=Math.min(i,e),s=t.pointStyleWidth||Math.min(s,e)),{boxWidth:s,boxHeight:i,itemHeight:Math.max(e,i)}};class xa extends Hs{constructor(t){super(),this._added=!1,this.legendHitBoxes=[],this._hoveredItem=null,this.doughnutMode=!1,this.chart=t.chart,this.options=t.options,this.ctx=t.ctx,this.legendItems=void 0,this.columnSizes=void 0,this.lineWidths=void 0,this.maxHeight=void 0,this.maxWidth=void 0,this.top=void 0,this.bottom=void 0,this.left=void 0,this.right=void 0,this.height=void 0,this.width=void 0,this._margins=void 0,this.position=void 0,this.weight=void 0,this.fullSize=void 0}update(t,e,i){this.maxWidth=t,this.maxHeight=e,this._margins=i,this.setDimensions(),this.buildLabels(),this.fit()}setDimensions(){this.isHorizontal()?(this.width=this.maxWidth,this.left=this._margins.left,this.right=this.width):(this.height=this.maxHeight,this.top=this._margins.top,this.bottom=this.height)}buildLabels(){const t=this.options.labels||{};let e=d(t.generateLabels,[this.chart],this)||[];t.filter&&(e=e.filter((e=>t.filter(e,this.chart.data)))),t.sort&&(e=e.sort(((e,i)=>t.sort(e,i,this.chart.data)))),this.options.reverse&&e.reverse(),this.legendItems=e}fit(){const{options:t,ctx:e}=this;if(!t.display)return void(this.width=this.height=0);const i=t.labels,s=Si(i.font),n=s.size,o=this._computeTitleHeight(),{boxWidth:a,itemHeight:r}=ba(i,n);let l,h;e.font=s.string,this.isHorizontal()?(l=this.maxWidth,h=this._fitRows(o,n,a,r)+10):(h=this.maxHeight,l=this._fitCols(o,s,a,r)+10),this.width=Math.min(l,t.maxWidth||this.maxWidth),this.height=Math.min(h,t.maxHeight||this.maxHeight)}_fitRows(t,e,i,s){const{ctx:n,maxWidth:o,options:{labels:{padding:a}}}=this,r=this.legendHitBoxes=[],l=this.lineWidths=[0],h=s+a;let c=t;n.textAlign="left",n.textBaseline="middle";let d=-1,u=-h;return this.legendItems.forEach(((t,f)=>{const g=i+e/2+n.measureText(t.text).width;(0===f||l[l.length-1]+g+2*a>o)&&(c+=h,l[l.length-(f>0?0:1)]=0,u+=h,d++),r[f]={left:0,top:u,row:d,width:g,height:s},l[l.length-1]+=g+a})),c}_fitCols(t,e,i,s){const{ctx:n,maxHeight:o,options:{labels:{padding:a}}}=this,r=this.legendHitBoxes=[],l=this.columnSizes=[],h=o-t;let c=a,d=0,u=0,f=0,g=0;return this.legendItems.forEach(((t,o)=>{const{itemWidth:p,itemHeight:m}=function(t,e,i,s,n){const o=function(t,e,i,s){let n=t.text;n&&"string"!=typeof n&&(n=n.reduce(((t,e)=>t.length>e.length?t:e)));return e+i.size/2+s.measureText(n).width}(s,t,e,i),a=function(t,e,i){let s=t;"string"!=typeof e.text&&(s=_a(e,i));return s}(n,s,e.lineHeight);return{itemWidth:o,itemHeight:a}}(i,e,n,t,s);o>0&&u+m+2*a>h&&(c+=d+a,l.push({width:d,height:u}),f+=d+a,g++,d=u=0),r[o]={left:f,top:u,col:g,width:p,height:m},d=Math.max(d,p),u+=m+a})),c+=d,l.push({width:d,height:u}),c}adjustHitBoxes(){if(!this.options.display)return;const t=this._computeTitleHeight(),{legendHitBoxes:e,options:{align:i,labels:{padding:s},rtl:n}}=this,o=Oi(n,this.left,this.width);if(this.isHorizontal()){let n=0,a=ft(i,this.left+s,this.right-this.lineWidths[n]);for(const r of e)n!==r.row&&(n=r.row,a=ft(i,this.left+s,this.right-this.lineWidths[n])),r.top+=this.top+t+s,r.left=o.leftForLtr(o.x(a),r.width),a+=r.width+s}else{let n=0,a=ft(i,this.top+t+s,this.bottom-this.columnSizes[n].height);for(const r of e)r.col!==n&&(n=r.col,a=ft(i,this.top+t+s,this.bottom-this.columnSizes[n].height)),r.top=a,r.left+=this.left+s,r.left=o.leftForLtr(o.x(r.left),r.width),a+=r.height+s}}isHorizontal(){return"top"===this.options.position||"bottom"===this.options.position}draw(){if(this.options.display){const t=this.ctx;Ie(t,this),this._draw(),ze(t)}}_draw(){const{options:t,columnSizes:e,lineWidths:i,ctx:s}=this,{align:n,labels:o}=t,a=ue.color,r=Oi(t.rtl,this.left,this.width),h=Si(o.font),{padding:c}=o,d=h.size,u=d/2;let f;this.drawTitle(),s.textAlign=r.textAlign("left"),s.textBaseline="middle",s.lineWidth=.5,s.font=h.string;const{boxWidth:g,boxHeight:p,itemHeight:m}=ba(o,d),b=this.isHorizontal(),x=this._computeTitleHeight();f=b?{x:ft(n,this.left+c,this.right-i[0]),y:this.top+c+x,line:0}:{x:this.left+c,y:ft(n,this.top+x+c,this.bottom-e[0].height),line:0},Ai(this.ctx,t.textDirection);const _=m+c;this.legendItems.forEach(((y,v)=>{s.strokeStyle=y.fontColor,s.fillStyle=y.fontColor;const M=s.measureText(y.text).width,w=r.textAlign(y.textAlign||(y.textAlign=o.textAlign)),k=g+u+M;let S=f.x,P=f.y;r.setWidth(this.width),b?v>0&&S+k+c>this.right&&(P=f.y+=_,f.line++,S=f.x=ft(n,this.left+c,this.right-i[f.line])):v>0&&P+_>this.bottom&&(S=f.x=S+e[f.line].width+c,f.line++,P=f.y=ft(n,this.top+x+c,this.bottom-e[f.line].height));if(function(t,e,i){if(isNaN(g)||g<=0||isNaN(p)||p<0)return;s.save();const n=l(i.lineWidth,1);if(s.fillStyle=l(i.fillStyle,a),s.lineCap=l(i.lineCap,"butt"),s.lineDashOffset=l(i.lineDashOffset,0),s.lineJoin=l(i.lineJoin,"miter"),s.lineWidth=n,s.strokeStyle=l(i.strokeStyle,a),s.setLineDash(l(i.lineDash,[])),o.usePointStyle){const a={radius:p*Math.SQRT2/2,pointStyle:i.pointStyle,rotation:i.rotation,borderWidth:n},l=r.xPlus(t,g/2);Ee(s,a,l,e+u,o.pointStyleWidth&&g)}else{const o=e+Math.max((d-p)/2,0),a=r.leftForLtr(t,g),l=wi(i.borderRadius);s.beginPath(),Object.values(l).some((t=>0!==t))?He(s,{x:a,y:o,w:g,h:p,radius:l}):s.rect(a,o,g,p),s.fill(),0!==n&&s.stroke()}s.restore()}(r.x(S),P,y),S=gt(w,S+g+u,b?S+k:this.right,t.rtl),function(t,e,i){Ne(s,i.text,t,e+m/2,h,{strikethrough:i.hidden,textAlign:r.textAlign(i.textAlign)})}(r.x(S),P,y),b)f.x+=k+c;else if("string"!=typeof y.text){const t=h.lineHeight;f.y+=_a(y,t)+c}else f.y+=_})),Ti(this.ctx,t.textDirection)}drawTitle(){const t=this.options,e=t.title,i=Si(e.font),s=ki(e.padding);if(!e.display)return;const n=Oi(t.rtl,this.left,this.width),o=this.ctx,a=e.position,r=i.size/2,l=s.top+r;let h,c=this.left,d=this.width;if(this.isHorizontal())d=Math.max(...this.lineWidths),h=this.top+l,c=ft(t.align,c,this.right-d);else{const e=this.columnSizes.reduce(((t,e)=>Math.max(t,e.height)),0);h=l+ft(t.align,this.top,this.bottom-e-t.labels.padding-this._computeTitleHeight())}const u=ft(a,c,c+d);o.textAlign=n.textAlign(ut(a)),o.textBaseline="middle",o.strokeStyle=e.color,o.fillStyle=e.color,o.font=i.string,Ne(o,e.text,u,h,i)}_computeTitleHeight(){const t=this.options.title,e=Si(t.font),i=ki(t.padding);return t.display?e.lineHeight+i.height:0}_getLegendItemAt(t,e){let i,s,n;if(tt(t,this.left,this.right)&&tt(e,this.top,this.bottom))for(n=this.legendHitBoxes,i=0;it.chart.options.color,boxWidth:40,padding:10,generateLabels(t){const e=t.data.datasets,{labels:{usePointStyle:i,pointStyle:s,textAlign:n,color:o,useBorderRadius:a,borderRadius:r}}=t.legend.options;return t._getSortedDatasetMetas().map((t=>{const l=t.controller.getStyle(i?0:void 0),h=ki(l.borderWidth);return{text:e[t.index].label,fillStyle:l.backgroundColor,fontColor:o,hidden:!t.visible,lineCap:l.borderCapStyle,lineDash:l.borderDash,lineDashOffset:l.borderDashOffset,lineJoin:l.borderJoinStyle,lineWidth:(h.width+h.height)/4,strokeStyle:l.borderColor,pointStyle:s||l.pointStyle,rotation:l.rotation,textAlign:n||l.textAlign,borderRadius:a&&(r||l.borderRadius),datasetIndex:t.index}}),this)}},title:{color:t=>t.chart.options.color,display:!1,position:"center",text:""}},descriptors:{_scriptable:t=>!t.startsWith("on"),labels:{_scriptable:t=>!["generateLabels","filter","sort"].includes(t)}}};class va extends Hs{constructor(t){super(),this.chart=t.chart,this.options=t.options,this.ctx=t.ctx,this._padding=void 0,this.top=void 0,this.bottom=void 0,this.left=void 0,this.right=void 0,this.width=void 0,this.height=void 0,this.position=void 0,this.weight=void 0,this.fullSize=void 0}update(t,e){const i=this.options;if(this.left=0,this.top=0,!i.display)return void(this.width=this.height=this.right=this.bottom=0);this.width=this.right=t,this.height=this.bottom=e;const s=n(i.text)?i.text.length:1;this._padding=ki(i.padding);const o=s*Si(i.font).lineHeight+this._padding.height;this.isHorizontal()?this.height=o:this.width=o}isHorizontal(){const t=this.options.position;return"top"===t||"bottom"===t}_drawArgs(t){const{top:e,left:i,bottom:s,right:n,options:o}=this,a=o.align;let r,l,h,c=0;return this.isHorizontal()?(l=ft(a,i,n),h=e+t,r=n-i):("left"===o.position?(l=i+t,h=ft(a,s,e),c=-.5*C):(l=n-t,h=ft(a,e,s),c=.5*C),r=s-e),{titleX:l,titleY:h,maxWidth:r,rotation:c}}draw(){const t=this.ctx,e=this.options;if(!e.display)return;const i=Si(e.font),s=i.lineHeight/2+this._padding.top,{titleX:n,titleY:o,maxWidth:a,rotation:r}=this._drawArgs(s);Ne(t,e.text,0,0,i,{color:e.color,maxWidth:a,rotation:r,textAlign:ut(e.align),textBaseline:"middle",translation:[n,o]})}}var Ma={id:"title",_element:va,start(t,e,i){!function(t,e){const i=new va({ctx:t.ctx,options:e,chart:t});as.configure(t,i,e),as.addBox(t,i),t.titleBlock=i}(t,i)},stop(t){const e=t.titleBlock;as.removeBox(t,e),delete t.titleBlock},beforeUpdate(t,e,i){const s=t.titleBlock;as.configure(t,s,i),s.options=i},defaults:{align:"center",display:!1,font:{weight:"bold"},fullSize:!0,padding:10,position:"top",text:"",weight:2e3},defaultRoutes:{color:"color"},descriptors:{_scriptable:!0,_indexable:!1}};const wa=new WeakMap;var ka={id:"subtitle",start(t,e,i){const s=new va({ctx:t.ctx,options:i,chart:t});as.configure(t,s,i),as.addBox(t,s),wa.set(t,s)},stop(t){as.removeBox(t,wa.get(t)),wa.delete(t)},beforeUpdate(t,e,i){const s=wa.get(t);as.configure(t,s,i),s.options=i},defaults:{align:"center",display:!1,font:{weight:"normal"},fullSize:!0,padding:0,position:"top",text:"",weight:1500},defaultRoutes:{color:"color"},descriptors:{_scriptable:!0,_indexable:!1}};const Sa={average(t){if(!t.length)return!1;let e,i,s=0,n=0,o=0;for(e=0,i=t.length;e-1?t.split("\n"):t}function Ca(t,e){const{element:i,datasetIndex:s,index:n}=e,o=t.getDatasetMeta(s).controller,{label:a,value:r}=o.getLabelAndValue(n);return{chart:t,label:a,parsed:o.getParsed(n),raw:t.data.datasets[s].data[n],formattedValue:r,dataset:o.getDataset(),dataIndex:n,datasetIndex:s,element:i}}function Oa(t,e){const i=t.chart.ctx,{body:s,footer:n,title:o}=t,{boxWidth:a,boxHeight:r}=e,l=Si(e.bodyFont),h=Si(e.titleFont),c=Si(e.footerFont),d=o.length,f=n.length,g=s.length,p=ki(e.padding);let m=p.height,b=0,x=s.reduce(((t,e)=>t+e.before.length+e.lines.length+e.after.length),0);if(x+=t.beforeBody.length+t.afterBody.length,d&&(m+=d*h.lineHeight+(d-1)*e.titleSpacing+e.titleMarginBottom),x){m+=g*(e.displayColors?Math.max(r,l.lineHeight):l.lineHeight)+(x-g)*l.lineHeight+(x-1)*e.bodySpacing}f&&(m+=e.footerMarginTop+f*c.lineHeight+(f-1)*e.footerSpacing);let _=0;const y=function(t){b=Math.max(b,i.measureText(t).width+_)};return i.save(),i.font=h.string,u(t.title,y),i.font=l.string,u(t.beforeBody.concat(t.afterBody),y),_=e.displayColors?a+2+e.boxPadding:0,u(s,(t=>{u(t.before,y),u(t.lines,y),u(t.after,y)})),_=0,i.font=c.string,u(t.footer,y),i.restore(),b+=p.width,{width:b,height:m}}function Aa(t,e,i,s){const{x:n,width:o}=i,{width:a,chartArea:{left:r,right:l}}=t;let h="center";return"center"===s?h=n<=(r+l)/2?"left":"right":n<=o/2?h="left":n>=a-o/2&&(h="right"),function(t,e,i,s){const{x:n,width:o}=s,a=i.caretSize+i.caretPadding;return"left"===t&&n+o+a>e.width||"right"===t&&n-o-a<0||void 0}(h,t,e,i)&&(h="center"),h}function Ta(t,e,i){const s=i.yAlign||e.yAlign||function(t,e){const{y:i,height:s}=e;return it.height-s/2?"bottom":"center"}(t,i);return{xAlign:i.xAlign||e.xAlign||Aa(t,e,i,s),yAlign:s}}function La(t,e,i,s){const{caretSize:n,caretPadding:o,cornerRadius:a}=t,{xAlign:r,yAlign:l}=i,h=n+o,{topLeft:c,topRight:d,bottomLeft:u,bottomRight:f}=wi(a);let g=function(t,e){let{x:i,width:s}=t;return"right"===e?i-=s:"center"===e&&(i-=s/2),i}(e,r);const p=function(t,e,i){let{y:s,height:n}=t;return"top"===e?s+=i:s-="bottom"===e?n+i:n/2,s}(e,l,h);return"center"===l?"left"===r?g+=h:"right"===r&&(g-=h):"left"===r?g-=Math.max(c,u)+n:"right"===r&&(g+=Math.max(d,f)+n),{x:J(g,0,s.width-e.width),y:J(p,0,s.height-e.height)}}function Ea(t,e,i){const s=ki(i.padding);return"center"===e?t.x+t.width/2:"right"===e?t.x+t.width-s.right:t.x+s.left}function Ra(t){return Pa([],Da(t))}function Ia(t,e){const i=e&&e.dataset&&e.dataset.tooltip&&e.dataset.tooltip.callbacks;return i?t.override(i):t}const za={beforeTitle:e,title(t){if(t.length>0){const e=t[0],i=e.chart.data.labels,s=i?i.length:0;if(this&&this.options&&"dataset"===this.options.mode)return e.dataset.label||"";if(e.label)return e.label;if(s>0&&e.dataIndex{const e={before:[],lines:[],after:[]},n=Ia(i,t);Pa(e.before,Da(Fa(n,"beforeLabel",this,t))),Pa(e.lines,Fa(n,"label",this,t)),Pa(e.after,Da(Fa(n,"afterLabel",this,t))),s.push(e)})),s}getAfterBody(t,e){return Ra(Fa(e.callbacks,"afterBody",this,t))}getFooter(t,e){const{callbacks:i}=e,s=Fa(i,"beforeFooter",this,t),n=Fa(i,"footer",this,t),o=Fa(i,"afterFooter",this,t);let a=[];return a=Pa(a,Da(s)),a=Pa(a,Da(n)),a=Pa(a,Da(o)),a}_createItems(t){const e=this._active,i=this.chart.data,s=[],n=[],o=[];let a,r,l=[];for(a=0,r=e.length;at.filter(e,s,n,i)))),t.itemSort&&(l=l.sort(((e,s)=>t.itemSort(e,s,i)))),u(l,(e=>{const i=Ia(t.callbacks,e);s.push(Fa(i,"labelColor",this,e)),n.push(Fa(i,"labelPointStyle",this,e)),o.push(Fa(i,"labelTextColor",this,e))})),this.labelColors=s,this.labelPointStyles=n,this.labelTextColors=o,this.dataPoints=l,l}update(t,e){const i=this.options.setContext(this.getContext()),s=this._active;let n,o=[];if(s.length){const t=Sa[i.position].call(this,s,this._eventPosition);o=this._createItems(i),this.title=this.getTitle(o,i),this.beforeBody=this.getBeforeBody(o,i),this.body=this.getBody(o,i),this.afterBody=this.getAfterBody(o,i),this.footer=this.getFooter(o,i);const e=this._size=Oa(this,i),a=Object.assign({},t,e),r=Ta(this.chart,i,a),l=La(i,a,r,this.chart);this.xAlign=r.xAlign,this.yAlign=r.yAlign,n={opacity:1,x:l.x,y:l.y,width:e.width,height:e.height,caretX:t.x,caretY:t.y}}else 0!==this.opacity&&(n={opacity:0});this._tooltipItems=o,this.$context=void 0,n&&this._resolveAnimations().update(this,n),t&&i.external&&i.external.call(this,{chart:this.chart,tooltip:this,replay:e})}drawCaret(t,e,i,s){const n=this.getCaretPosition(t,i,s);e.lineTo(n.x1,n.y1),e.lineTo(n.x2,n.y2),e.lineTo(n.x3,n.y3)}getCaretPosition(t,e,i){const{xAlign:s,yAlign:n}=this,{caretSize:o,cornerRadius:a}=i,{topLeft:r,topRight:l,bottomLeft:h,bottomRight:c}=wi(a),{x:d,y:u}=t,{width:f,height:g}=e;let p,m,b,x,_,y;return"center"===n?(_=u+g/2,"left"===s?(p=d,m=p-o,x=_+o,y=_-o):(p=d+f,m=p+o,x=_-o,y=_+o),b=p):(m="left"===s?d+Math.max(r,h)+o:"right"===s?d+f-Math.max(l,c)-o:this.caretX,"top"===n?(x=u,_=x-o,p=m-o,b=m+o):(x=u+g,_=x+o,p=m+o,b=m-o),y=x),{x1:p,x2:m,x3:b,y1:x,y2:_,y3:y}}drawTitle(t,e,i){const s=this.title,n=s.length;let o,a,r;if(n){const l=Oi(i.rtl,this.x,this.width);for(t.x=Ea(this,i.titleAlign,i),e.textAlign=l.textAlign(i.titleAlign),e.textBaseline="middle",o=Si(i.titleFont),a=i.titleSpacing,e.fillStyle=i.titleColor,e.font=o.string,r=0;r0!==t))?(t.beginPath(),t.fillStyle=n.multiKeyBackground,He(t,{x:e,y:g,w:h,h:l,radius:r}),t.fill(),t.stroke(),t.fillStyle=a.backgroundColor,t.beginPath(),He(t,{x:i,y:g+1,w:h-2,h:l-2,radius:r}),t.fill()):(t.fillStyle=n.multiKeyBackground,t.fillRect(e,g,h,l),t.strokeRect(e,g,h,l),t.fillStyle=a.backgroundColor,t.fillRect(i,g+1,h-2,l-2))}t.fillStyle=this.labelTextColors[i]}drawBody(t,e,i){const{body:s}=this,{bodySpacing:n,bodyAlign:o,displayColors:a,boxHeight:r,boxWidth:l,boxPadding:h}=i,c=Si(i.bodyFont);let d=c.lineHeight,f=0;const g=Oi(i.rtl,this.x,this.width),p=function(i){e.fillText(i,g.x(t.x+f),t.y+d/2),t.y+=d+n},m=g.textAlign(o);let b,x,_,y,v,M,w;for(e.textAlign=o,e.textBaseline="middle",e.font=c.string,t.x=Ea(this,m,i),e.fillStyle=i.bodyColor,u(this.beforeBody,p),f=a&&"right"!==m?"center"===o?l/2+h:l+2+h:0,y=0,M=s.length;y0&&e.stroke()}_updateAnimationTarget(t){const e=this.chart,i=this.$animations,s=i&&i.x,n=i&&i.y;if(s||n){const i=Sa[t.position].call(this,this._active,this._eventPosition);if(!i)return;const o=this._size=Oa(this,t),a=Object.assign({},i,this._size),r=Ta(e,t,a),l=La(t,a,r,e);s._to===l.x&&n._to===l.y||(this.xAlign=r.xAlign,this.yAlign=r.yAlign,this.width=o.width,this.height=o.height,this.caretX=i.x,this.caretY=i.y,this._resolveAnimations().update(this,l))}}_willRender(){return!!this.opacity}draw(t){const e=this.options.setContext(this.getContext());let i=this.opacity;if(!i)return;this._updateAnimationTarget(e);const s={width:this.width,height:this.height},n={x:this.x,y:this.y};i=Math.abs(i)<.001?0:i;const o=ki(e.padding),a=this.title.length||this.beforeBody.length||this.body.length||this.afterBody.length||this.footer.length;e.enabled&&a&&(t.save(),t.globalAlpha=i,this.drawBackground(n,t,s,e),Ai(t,e.textDirection),n.y+=o.top,this.drawTitle(n,t,e),this.drawBody(n,t,e),this.drawFooter(n,t,e),Ti(t,e.textDirection),t.restore())}getActiveElements(){return this._active||[]}setActiveElements(t,e){const i=this._active,s=t.map((({datasetIndex:t,index:e})=>{const i=this.chart.getDatasetMeta(t);if(!i)throw new Error("Cannot find a dataset at index "+t);return{datasetIndex:t,element:i.data[e],index:e}})),n=!f(i,s),o=this._positionChanged(s,e);(n||o)&&(this._active=s,this._eventPosition=e,this._ignoreReplayEvents=!0,this.update(!0))}handleEvent(t,e,i=!0){if(e&&this._ignoreReplayEvents)return!1;this._ignoreReplayEvents=!1;const s=this.options,n=this._active||[],o=this._getActiveElements(t,n,e,i),a=this._positionChanged(o,t),r=e||!f(o,n)||a;return r&&(this._active=o,(s.enabled||s.external)&&(this._eventPosition={x:t.x,y:t.y},this.update(!0,e))),r}_getActiveElements(t,e,i,s){const n=this.options;if("mouseout"===t.type)return[];if(!s)return e;const o=this.chart.getElementsAtEventForMode(t,n.mode,n,i);return n.reverse&&o.reverse(),o}_positionChanged(t,e){const{caretX:i,caretY:s,options:n}=this,o=Sa[n.position].call(this,t,e);return!1!==o&&(i!==o.x||s!==o.y)}}var Ba={id:"tooltip",_element:Va,positioners:Sa,afterInit(t,e,i){i&&(t.tooltip=new Va({chart:t,options:i}))},beforeUpdate(t,e,i){t.tooltip&&t.tooltip.initialize(i)},reset(t,e,i){t.tooltip&&t.tooltip.initialize(i)},afterDraw(t){const e=t.tooltip;if(e&&e._willRender()){const i={tooltip:e};if(!1===t.notifyPlugins("beforeTooltipDraw",{...i,cancelable:!0}))return;e.draw(t.ctx),t.notifyPlugins("afterTooltipDraw",i)}},afterEvent(t,e){if(t.tooltip){const i=e.replay;t.tooltip.handleEvent(e.event,i,e.inChartArea)&&(e.changed=!0)}},defaults:{enabled:!0,external:null,position:"average",backgroundColor:"rgba(0,0,0,0.8)",titleColor:"#fff",titleFont:{weight:"bold"},titleSpacing:2,titleMarginBottom:6,titleAlign:"left",bodyColor:"#fff",bodySpacing:2,bodyFont:{},bodyAlign:"left",footerColor:"#fff",footerSpacing:2,footerMarginTop:6,footerFont:{weight:"bold"},footerAlign:"left",padding:6,caretPadding:2,caretSize:5,cornerRadius:6,boxHeight:(t,e)=>e.bodyFont.size,boxWidth:(t,e)=>e.bodyFont.size,multiKeyBackground:"#fff",displayColors:!0,boxPadding:0,borderColor:"rgba(0,0,0,0)",borderWidth:0,animation:{duration:400,easing:"easeOutQuart"},animations:{numbers:{type:"number",properties:["x","y","width","height","caretX","caretY"]},opacity:{easing:"linear",duration:200}},callbacks:za},defaultRoutes:{bodyFont:"font",footerFont:"font",titleFont:"font"},descriptors:{_scriptable:t=>"filter"!==t&&"itemSort"!==t&&"external"!==t,_indexable:!1,callbacks:{_scriptable:!1,_indexable:!1},animation:{_fallback:!1},animations:{_fallback:"animation"}},additionalOptionScopes:["interaction"]};return An.register(Yn,jo,fo,t),An.helpers={...Wi},An._adapters=Rn,An.Animation=Cs,An.Animations=Os,An.animator=xt,An.controllers=en.controllers.items,An.DatasetController=Ns,An.Element=Hs,An.elements=fo,An.Interaction=Xi,An.layouts=as,An.platforms=Ss,An.Scale=Js,An.Ticks=ae,Object.assign(An,Yn,jo,fo,t,Ss),An.Chart=An,"undefined"!=typeof window&&(window.Chart=An),An})); //# sourceMappingURL=chart.umd.js.map ================================================ FILE: packages/Webkul/Admin/src/Resources/assets/js/directives/debounce.js ================================================ var timeoutID = null; export default { mounted(el, binding) { let handler = function (e) { if (binding.value !== binding.oldValue) { clearTimeout(timeoutID) timeoutID = setTimeout(function () { el.dispatchEvent(new Event('change')) }, binding.value || 500) } }; el.addEventListener('input', handler); } } ================================================ FILE: packages/Webkul/Admin/src/Resources/assets/js/directives/dompurify.js ================================================ import DOMPurify from 'dompurify'; export default { beforeMount(el, binding) { el.innerHTML = DOMPurify.sanitize(binding.value); }, updated(el, binding) { el.innerHTML = DOMPurify.sanitize(binding.value); } }; ================================================ FILE: packages/Webkul/Admin/src/Resources/assets/js/directives/tooltip.js ================================================ export default { mounted(el, binding) { initTooltip(el, binding); }, updated(el, binding) { initTooltip(el, binding); } }; const initTooltip = (el, binding) => { const defaultOptions = { placement: 'top', trigger: 'hover', html: false, content: '', delay: { show: 200, hide: 100 } }; const options = { ...defaultOptions, ...(typeof binding.value === 'object' ? binding.value : { content: binding.value }) }; let tooltip = document.getElementById(`tooltip-${el.tooltipId}`); if (! tooltip) { el.tooltipId = Math.random().toString(36).substring(2, 9); tooltip = document.createElement('div'); tooltip.id = `tooltip-${el.tooltipId}`; tooltip.className = 'max-w-[250px] break-words rounded-lg bg-gray-800 px-4 py-3 text-sm leading-snug text-white shadow-lg transition-opacity transition-transform duration-200'; tooltip.style.display = 'none'; tooltip.style.position = 'absolute'; tooltip.style.zIndex = '10000'; const inner = document.createElement('div'); inner.className = 'tooltip-inner'; const arrow = document.createElement('div'); arrow.className = 'absolute h-0 w-0 border-solid'; tooltip.appendChild(inner); tooltip.appendChild(arrow); document.body.appendChild(tooltip); if (options.html) { inner.innerHTML = options.content; } else { inner.textContent = options.content; } el._tooltip = tooltip; const showTooltip = () => { tooltip.style.display = 'block'; const rect = el.getBoundingClientRect(); const tooltipRect = tooltip.getBoundingClientRect(); let top, left; switch (options.placement) { case 'top': top = rect.top - tooltipRect.height - 10; left = rect.left + (rect.width / 2) - (tooltipRect.width / 2); arrow.style.top = 'auto'; arrow.style.bottom = '-5px'; arrow.style.left = '50%'; arrow.style.transform = 'translateX(-50%)'; break; case 'bottom': top = rect.bottom + 10; left = rect.left + (rect.width / 2) - (tooltipRect.width / 2); arrow.style.bottom = 'auto'; arrow.style.top = '-5px'; arrow.style.left = '50%'; arrow.style.transform = 'translateX(-50%) rotate(180deg)'; break; case 'left': top = rect.top + (rect.height / 2) - (tooltipRect.height / 2); left = rect.left - tooltipRect.width - 10; arrow.style.top = '50%'; arrow.style.left = 'auto'; arrow.style.right = '-5px'; arrow.style.transform = 'translateY(-50%) rotate(-90deg)'; break; case 'right': top = rect.top + (rect.height / 2) - (tooltipRect.height / 2); left = rect.right + 10; arrow.style.top = '50%'; arrow.style.right = 'auto'; arrow.style.left = '-5px'; arrow.style.transform = 'translateY(-50%) rotate(90deg)'; break; } if (top < 0) { top = 0; } if (left < 0) { left = 0; } if (left + tooltipRect.width > window.innerWidth) { left = window.innerWidth - tooltipRect.width; } tooltip.style.top = `${top + window.scrollY}px`; tooltip.style.left = `${left + window.scrollX}px`; }; const hideTooltip = () => { tooltip.style.display = 'none'; }; if (options.trigger === 'hover') { el.addEventListener('mouseenter', () => { el._showTimeout = setTimeout(showTooltip, options.delay.show); }); el.addEventListener('mouseleave', () => { clearTimeout(el._showTimeout); el._hideTimeout = setTimeout(hideTooltip, options.delay.hide); }); } else if (options.trigger === 'click') { el.addEventListener('click', showTooltip); document.addEventListener('click', (e) => { if (e.target !== el && !el.contains(e.target)) { hideTooltip(); } }); } } else { const inner = tooltip.querySelector('.tooltip-inner'); if (options.html) { inner.innerHTML = options.content; } else { inner.textContent = options.content; } } } ================================================ FILE: packages/Webkul/Admin/src/Resources/assets/js/plugins/admin.js ================================================ export default { install(app) { app.config.globalProperties.$admin = { /** * Generates a formatted price. * * @param {number} price - The price value to be formatted. * @returns {string} - The formatted price string. */ formatPrice: (price) => { let locale = document.querySelector( 'meta[http-equiv="content-language"]' ).content; locale = locale.replace(/([a-z]{2})_([A-Z]{2})/g, "$1-$2"); const currency = JSON.parse( document.querySelector('meta[name="currency"]').content ); const symbol = currency.symbol !== "" ? currency.symbol : currency.code; if (!currency.currency_position) { return new Intl.NumberFormat(locale, { style: "currency", currency: currency.code, }).format(price); } const formatter = new Intl.NumberFormat(locale, { style: "currency", currency: currency.code, minimumFractionDigits: currency.decimal ?? 2, }); const formattedCurrency = formatter .formatToParts(price) .map((part) => { switch (part.type) { case "currency": return ""; case "group": return currency.group_separator === "" ? part.value : currency.group_separator; case "decimal": return currency.decimal_separator === "" ? part.value : currency.decimal_separator; default: return part.value; } }) .join(""); switch (currency.currency_position) { case "left": return symbol + formattedCurrency; case "left_with_space": return symbol + " " + formattedCurrency; case "right": return formattedCurrency + symbol; case "right_with_space": return formattedCurrency + " " + symbol; default: return formattedCurrency; } }, /** * Generates a formatted date based on specified timezone. * * @param {string} dateString - The date value to be formatted. * @param {string} format - The format to be used for formatting the date. * @param {string} timezone - The timezone to use (e.g., 'America/New_York'). * @returns {string} - The formatted date string. */ formatDate: (dateString, format, timezone) => { const date = new Date(dateString); const options = { timeZone: timezone }; const formatter = new Intl.DateTimeFormat("en-US", { ...options, hour12: false, year: "numeric", month: "numeric", day: "numeric", hour: "numeric", minute: "numeric", second: "numeric", }); const parts = formatter.formatToParts(date); const dateParts = {}; parts.forEach((part) => { if (part.type !== "literal") { dateParts[part.type] = part.value; } }); const tzDay = parseInt(dateParts.day, 10); const tzMonth = parseInt(dateParts.month, 10); const tzYear = parseInt(dateParts.year, 10); const tzHour = parseInt(dateParts.hour, 10); const tzMinute = parseInt(dateParts.minute, 10); const formatters = { d: tzDay, DD: tzDay.toString().padStart(2, "0"), M: tzMonth, MM: tzMonth.toString().padStart(2, "0"), MMM: new Date(tzYear, tzMonth - 1, 1).toLocaleString( "en-US", { month: "short" } ), MMMM: new Date(tzYear, tzMonth - 1, 1).toLocaleString( "en-US", { month: "long" } ), yy: tzYear.toString().slice(-2), yyyy: tzYear, H: tzHour, HH: tzHour.toString().padStart(2, "0"), h: tzHour % 12 || 12, hh: (tzHour % 12 || 12).toString().padStart(2, "0"), m: tzMinute, mm: tzMinute.toString().padStart(2, "0"), A: tzHour < 12 ? "AM" : "PM", }; return format.replace( /\b(?:d|DD|M|MM|MMM|MMMM|yy|yyyy|H|HH|h|hh|m|mm|A)\b/g, (match) => formatters[match] ); }, }; }, }; ================================================ FILE: packages/Webkul/Admin/src/Resources/assets/js/plugins/axios.js ================================================ /** * We'll load the axios HTTP library which allows us to easily issue requests * to our Laravel back-end. This library automatically handles sending the * CSRF token as a header based on the value of the "XSRF" token cookie. */ import axios from "axios"; window.axios = axios; window.axios.defaults.headers.common["X-Requested-With"] = "XMLHttpRequest"; export default { install(app) { app.config.globalProperties.$axios = axios; }, }; ================================================ FILE: packages/Webkul/Admin/src/Resources/assets/js/plugins/createElement.js ================================================ import { h, resolveComponent } from "vue/dist/vue.esm-bundler"; export default { install(app) { /** * Create the virtual dom element */ app.config.globalProperties.$h = h; /** * Resolve the component which is globally registered */ app.config.globalProperties.$resolveComponent = resolveComponent; }, }; ================================================ FILE: packages/Webkul/Admin/src/Resources/assets/js/plugins/draggable.js ================================================ import Draggable from 'vuedraggable'; export default { install: (app) => { /** * Global component registration; */ app.component("draggable", Draggable); }, }; ================================================ FILE: packages/Webkul/Admin/src/Resources/assets/js/plugins/emitter.js ================================================ import mitt from "mitt"; const emitter = mitt(); window.emitter = emitter; export default { install: (app, options) => { app.config.globalProperties.$emitter = emitter; }, }; ================================================ FILE: packages/Webkul/Admin/src/Resources/assets/js/plugins/flatpickr.js ================================================ import Flatpickr from "flatpickr"; import "flatpickr/dist/flatpickr.css"; import { Spanish } from "flatpickr/dist/l10n/es.js"; import { Arabic } from "flatpickr/dist/l10n/ar.js"; import { Persian } from "flatpickr/dist/l10n/fa.js"; import { Turkish } from "flatpickr/dist/l10n/tr.js"; export default { install: (app) => { window.Flatpickr = Flatpickr; const setLocaleFromLang = () => { const lang = document.documentElement.lang || "en"; const localeMap = { es: Spanish, ar: Arabic, fa: Persian, tr: Turkish }; const locale = localeMap[lang] || null; if (locale) { window.Flatpickr.localize(locale); } }; setLocaleFromLang(); const changeTheme = (theme) => { document.getElementById('flatpickr')?.remove(); if (theme === 'light') { return; } const linkElement = document.createElement("link"); linkElement.rel = "stylesheet"; linkElement.type = "text/css"; linkElement.href = `https://npmcdn.com/flatpickr/dist/themes/${theme}.css`; linkElement.id = 'flatpickr'; document.head.appendChild(linkElement); }; const currentTheme = document.documentElement.classList.contains("dark") ? "dark" : "light"; changeTheme(currentTheme); window.emitter.on("change-theme", (theme) => changeTheme(theme)); }, }; ================================================ FILE: packages/Webkul/Admin/src/Resources/assets/js/plugins/vee-validate.js ================================================ /** * We are defining all the global rules here and configuring * all the `vee-validate` settings. */ import { configure, defineRule, Field, Form, ErrorMessage } from "vee-validate"; import { localize, setLocale } from "@vee-validate/i18n"; import ar from "@vee-validate/i18n/dist/locale/ar.json"; import bn from "@vee-validate/i18n/dist/locale/bn.json"; import de from "@vee-validate/i18n/dist/locale/de.json"; import en from "@vee-validate/i18n/dist/locale/en.json"; import es from "@vee-validate/i18n/dist/locale/es.json"; import fa from "@vee-validate/i18n/dist/locale/fa.json"; import fr from "@vee-validate/i18n/dist/locale/fr.json"; import he from "@vee-validate/i18n/dist/locale/he.json"; import hi_IN from "../../locales/hi_IN.json"; import it from "@vee-validate/i18n/dist/locale/it.json"; import ja from "@vee-validate/i18n/dist/locale/ja.json"; import nl from "@vee-validate/i18n/dist/locale/nl.json"; import pl from "@vee-validate/i18n/dist/locale/pl.json"; import pt_BR from "@vee-validate/i18n/dist/locale/pt_BR.json"; import ru from "@vee-validate/i18n/dist/locale/ru.json"; import sin from "../../locales/sin.json"; import tr from "@vee-validate/i18n/dist/locale/tr.json"; import uk from "@vee-validate/i18n/dist/locale/uk.json"; import zh_CN from "@vee-validate/i18n/dist/locale/zh_CN.json"; import { all } from '@vee-validate/rules'; window.defineRule = defineRule; export default { install: (app) => { /** * Global components registration; */ app.component("VForm", Form); app.component("VField", Field); app.component("VErrorMessage", ErrorMessage); window.addEventListener("load", () => setLocale(document.documentElement.attributes.lang.value)); /** * Registration of all global validators. */ Object.entries(all).forEach(([name, rule]) => defineRule(name, rule)); /** * This regular expression allows phone numbers with the following conditions: * - The phone number can start with an optional "+" sign. * - After the "+" sign, there should be one or more digits. * * This validation is sufficient for global-level phone number validation. If * someone wants to customize it, they can override this rule. */ defineRule("phone", (value) => { if (! value || ! value.length) { return true; } if (! /^\+?\d+$/.test(value)) { return false; } return true; }); defineRule("address", (value) => { if (!value || !value.length) { return true; } if ( !/^[a-zA-Z0-9\s.\/*'\u0600-\u06FF\u0750-\u077F\u08A0-\u08FF\u0590-\u05FF\u3040-\u309F\u30A0-\u30FF\u0400-\u04FF\u0D80-\u0DFF\u3400-\u4DBF\u2000-\u2A6D\u00C0-\u017F\u0980-\u09FF\u0900-\u097F\u4E00-\u9FFF,\(\)-]{1,60}$/iu.test( value ) ) { return false; } return true; }); defineRule("postcode", (value) => { if (! value || ! value.length) { return true; } if (! /^[a-zA-Z0-9][a-zA-Z0-9\s-]*[a-zA-Z0-9]$/.test(value)) { return false; } return true; }); defineRule("decimal", (value, { decimals = '*', separator = '.' } = {}) => { if (value === null || value === undefined || value === '') { return true; } if (Number(decimals) === 0) { return /^-?\d*$/.test(value); } const regexPart = decimals === '*' ? '+' : `{1,${decimals}}`; const regex = new RegExp(`^[-+]?\\d*(\\${separator}\\d${regexPart})?([eE]{1}[-]?\\d+)?$`); return regex.test(value); }); defineRule("required_if", (value, { condition = true } = {}) => { if (condition) { if (value === null || value === undefined || value === '') { return false; } } return true; }); defineRule("", () => true); defineRule("date_format", (value) => { const regex = /^\d{4}-\d{2}-\d{2}$/; return regex.test(value); }); defineRule("after", (value) => { const today = new Date(); const inputDate = new Date(value); today.setHours(0, 0, 0, 0); inputDate.setHours(0, 0, 0, 0); return inputDate >= today; }); configure({ /** * Built-in error messages and custom error messages are available. Multiple * locales can be added in the same way. */ generateMessage: localize({ ar: { ...ar, messages: { ...ar.messages, phone: "يجب أن يكون هذا {field} رقم هاتف صالحًا", after: "يجب أن يكون {field} تاريخًا في المستقبل أو اليوم.", }, }, en: { ...en, messages: { ...en.messages, phone: "This {field} must be a valid phone number", after: "The {field} must be a date in the future or today.", }, }, es: { ...es, messages: { ...es.messages, phone: "Este {field} debe ser un número de teléfono válido.", after: "El {field} debe ser una fecha en el futuro o hoy.", }, }, fa: { ...fa, messages: { ...fa.messages, phone: "این {field} باید یک شماره تلفن معتبر باشد.", after: "{field} باید یک تاریخ در آینده یا امروز باشد.", }, }, tr: { ...tr, messages: { ...tr.messages, phone: "Bu {field} geçerli bir telefon numarası olmalıdır.", after: "{field} gelecekte veya bugün olmalıdır.", }, }, }), validateOnBlur: true, validateOnInput: true, validateOnChange: true, }); }, }; ================================================ FILE: packages/Webkul/Admin/src/Resources/assets/js/plugins/vue-cal.js ================================================ import VueCal from 'vue-cal'; import 'vue-cal/dist/vuecal.css'; import 'vue-cal/dist/i18n/ar.es.js'; import 'vue-cal/dist/i18n/en.es.js'; import 'vue-cal/dist/i18n/es.es.js'; import 'vue-cal/dist/i18n/fa.es.js'; import 'vue-cal/dist/i18n/tr.es.js'; import 'vue-cal/dist/i18n/pt-br.es.js'; import 'vue-cal/dist/i18n/vi.es.js'; export default { install: (app, options) => app.component('v-vue-cal', VueCal), }; ================================================ FILE: packages/Webkul/Admin/src/Resources/assets/locales/hi_IN.json ================================================ { "code": "hi_IN", "messages": { "_default": "यह {field} मान्य नहीं है", "alpha": "{field} फ़ील्ड में केवल वर्णात्मक अक्षर हो सकते हैं", "alpha_num": "{field} फ़ील्ड में केवल वर्णात्मक और संख्यात्मक अक्षर हो सकते हैं", "alpha_dash": "{field} फ़ील्ड में वर्णात्मक और संख्यात्मक अक्षरों के साथ डैश और अंडरस्कोर हो सकते हैं", "alpha_spaces": "{field} फ़ील्ड में केवल वर्णात्मक अक्षर और अंतर हो सकते हैं", "between": "{field} फ़ील्ड 0:{min} और 1:{max} के बीच होना चाहिए", "confirmed": "{field} फ़ील्ड की पुष्टि मेल नहीं खाती", "digits": "{field} फ़ील्ड संख्यात्मक होनी चाहिए और बिल्कुल 0:{length} अंक होने चाहिए", "dimensions": "{field} फ़ील्ड 0:{width} पिक्सेल और 1:{height} पिक्सेल होना चाहिए", "email": "{field} फ़ील्ड में एक मान्य ईमेल होना चाहिए", "not_one_of": "{field} फ़ील्ड मान्य मूल्य नहीं है", "ext": "{field} फ़ील्ड में मान्य फ़ाइल नहीं है", "image": "{field} फ़ील्ड एक छवि होनी चाहिए", "integer": "{field} फ़ील्ड एक पूर्णांक होना चाहिए", "length": "{field} फ़ील्ड 0:{length} लंबा होना चाहिए", "max_value": "{field} फ़ील्ड 0:{max} या उससे कम होना चाहिए", "max": "{field} फ़ील्ड 0:{length} अक्षरों से अधिक नहीं हो सकता", "mimes": "{field} फ़ील्ड को मान्य फ़ाइल प्रकार होना चाहिए", "min_value": "{field} फ़ील्ड 0:{min} या उससे अधिक होना चाहिए", "min": "{field} फ़ील्ड कम से कम 0:{length} अक्षरों का होना चाहिए", "numeric": "{field} फ़ील्ड में केवल संख्याएँ हो सकती हैं", "one_of": "{field} फ़ील्ड मान्य मूल्य नहीं है", "regex": "{field} फ़ील्ड का प्रारूप अवैध है", "required_if": "{field} फ़ील्ड आवश्यक है", "required": "{field} फ़ील्ड आवश्यक है", "size": "{field} फ़ील्ड का आकार 0:{size}KB से कम होना चाहिए", "url": "{field} फ़ील्ड में एक मान्य URL नहीं है" } } ================================================ FILE: packages/Webkul/Admin/src/Resources/assets/locales/sin.json ================================================ { "code": "sin", "messages": { "_default": "මේ {field} වල වලංගු නොවේ", "alpha": "{field} ක්ෂණික සංඛ්‍යාවක් පිළිබඳව සියල්ල සියල්ල සහිතව හැකිය", "alpha_num": "{field} ක්ෂණික සහ සංඛ්‍යාවක් පිළිබඳව සියල්ල සහිතව හැකිය", "alpha_dash": "{field} ක්ෂණික සහ සංඛ්‍යාවක් සමග දැහැ හෝ පරිදි ලොව සහිතව හැකිය", "alpha_spaces": "{field} ක්ෂණික සංඛ්‍යාවක් සහිතව හැකිය, සහ වීඩියෝ හෝම්හෝ සහිතව හැකිය", "between": "{field} ක්ෂණික 0:{min} සහ 1:{max} අතර විය යුතුය", "confirmed": "{field} ක්ෂණික තහවුරු නොගත් බව තහවුරු කර නොයාය", "digits": "{field} ක්ෂණික සෂ්යෝගයක් හා සියලුමේ විය 0:{length} දිගු විය යුතුය", "dimensions": "{field} ක්ෂණික 0:{width} පික්සල සහ 1:{height} පික්සල විය යුතුය", "email": "{field} ක්ෂණික වලංගු ඊමේල් එක හෝ යුක්ත විය යුතුය", "not_one_of": "{field} ක්ෂණික වලංගු අගය නොවේ", "ext": "{field} ක්ෂණික වලංගු ගොනුව නොවේ", "image": "{field} ක්ෂණික වලංගු ඡායාරූපය යුතුය", "integer": "{field} ක්ෂණික වලංගු නික්මෙර වර්ගයේ යුතුය", "length": "{field} ක්ෂණික වලංගු 0:{length} හෝමාව යුතුය", "max_value": "{field} ක්ෂණික 0:{max} හෝමා හෝමා හෝමා යුතුය", "max": "{field} ක්ෂණික 0:{length} අකුරු වලංගු වී නොයාය", "mimes": "{field} ක්ෂණික ගොනුවේ වලංගු ගොනු වර්ගය හෝ හෝ හෝ යුතුය", "min_value": "{field} ක්ෂණික 0:{min} හෝමාව හෝමාව හෝමාව හෝමාව හෝමාව යුතුය", "min": "{field} ක්ෂණික 0:{length} හෝමාවක් හෝමාවක් හෝමාවක් හෝමාවක් යුතුය", "numeric": "{field} ක්ෂණික වලංගු සංඛ්‍යාවෙන් වයස්ක්‍ර සංඛ්‍යාවෙන් වයස්ක්‍ර විය ෺", "one_of": "{field} ක්ෂණික වලංගු අගය නොවේ", "regex": "{field} ක්ෂණික වලංගු ආකාරය අවලංගුය", "required_if": "{field} ක්ෂණිකයෙන් හෝයි", "required": "{field} ක්ෂණිකයෙන් හෝයි", "size": "{field} ක්ෂණික වලංගු විය හැකි ආකාරය 0:{size}KB හෝ හොයා යුතුයි", "url": "{field} ක්ෂණික වලංගු වර්ගවල URL නොවේ" } } ================================================ FILE: packages/Webkul/Admin/src/Resources/lang/ar/app.php ================================================ [ 'leads' => 'العملاء المحتملون', 'lead' => 'عميل محتمل', 'quotes' => 'عروض الأسعار', 'mail' => 'البريد', 'inbox' => 'الوارد', 'draft' => 'المسودات', 'outbox' => 'الصادر', 'sent' => 'المرسل', 'trash' => 'المحذوفات', 'activities' => 'الأنشطة', 'webhook' => 'Webhook', 'contacts' => 'جهات الاتصال', 'persons' => 'الأشخاص', 'organizations' => 'المنظمات', 'products' => 'المنتجات', 'settings' => 'الإعدادات', 'groups' => 'المجموعات', 'roles' => 'الأدوار', 'users' => 'المستخدمون', 'user' => 'المستخدم', 'automation' => 'الأتمتة', 'attributes' => 'السمات', 'pipelines' => 'قنوات البيع', 'sources' => 'المصادر', 'types' => 'الأنواع', 'email-templates' => 'قوالب البريد الإلكتروني', 'workflows' => 'سير العمل', 'other-settings' => 'إعدادات أخرى', 'tags' => 'الوسوم', 'configuration' => 'التهيئة', 'create' => 'إنشاء', 'edit' => 'تعديل', 'view' => 'عرض', 'print' => 'طباعة', 'delete' => 'حذف', 'export' => 'تصدير', 'mass-delete' => 'حذف جماعي', 'data-transfer' => 'نقل البيانات', 'imports' => 'الاستيرادات', 'import' => 'استيراد', 'event' => 'حدث', 'campaigns' => 'الحملات', 'warehouses' => 'المستودعات', 'inventory' => 'المخزون', ], 'users' => [ 'activate-warning' => 'حسابك لم يتم تفعيله بعد. يرجى الاتصال بالمسؤول.', 'login-error' => 'بيانات الاعتماد لا تتطابق مع سجلاتنا.', 'not-permission' => 'ليس لديك إذن للوصول إلى لوحة الإدارة.', 'login' => [ 'email' => 'عنوان البريد الإلكتروني', 'forget-password-link' => 'نسيت كلمة المرور؟', 'password' => 'كلمة المرور', 'submit-btn' => 'تسجيل الدخول', 'title' => 'تسجيل الدخول', ], 'forget-password' => [ 'create' => [ 'email' => 'البريد الإلكتروني المسجل', 'email-not-exist' => 'البريد الإلكتروني غير موجود', 'page-title' => 'نسيت كلمة المرور', 'reset-link-sent' => 'تم إرسال رابط إعادة تعيين كلمة المرور', 'sign-in-link' => 'العودة إلى تسجيل الدخول؟', 'submit-btn' => 'إعادة تعيين', 'title' => 'استعادة كلمة المرور', ], ], 'reset-password' => [ 'back-link-title' => 'العودة إلى تسجيل الدخول؟', 'confirm-password' => 'تأكيد كلمة المرور', 'email' => 'البريد الإلكتروني المسجل', 'password' => 'كلمة المرور', 'submit-btn' => 'إعادة تعيين كلمة المرور', 'title' => 'إعادة تعيين كلمة المرور', ], ], 'account' => [ 'edit' => [ 'back-btn' => 'رجوع', 'change-password' => 'تغيير كلمة المرور', 'confirm-password' => 'تأكيد كلمة المرور', 'current-password' => 'كلمة المرور الحالية', 'email' => 'البريد الإلكتروني', 'general' => 'عام', 'invalid-password' => 'كلمة المرور الحالية التي أدخلتها غير صحيحة.', 'name' => 'الاسم', 'password' => 'كلمة المرور', 'profile-image' => 'صورة الملف الشخصي', 'save-btn' => 'حفظ الحساب', 'title' => 'حسابي', 'update-success' => 'تم تحديث الحساب بنجاح', 'upload-image-info' => 'قم بتحميل صورة الملف الشخصي (110px X 110px) بتنسيق PNG أو JPG', ], ], 'components' => [ 'activities' => [ 'actions' => [ 'mail' => [ 'btn' => 'بريد', 'title' => 'كتابة بريد', 'to' => 'إلى', 'enter-emails' => 'اضغط Enter لإضافة عناوين البريد الإلكتروني', 'cc' => 'نسخة', 'bcc' => 'نسخة مخفية', 'subject' => 'الموضوع', 'send-btn' => 'إرسال', 'message' => 'رسالة', ], 'file' => [ 'btn' => 'ملف', 'title' => 'إضافة ملف', 'title-control' => 'عنوان', 'name' => 'اسم', 'description' => 'وصف', 'file' => 'ملف', 'save-btn' => 'حفظ الملف', ], 'note' => [ 'btn' => 'ملاحظة', 'title' => 'إضافة ملاحظة', 'comment' => 'تعليق', 'save-btn' => 'حفظ الملاحظة', ], 'activity' => [ 'btn' => 'نشاط', 'title' => 'إضافة نشاط', 'title-control' => 'عنوان', 'description' => 'وصف', 'schedule-from' => 'الجدولة من', 'schedule-to' => 'الجدولة إلى', 'location' => 'موقع', 'call' => 'مكالمة', 'meeting' => 'اجتماع', 'lunch' => 'غداء', 'save-btn' => 'حفظ النشاط', 'participants' => [ 'title' => 'المشاركون', 'placeholder' => 'اكتب للبحث عن المشاركين', 'users' => 'المستخدمون', 'persons' => 'الأشخاص', 'no-results' => 'لم يتم العثور على نتائج...', ], ], ], 'index' => [ 'all' => 'الكل', 'bcc' => 'نسخة مخفية', 'by-user' => 'بواسطة :user', 'calls' => 'المكالمات', 'cc' => 'نسخة', 'change-log' => 'سجلات التغيير', 'delete' => 'حذف', 'edit' => 'تعديل', 'emails' => 'البريد الإلكتروني', 'empty' => 'فارغ', 'files' => 'الملفات', 'from' => 'من', 'location' => 'موقع', 'lunches' => 'الغداء', 'mark-as-done' => 'وضع علامة تم', 'meetings' => 'الاجتماعات', 'notes' => 'الملاحظات', 'participants' => 'المشاركون', 'planned' => 'مخطط له', 'quotes' => 'الاقتباسات', 'scheduled-on' => 'مجدول في', 'system' => 'النظام', 'to' => 'إلى', 'unlink' => 'إلغاء الارتباط', 'view' => 'عرض', 'empty-placeholders' => [ 'all' => [ 'title' => 'لم يتم العثور على أي أنشطة', 'description' => 'لم يتم العثور على أي أنشطة لهذا العنصر. يمكنك إضافة الأنشطة من خلال النقر على زر "النشاط" في اللوحة الجانبية اليسرى.', ], 'planned' => [ 'title' => 'لم يتم العثور على أنشطة مخططة', 'description' => 'لم يتم العثور على أنشطة مخططة لهذا العنصر. يمكنك إضافة أنشطة مخططة من خلال النقر على زر "النشاط" في اللوحة الجانبية اليسرى.', ], 'notes' => [ 'title' => 'لم يتم العثور على ملاحظات', 'description' => 'لم يتم العثور على ملاحظات لهذا العنصر. يمكنك إضافة الملاحظات من خلال النقر على زر "الملاحظة" في اللوحة الجانبية اليسرى.', ], 'calls' => [ 'title' => 'لم يتم العثور على مكالمات', 'description' => 'لم يتم العثور على مكالمات لهذا العنصر. يمكنك إضافة المكالمات من خلال النقر على زر "النشاط" في اللوحة الجانبية اليسرى واختيار نوع المكالمة.', ], 'meetings' => [ 'title' => 'لم يتم العثور على اجتماعات', 'description' => 'لم يتم العثور على اجتماعات لهذا العنصر. يمكنك إضافة الاجتماعات من خلال النقر على زر "النشاط" في اللوحة الجانبية اليسرى واختيار نوع الاجتماع.', ], 'lunches' => [ 'title' => 'لم يتم العثور على غداءات', 'description' => 'لم يتم العثور على غداءات لهذا العنصر. يمكنك إضافة الغداءات من خلال النقر على زر "النشاط" في اللوحة الجانبية اليسرى واختيار نوع الغداء.', ], 'files' => [ 'title' => 'لم يتم العثور على ملفات', 'description' => 'لم يتم العثور على ملفات لهذا العنصر. يمكنك إضافة الملفات من خلال النقر على زر "الملف" في اللوحة الجانبية اليسرى.', ], 'emails' => [ 'title' => 'لم يتم العثور على رسائل بريد إلكتروني', 'description' => 'لم يتم العثور على رسائل بريد إلكتروني لهذا العنصر. يمكنك إضافة الرسائل من خلال النقر على زر "البريد" في اللوحة الجانبية اليسرى.', ], 'system' => [ 'title' => 'لم يتم العثور على سجلات التغيير', 'description' => 'لم يتم العثور على سجلات التغيير لهذا العنصر.', ], ], ], ], 'media' => [ 'images' => [ 'add-image-btn' => 'إضافة صورة', 'ai-add-image-btn' => 'الذكاء الاصطناعي السحري', 'allowed-types' => 'png, jpeg, jpg', 'not-allowed-error' => 'فقط ملفات الصور (.jpeg, .jpg, .png) مسموح بها.', 'placeholders' => [ 'front' => 'الأمام', 'next' => 'التالي', 'size' => 'الحجم', 'use-cases' => 'حالات الاستخدام', 'zoom' => 'تكبير', ], ], 'videos' => [ 'add-video-btn' => 'إضافة فيديو', 'allowed-types' => 'mp4, webm, mkv', 'not-allowed-error' => 'فقط ملفات الفيديو (.mp4, .mov, .ogg) مسموح بها.', ], ], 'datagrid' => [ 'index' => [ 'no-records-selected' => 'لم يتم اختيار أي سجلات.', 'must-select-a-mass-action-option' => 'يجب أن تختار خيارًا للإجراء الجماعي.', 'must-select-a-mass-action' => 'يجب أن تختار إجراءً جماعيًا.', ], 'toolbar' => [ 'length-of' => ':length من', 'of' => 'من', 'per-page' => 'لكل صفحة', 'results' => ':total نتائج', 'delete' => 'حذف', 'selected' => ':total عناصر مختارة', 'mass-actions' => [ 'submit' => 'إرسال', 'select-option' => 'اختر خيارًا', 'select-action' => 'اختر إجراءً', ], 'filter' => [ 'apply-filters-btn' => 'تطبيق الفلاتر', 'back-btn' => 'رجوع', 'create-new-filter' => 'إنشاء فلتر جديد', 'custom-filters' => 'فلاتر مخصصة', 'delete-error' => 'حدث خطأ أثناء حذف الفلتر، يرجى المحاولة مرة أخرى.', 'delete-success' => 'تم حذف الفلتر بنجاح.', 'empty-description' => 'لا توجد فلاتر محددة للحفظ. يرجى اختيار الفلاتر للحفظ.', 'empty-title' => 'أضف فلاتر للحفظ', 'name' => 'الاسم', 'quick-filters' => 'الفلاتر السريعة', 'save-btn' => 'حفظ', 'save-filter' => 'حفظ الفلتر', 'saved-success' => 'تم حفظ الفلتر بنجاح.', 'selected-filters' => 'الفلاتر المحددة', 'title' => 'فلتر', 'update' => 'تحديث', 'update-filter' => 'تحديث الفلتر', 'updated-success' => 'تم تحديث الفلتر بنجاح.', ], 'search' => [ 'title' => 'بحث', ], ], 'filters' => [ 'select' => 'اختر', 'title' => 'فلاتر', 'dropdown' => [ 'searchable' => [ 'at-least-two-chars' => 'اكتب حرفين على الأقل...', 'no-results' => 'لم يتم العثور على نتائج...', ], ], 'custom-filters' => [ 'clear-all' => 'مسح الكل', 'title' => 'فلاتر مخصصة', ], 'boolean-options' => [ 'false' => 'خاطئ', 'true' => 'صحيح', ], 'date-options' => [ 'last-month' => 'الشهر الماضي', 'last-six-months' => 'آخر 6 أشهر', 'last-three-months' => 'آخر 3 أشهر', 'this-month' => 'هذا الشهر', 'this-week' => 'هذا الأسبوع', 'this-year' => 'هذا العام', 'today' => 'اليوم', 'yesterday' => 'الأمس', ], ], 'table' => [ 'actions' => 'الإجراءات', 'no-records-available' => 'لا توجد سجلات متاحة.', ], ], 'modal' => [ 'confirm' => [ 'agree-btn' => 'موافق', 'disagree-btn' => 'غير موافق', 'message' => 'هل أنت متأكد أنك تريد تنفيذ هذا الإجراء؟', 'title' => 'هل أنت متأكد؟', ], ], 'tags' => [ 'index' => [ 'title' => 'الوسوم', 'added-tags' => 'الوسوم المضافة', 'save-btn' => 'حفظ الوسم', 'placeholder' => 'اكتب للبحث عن الوسوم', 'add-tag' => 'إضافة ":term"...', 'aquarelle-red' => 'أحمر مائي', 'crushed-cashew' => 'كاجو مطحون', 'beeswax' => 'شمع العسل', 'lemon-chiffon' => 'شيفون الليمون', 'snow-flurry' => 'زوبعة ثلجية', 'honeydew' => 'شمام', ], ], 'layouts' => [ 'powered-by' => [ 'description' => 'Powered by :krayin, an open-source project by :webkul.', ], 'header' => [ 'mega-search' => [ 'title' => 'البحث الشامل', 'tabs' => [ 'leads' => 'العملاء المحتملين', 'quotes' => 'عروض الأسعار', 'persons' => 'الأشخاص', 'products' => 'المنتجات', ], 'explore-all-products' => 'استكشاف جميع المنتجات', 'explore-all-leads' => 'استكشاف جميع العملاء المحتملين', 'explore-all-contacts' => 'استكشاف جميع جهات الاتصال', 'explore-all-quotes' => 'استكشاف جميع عروض الأسعار', 'explore-all-matching-products' => 'استكشاف جميع المنتجات المطابقة لـ ":query" (:count)', 'explore-all-matching-leads' => 'استكشاف جميع العملاء المحتملين المطابقين لـ ":query" (:count)', 'explore-all-matching-contacts' => 'استكشاف جميع جهات الاتصال المطابقة لـ ":query" (:count)', 'explore-all-matching-quotes' => 'استكشاف جميع عروض الأسعار المطابقة لـ ":query" (:count)', ], ], ], 'attributes' => [ 'edit' => [ 'delete' => 'حذف', ], 'lookup' => [ 'click-to-add' => 'انقر للإضافة', 'search' => 'بحث...', 'no-result-found' => 'لم يتم العثور على نتائج', ], ], 'lookup' => [ 'click-to-add' => 'انقر للإضافة', 'no-results' => 'لم يتم العثور على نتائج', 'add-as-new' => 'إضافة كجديد', 'search' => 'بحث...', ], 'flash-group' => [ 'success' => 'نجاح', 'error' => 'خطأ', 'warning' => 'تحذير', 'info' => 'معلومات', ], 'tiny-mce' => [ 'http-error' => 'خطأ في HTTP', 'invalid-json' => 'استجابة JSON غير صالحة من الخادم.', 'upload-failed' => 'فشل تحميل الملف. يرجى المحاولة مرة أخرى.', ], ], 'quotes' => [ 'index' => [ 'title' => 'عروض الأسعار', 'create-btn' => 'إنشاء عرض سعر', 'create-success' => 'تم إنشاء عرض السعر بنجاح.', 'update-success' => 'تم تحديث عرض السعر بنجاح.', 'delete-success' => 'تم حذف عرض السعر بنجاح.', 'delete-failed' => 'لا يمكن حذف عرض السعر.', 'datagrid' => [ 'subject' => 'الموضوع', 'sales-person' => 'مندوب المبيعات', 'expired-at' => 'تاريخ الانتهاء', 'created-at' => 'تاريخ الإنشاء', 'person' => 'الشخص', 'subtotal' => 'المجموع الفرعي', 'discount' => 'الخصم', 'tax' => 'الضريبة', 'adjustment' => 'التعديل', 'grand-total' => 'المجموع الكلي', 'edit' => 'تعديل', 'delete' => 'حذف', 'print' => 'طباعة', ], 'pdf' => [ 'adjustment' => 'التعديل', 'amount' => 'المبلغ', 'billing-address' => 'عنوان الفوترة', 'date' => 'التاريخ', 'discount' => 'الخصم', 'expired-at' => 'تاريخ الانتهاء', 'grand-total' => 'المجموع الكلي', 'person' => 'Person', 'price' => 'السعر', 'product-name' => 'اسم المنتج', 'quantity' => 'الكمية', 'quote-id' => 'رقم عرض السعر', 'sales-person' => 'مندوب المبيعات', 'shipping-address' => 'عنوان الشحن', 'sku' => 'رمز المنتج', 'sub-total' => 'المجموع الفرعي', 'subject' => 'Subject', 'tax' => 'الضريبة', 'title' => 'عرض السعر', ], ], 'create' => [ 'title' => 'إنشاء عرض سعر', 'save-btn' => 'حفظ عرض السعر', 'quote-info' => 'معلومات عرض السعر', 'quote-info-info' => 'أدخل المعلومات الأساسية لعرض السعر.', 'address-info' => 'معلومات العنوان', 'address-info-info' => 'معلومات عن العنوان المتعلق بعرض السعر.', 'quote-items' => 'بنود عرض السعر', 'search-products' => 'البحث عن المنتجات', 'link-to-lead' => 'ربط بالعميل المحتمل', 'quote-item-info' => 'أضف طلب المنتج لهذا العرض.', 'quote-name' => 'اسم عرض السعر', 'quantity' => 'الكمية', 'price' => 'السعر', 'discount' => 'الخصم', 'tax' => 'الضريبة', 'total' => 'المجموع', 'amount' => 'المبلغ', 'add-item' => '+ إضافة بند', 'sub-total' => 'المجموع الفرعي (:symbol)', 'total-discount' => 'الخصم (:symbol)', 'total-tax' => 'الضريبة (:symbol)', 'total-adjustment' => 'التعديل (:symbol)', 'grand-total' => 'المجموع الكلي (:symbol)', 'discount-amount' => 'مبلغ الخصم', 'tax-amount' => 'مبلغ الضريبة', 'adjustment-amount' => 'مبلغ التعديل', 'product-name' => 'اسم المنتج', 'action' => 'الإجراء', ], 'edit' => [ 'title' => 'تعديل عرض السعر', 'save-btn' => 'حفظ عرض السعر', 'quote-info' => 'معلومات عرض السعر', 'quote-info-info' => 'أدخل المعلومات الأساسية لعرض السعر.', 'address-info' => 'معلومات العنوان', 'address-info-info' => 'معلومات عن العنوان المتعلق بعرض السعر.', 'quote-items' => 'بنود عرض السعر', 'link-to-lead' => 'ربط بالعميل المحتمل', 'quote-item-info' => 'أضف طلب المنتج لهذا العرض.', 'quote-name' => 'اسم عرض السعر', 'quantity' => 'الكمية', 'price' => 'السعر', 'search-products' => 'البحث عن المنتجات', 'discount' => 'الخصم', 'tax' => 'الضريبة', 'total' => 'المجموع', 'amount' => 'المبلغ', 'add-item' => '+ إضافة بند', 'sub-total' => 'المجموع الفرعي (:symbol)', 'total-discount' => 'الخصم (:symbol)', 'total-tax' => 'الضريبة (:symbol)', 'total-adjustment' => 'التعديل (:symbol)', 'grand-total' => 'المجموع الكلي (:symbol)', 'discount-amount' => 'مبلغ الخصم', 'tax-amount' => 'مبلغ الضريبة', 'adjustment-amount' => 'مبلغ التعديل', 'product-name' => 'اسم المنتج', 'action' => 'الإجراء', ], ], 'contacts' => [ 'persons' => [ 'index' => [ 'title' => 'الأشخاص', 'create-btn' => 'إنشاء شخص', 'create-success' => 'تم إنشاء الشخص بنجاح.', 'update-success' => 'تم تحديث الشخص بنجاح.', 'all-delete-success' => 'تم حذف جميع الأشخاص المحددين بنجاح.', 'partial-delete-warning' => 'تم حذف بعض الأشخاص بنجاح. لم يتم حذف الآخرين لأنهم مرتبطون بعملاء محتملين.', 'none-delete-warning' => 'لم يتم حذف أي من الأشخاص المحددين لأنهم مرتبطون بعملاء محتملين.', 'no-selection' => 'لم يتم تحديد أي أشخاص للحذف.', 'delete-failed' => 'فشل في حذف الأشخاص المحددين.', 'datagrid' => [ 'contact-numbers' => 'أرقام الاتصال', 'delete' => 'حذف', 'edit' => 'تعديل', 'emails' => 'البريد الإلكتروني', 'id' => 'المعرف', 'view' => 'عرض', 'name' => 'الاسم', 'organization-name' => 'اسم المؤسسة', ], ], 'view' => [ 'title' => ':name', 'about-person' => 'عن الشخص', 'about-organization' => 'حول المؤسسة', 'activities' => [ 'index' => [ 'all' => 'الكل', 'calls' => 'المكالمات', 'meetings' => 'الاجتماعات', 'lunches' => 'الغداء', 'files' => 'الملفات', 'quotes' => 'عروض الأسعار', 'notes' => 'الملاحظات', 'emails' => 'رسائل البريد الإلكتروني', 'by-user' => 'بواسطة :user', 'scheduled-on' => 'مجدول في', 'location' => 'الموقع', 'participants' => 'المشاركون', 'mark-as-done' => 'وضع علامة كمنتهي', 'delete' => 'حذف', 'edit' => 'تعديل', ], 'actions' => [ 'mail' => [ 'btn' => 'بريد', 'title' => 'إنشاء بريد', 'to' => 'إلى', 'cc' => 'نسخة', 'bcc' => 'نسخة مخفية', 'subject' => 'الموضوع', 'send-btn' => 'إرسال', 'message' => 'الرسالة', ], 'file' => [ 'btn' => 'ملف', 'title' => 'إضافة ملف', 'title-control' => 'العنوان', 'name' => 'اسم الملف', 'description' => 'الوصف', 'file' => 'الملف', 'save-btn' => 'حفظ الملف', ], 'note' => [ 'btn' => 'ملاحظة', 'title' => 'إضافة ملاحظة', 'comment' => 'التعليق', 'save-btn' => 'حفظ الملاحظة', ], 'activity' => [ 'btn' => 'نشاط', 'title' => 'إضافة نشاط', 'title-control' => 'العنوان', 'description' => 'الوصف', 'schedule-from' => 'مجدول من', 'schedule-to' => 'مجدول إلى', 'location' => 'الموقع', 'call' => 'مكالمة', 'meeting' => 'اجتماع', 'lunch' => 'غداء', 'save-btn' => 'حفظ النشاط', ], ], ], 'tags' => [ 'create-success' => 'تم إنشاء الوسم بنجاح.', 'destroy-success' => 'تم حذف الوسم بنجاح.', ], ], 'create' => [ 'title' => 'إنشاء شخص', 'save-btn' => 'حفظ الشخص', ], 'edit' => [ 'title' => 'تعديل الشخص', 'save-btn' => 'حفظ الشخص', ], ], 'organizations' => [ 'index' => [ 'title' => 'المؤسسات', 'create-btn' => 'إنشاء مؤسسة', 'create-success' => 'تم إنشاء المؤسسة بنجاح.', 'update-success' => 'تم تحديث المؤسسة بنجاح.', 'delete-success' => 'تم حذف المؤسسة بنجاح.', 'delete-failed' => 'لا يمكن حذف المؤسسة.', 'datagrid' => [ 'delete' => 'حذف', 'edit' => 'تعديل', 'id' => 'المعرف', 'name' => 'الاسم', 'persons-count' => 'عدد الأشخاص', ], ], 'create' => [ 'title' => 'إنشاء مؤسسة', 'save-btn' => 'حفظ المؤسسة', ], 'edit' => [ 'title' => 'تعديل المؤسسة', 'save-btn' => 'حفظ المؤسسة', ], ], ], 'products' => [ 'index' => [ 'title' => 'المنتجات', 'create-btn' => 'إنشاء منتج', 'create-success' => 'تم إنشاء المنتج بنجاح.', 'update-success' => 'تم تحديث المنتج بنجاح.', 'delete-success' => 'تم حذف المنتج بنجاح.', 'delete-failed' => 'لا يمكن حذف المنتج.', 'datagrid' => [ 'allocated' => 'مخصص', 'delete' => 'حذف', 'edit' => 'تعديل', 'id' => 'المعرف', 'in-stock' => 'في المخزون', 'name' => 'الاسم', 'on-hand' => 'متوفر', 'tag-name' => 'اسم الوسم', 'price' => 'السعر', 'sku' => 'رمز المنتج', 'view' => 'عرض', ], ], 'create' => [ 'save-btn' => 'حفظ المنتجات', 'title' => 'إنشاء منتجات', 'general' => 'عام', 'price' => 'السعر', ], 'edit' => [ 'title' => 'تعديل المنتجات', 'save-btn' => 'حفظ المنتجات', 'general' => 'عام', 'price' => 'السعر', ], 'view' => [ 'sku' => 'رمز المنتج', 'all' => 'الكل', 'notes' => 'الملاحظات', 'files' => 'الملفات', 'inventories' => 'المخزون', 'change-logs' => 'سجلات التغيير', 'attributes' => [ 'about-product' => 'عن المنتج', ], 'inventory' => [ 'source' => 'المصدر', 'in-stock' => 'في المخزون', 'allocated' => 'مخصص', 'on-hand' => 'متوفر', 'actions' => 'الإجراءات', 'assign' => 'تعيين', 'add-source' => 'إضافة مصدر', 'location' => 'الموقع', 'add-more' => 'إضافة المزيد', 'save' => 'حفظ', ], ], ], 'settings' => [ 'title' => 'الإعدادات', 'groups' => [ 'index' => [ 'create-btn' => 'إنشاء مجموعة', 'title' => 'المجموعات', 'create-success' => 'تم إنشاء المجموعة بنجاح.', 'update-success' => 'تم تحديث المجموعة بنجاح.', 'destroy-success' => 'تم حذف المجموعة بنجاح.', 'delete-failed' => 'لا يمكن حذف المجموعة.', 'delete-failed-associated-users' => 'لا يمكن حذف المجموعة لأنها مستخدمة من قبل المستخدمين.', 'datagrid' => [ 'delete' => 'حذف', 'description' => 'الوصف', 'edit' => 'تعديل', 'id' => 'المعرف', 'name' => 'الاسم', ], 'edit' => [ 'title' => 'تعديل المجموعة', ], 'create' => [ 'name' => 'الاسم', 'title' => 'إنشاء مجموعة', 'description' => 'الوصف', 'save-btn' => 'حفظ المجموعة', ], ], ], 'roles' => [ 'index' => [ 'being-used' => 'لا يمكن حذف الدور، حيث أنه قيد الاستخدام في مستخدم الإدارة.', 'create-btn' => 'إنشاء الأدوار', 'create-success' => 'تم إنشاء الدور بنجاح.', 'current-role-delete-error' => 'لا يمكن حذف الدور المخصص للمستخدم الحالي.', 'delete-failed' => 'لا يمكن حذف الدور.', 'delete-success' => 'تم حذف الدور بنجاح.', 'last-delete-error' => 'مطلوب دور واحد على الأقل.', 'settings' => 'الإعدادات', 'title' => 'الأدوار', 'update-success' => 'تم تحديث الدور بنجاح.', 'user-define-error' => 'لا يمكن حذف دور النظام.', 'datagrid' => [ 'all' => 'الكل', 'custom' => 'مخصص', 'delete' => 'حذف', 'description' => 'الوصف', 'edit' => 'تعديل', 'id' => 'المعرف', 'name' => 'الاسم', 'permission-type' => 'نوع الإذن', ], ], 'create' => [ 'access-control' => 'التحكم في الوصول', 'all' => 'الكل', 'back-btn' => 'رجوع', 'custom' => 'مخصص', 'description' => 'الوصف', 'general' => 'عام', 'name' => 'الاسم', 'permissions' => 'الأذونات', 'save-btn' => 'حفظ الدور', 'title' => 'إنشاء دور', ], 'edit' => [ 'access-control' => 'التحكم في الوصول', 'all' => 'الكل', 'back-btn' => 'رجوع', 'custom' => 'مخصص', 'description' => 'الوصف', 'general' => 'عام', 'name' => 'الاسم', 'permissions' => 'الأذونات', 'save-btn' => 'حفظ الدور', 'title' => 'تعديل الدور', ], ], 'types' => [ 'index' => [ 'create-btn' => 'إنشاء نوع', 'create-success' => 'تم إنشاء النوع بنجاح.', 'delete-failed' => 'لا يمكن حذف النوع.', 'delete-success' => 'تم حذف النوع بنجاح.', 'title' => 'الأنواع', 'update-success' => 'تم تحديث النوع بنجاح.', 'datagrid' => [ 'delete' => 'حذف', 'description' => 'الوصف', 'edit' => 'تعديل', 'id' => 'المعرف', 'name' => 'الاسم', ], 'create' => [ 'name' => 'الاسم', 'save-btn' => 'حفظ النوع', 'title' => 'إنشاء نوع', ], 'edit' => [ 'title' => 'تعديل النوع', ], ], ], 'sources' => [ 'index' => [ 'title' => 'المصادر', 'create-btn' => 'إنشاء مصدر', 'create-success' => 'تم إنشاء المصدر بنجاح.', 'delete-failed' => 'لا يمكن حذف المصدر.', 'delete-success' => 'تم حذف المصدر بنجاح.', 'update-success' => 'تم تحديث المصدر بنجاح.', 'delete-failed-associated-leads' => 'لا يمكن حذف المصدر لأنه مرتبط بعملاء محتملين موجودين. يرجى فصل أو تحديث هؤلاء العملاء قبل الحذف.', 'datagrid' => [ 'delete' => 'حذف', 'edit' => 'تعديل', 'id' => 'المعرف', 'name' => 'الاسم', ], 'create' => [ 'name' => 'اسم', 'save-btn' => 'حفظ المصدر', 'title' => 'إنشاء مصدر', ], 'edit' => [ 'title' => 'تعديل المصدر', ], ], ], 'workflows' => [ 'index' => [ 'title' => 'سير العمل', 'create-btn' => 'إنشاء سير عمل', 'create-success' => 'تم إنشاء سير العمل بنجاح.', 'update-success' => 'تم تحديث سير العمل بنجاح.', 'delete-success' => 'تم حذف سير العمل بنجاح.', 'delete-failed' => 'لا يمكن حذف سير العمل.', 'datagrid' => [ 'delete' => 'حذف', 'description' => 'الوصف', 'edit' => 'تعديل', 'id' => 'المعرف', 'name' => 'الاسم', ], ], 'helpers' => [ 'update-related-leads' => 'تحديث العملاء المحتملين ذوي الصلة', 'send-email-to-sales-owner' => 'إرسال بريد إلكتروني إلى مسؤول المبيعات', 'send-email-to-participants' => 'إرسال بريد إلكتروني إلى المشاركين', 'add-webhook' => 'إضافة Webhook', 'update-lead' => 'تحديث العميل المحتمل', 'update-person' => 'تحديث الشخص', 'send-email-to-person' => 'إرسال بريد إلكتروني إلى الشخص', 'add-tag' => 'إضافة علامة', 'add-note-as-activity' => 'إضافة ملاحظة كنشاط', 'update-quote' => 'تحديث العرض', ], 'create' => [ 'title' => 'إنشاء سير عمل', 'event' => 'الحدث', 'back-btn' => 'رجوع', 'save-btn' => 'حفظ سير العمل', 'name' => 'الاسم', 'basic-details' => 'التفاصيل الأساسية', 'description' => 'الوصف', 'actions' => 'الإجراءات', 'basic-details-info' => 'ضع المعلومات الأساسية لسير العمل.', 'event-info' => 'يقوم الحدث بتشغيل والتحقق من الشروط وتنفيذ الإجراءات المحددة مسبقًا.', 'conditions' => 'الشروط', 'conditions-info' => 'الشروط هي قواعد تتحقق من السيناريوهات، ويتم تشغيلها في مناسبات محددة.', 'actions-info' => 'الإجراء لا يقلل فقط من عبء العمل ولكنه يجعل أتمتة CRM أسهل بكثير', 'value' => 'القيمة', 'condition-type' => 'نوع الشرط', 'all-condition-are-true' => 'جميع الشروط صحيحة', 'any-condition-are-true' => 'أي شرط صحيح', 'add-condition' => 'إضافة شرط', 'add-action' => 'إضافة إجراء', 'yes' => 'نعم', 'no' => 'لا', 'email' => 'البريد الإلكتروني', 'is-equal-to' => 'يساوي', 'is-not-equal-to' => 'لا يساوي', 'equals-or-greater-than' => 'يساوي أو أكبر من', 'equals-or-less-than' => 'يساوي أو أقل من', 'greater-than' => 'أكبر من', 'less-than' => 'أقل من', 'type' => 'النوع', 'contain' => 'يحتوي', 'contains' => 'يحتوي', 'does-not-contain' => 'لا يحتوي', ], 'edit' => [ 'title' => 'تعديل سير العمل', 'event' => 'الحدث', 'back-btn' => 'رجوع', 'save-btn' => 'حفظ سير العمل', 'name' => 'الاسم', 'basic-details' => 'التفاصيل الأساسية', 'description' => 'الوصف', 'actions' => 'الإجراءات', 'type' => 'النوع', 'basic-details-info' => 'ضع المعلومات الأساسية لسير العمل.', 'event-info' => 'يقوم الحدث بتشغيل والتحقق من الشروط وتنفيذ الإجراءات المحددة مسبقًا.', 'conditions' => 'الشروط', 'conditions-info' => 'الشروط هي قواعد تتحقق من السيناريوهات، ويتم تشغيلها في مناسبات محددة.', 'actions-info' => 'الإجراء لا يقلل فقط من عبء العمل ولكنه يجعل أتمتة CRM أسهل بكثير', 'value' => 'القيمة', 'condition-type' => 'نوع الشرط', 'all-condition-are-true' => 'جميع الشروط صحيحة', 'any-condition-are-true' => 'أي شرط صحيح', 'add-condition' => 'إضافة شرط', 'add-action' => 'إضافة إجراء', 'yes' => 'نعم', 'no' => 'لا', 'email' => 'البريد الإلكتروني', 'is-equal-to' => 'يساوي', 'is-not-equal-to' => 'لا يساوي', 'equals-or-greater-than' => 'يساوي أو أكبر من', 'equals-or-less-than' => 'يساوي أو أقل من', 'greater-than' => 'أكبر من', 'less-than' => 'أقل من', 'contain' => 'يحتوي', 'contains' => 'يحتوي', 'does-not-contain' => 'لا يحتوي', ], ], 'webforms' => [ 'index' => [ 'title' => 'نماذج الويب', 'create-btn' => 'إنشاء نموذج ويب', 'create-success' => 'تم إنشاء نموذج الويب بنجاح.', 'update-success' => 'تم تحديث نموذج الويب بنجاح.', 'delete-success' => 'تم حذف نموذج الويب بنجاح.', 'delete-failed' => 'لا يمكن حذف نموذج الويب.', 'datagrid' => [ 'id' => 'المعرف', 'title' => 'العنوان', 'edit' => 'تعديل', 'delete' => 'حذف', ], ], 'create' => [ 'title' => 'إنشاء استمارة ويب', 'add-attribute-btn' => 'زر إضافة سمة', 'attribute-label-color' => 'لون تسمية السمة', 'attributes' => 'السمات', 'attributes-info' => 'أضف سمات مخصصة إلى النموذج.', 'background-color' => 'لون الخلفية', 'create-lead' => 'إنشاء عميل محتمل', 'customize-webform' => 'تخصيص نموذج الويب', 'customize-webform-info' => 'خصص نموذج الويب الخاص بك بألوان العناصر التي تختارها.', 'description' => 'الوصف', 'display-custom-message' => 'عرض رسالة مخصصة', 'form-background-color' => 'لون خلفية النموذج', 'form-submit-btn-color' => 'لون زر إرسال النموذج', 'form-submit-button-color' => 'لون زر إرسال النموذج', 'form-title-color' => 'لون عنوان النموذج', 'general' => 'عام', 'leads' => 'العملاء المحتملون', 'person' => 'شخص', 'save-btn' => 'حفظ نموذج الويب', 'submit-button-label' => 'تسمية زر الإرسال', 'submit-success-action' => 'إجراء نجاح الإرسال', 'redirect-to-url' => 'إعادة التوجيه إلى الرابط', 'choose-value' => 'اختر قيمة', 'select-file' => 'اختر ملف', 'select-image' => 'اختر صورة', 'enter-value' => 'أدخل القيمة', ], 'edit' => [ 'add-attribute-btn' => 'زر إضافة سمة', 'attribute-label-color' => 'لون تسمية السمة', 'attributes' => 'السمات', 'attributes-info' => 'أضف سمات مخصصة إلى النموذج.', 'background-color' => 'لون الخلفية', 'choose-value' => 'اختر القيمة', 'code-snippet' => 'مقتطف الكود', 'copied' => 'تم النسخ', 'copy' => 'نسخ', 'create-lead' => 'إنشاء عميل محتمل', 'customize-webform' => 'تخصيص نموذج الويب', 'customize-webform-info' => 'خصص نموذج الويب الخاص بك بألوان العناصر التي تختارها.', 'description' => 'الوصف', 'display-custom-message' => 'عرض رسالة مخصصة', 'embed' => 'تضمين', 'enter-value' => 'أدخل القيمة', 'form-background-color' => 'لون خلفية النموذج', 'form-submit-btn-color' => 'لون زر إرسال النموذج', 'form-submit-button-color' => 'لون زر إرسال النموذج', 'form-title-color' => 'لون عنوان النموذج', 'general' => 'عام', 'leads' => 'العملاء المحتملون', 'person' => 'شخص', 'preview' => 'معاينة', 'public-url' => 'الرابط العام', 'redirect-to-url' => 'إعادة التوجيه إلى الرابط', 'save-btn' => 'حفظ نموذج الويب', 'select-file' => 'اختر ملف', 'select-image' => 'اختر صورة', 'submit-button-label' => 'تسمية زر الإرسال', 'submit-success-action' => 'إجراء نجاح الإرسال', 'title' => 'تعديل استمارة ويب', ], ], 'email-template' => [ 'index' => [ 'create-btn' => 'إنشاء قالب بريد إلكتروني', 'title' => 'قوالب البريد الإلكتروني', 'create-success' => 'تم إنشاء قالب البريد الإلكتروني بنجاح.', 'update-success' => 'تم تحديث قالب البريد الإلكتروني بنجاح.', 'delete-success' => 'تم حذف قالب البريد الإلكتروني بنجاح.', 'delete-failed' => 'لا يمكن حذف قالب البريد الإلكتروني.', 'datagrid' => [ 'delete' => 'حذف', 'edit' => 'تعديل', 'id' => 'المعرف', 'name' => 'الاسم', 'subject' => 'الموضوع', ], ], 'create' => [ 'title' => 'إنشاء قالب بريد إلكتروني', 'save-btn' => 'حفظ قالب البريد الإلكتروني', 'email-template' => 'قالب البريد الإلكتروني', 'subject' => 'الموضوع', 'content' => 'المحتوى', 'subject-placeholders' => 'العناصر النائبة للموضوع', 'general' => 'عام', 'name' => 'الاسم', ], 'edit' => [ 'title' => 'تعديل قالب البريد الإلكتروني', 'save-btn' => 'حفظ قالب البريد الإلكتروني', 'email-template' => 'قالب البريد الإلكتروني', 'subject' => 'الموضوع', 'content' => 'المحتوى', 'subject-placeholders' => 'العناصر النائبة للموضوع', 'general' => 'عام', 'name' => 'الاسم', ], ], 'marketing' => [ 'events' => [ 'index' => [ 'create-btn' => 'إنشاء حدث', 'title' => 'الأحداث', 'create-success' => 'تم إنشاء الحدث بنجاح.', 'update-success' => 'تم تحديث الحدث بنجاح.', 'delete-success' => 'تم حذف الحدث بنجاح.', 'delete-failed' => 'لا يمكن حذف الحدث.', 'mass-delete-success' => 'تم حذف الأحداث بنجاح', 'delete-failed-associated-campaigns' => 'لا يمكن حذف الحدث لأنه مرتبط بحملات موجودة. يرجى إلغاء ربط أو تحديث تلك الحملات قبل الحذف.', 'datagrid' => [ 'delete' => 'حذف', 'edit' => 'تحرير', 'id' => 'المعرف', 'name' => 'الاسم', 'description' => 'الوصف', 'date' => 'التاريخ', ], 'create' => [ 'title' => 'إنشاء حدث', 'name' => 'الاسم', 'date' => 'التاريخ', 'description' => 'الوصف', 'save-btn' => 'حفظ الحدث', ], 'edit' => [ 'title' => 'تحرير الحدث', ], ], ], 'campaigns' => [ 'index' => [ 'create-btn' => 'إنشاء حملة', 'title' => 'الحملات', 'create-success' => 'تم إنشاء الحملة بنجاح.', 'update-success' => 'تم تحديث الحملة بنجاح.', 'delete-success' => 'تم حذف الحملة بنجاح.', 'delete-failed' => 'لا يمكن حذف الحملة.', 'mass-delete-success' => 'تم حذف الحملات بنجاح.', 'datagrid' => [ 'id' => 'المعرف', 'name' => 'الاسم', 'subject' => 'الموضوع', 'status' => 'الحالة', 'active' => 'نشط', 'inactive' => 'غير نشط', 'edit' => 'تحرير', 'delete' => 'حذف', ], 'create' => [ 'title' => 'إنشاء حملة', 'name' => 'الاسم', 'type' => 'النوع', 'subject' => 'الموضوع', 'event' => 'الحدث', 'email-template' => 'نموذج البريد الإلكتروني', 'status' => 'الحالة', ], 'edit' => [ 'title' => 'تحرير الحملة', ], ], ], ], 'tags' => [ 'index' => [ 'create-btn' => 'إنشاء وسم', 'title' => 'الوسوم', 'create-success' => 'تم إنشاء الوسم بنجاح.', 'update-success' => 'تم تحديث الوسم بنجاح.', 'delete-success' => 'تم حذف الوسم بنجاح.', 'delete-failed' => 'لا يمكن حذف الوسم.', 'datagrid' => [ 'delete' => 'حذف', 'edit' => 'تعديل', 'id' => 'المعرف', 'name' => 'الاسم', 'users' => 'المستخدمون', 'created-at' => 'تم الإنشاء في', ], 'create' => [ 'name' => 'الاسم', 'save-btn' => 'حفظ الوسم', 'title' => 'إنشاء وسم', 'color' => 'اللون', ], 'edit' => [ 'title' => 'تعديل الوسم', ], ], ], 'users' => [ 'index' => [ 'create-btn' => 'إنشاء مستخدم', 'create-success' => 'تم إنشاء المستخدم بنجاح.', 'delete-failed' => 'لا يمكن حذف المستخدم.', 'delete-success' => 'تم حذف المستخدم بنجاح.', 'last-delete-error' => 'مطلوب مستخدم واحد على الأقل.', 'mass-delete-failed' => 'لا يمكن حذف المستخدمين.', 'mass-delete-success' => 'تم حذف المستخدمين بنجاح.', 'mass-update-failed' => 'لا يمكن تحديث المستخدمين.', 'mass-update-success' => 'تم تحديث المستخدمين بنجاح.', 'title' => 'المستخدمون', 'update-success' => 'تم تحديث المستخدم بنجاح.', 'user-define-error' => 'لا يمكن حذف مستخدم النظام.', 'active' => 'نشط', 'inactive' => 'غير نشط', 'datagrid' => [ 'active' => 'نشط', 'created-at' => 'تم الإنشاء في', 'delete' => 'حذف', 'edit' => 'تعديل', 'email' => 'البريد الإلكتروني', 'id' => 'المعرف', 'inactive' => 'غير نشط', 'name' => 'الاسم', 'status' => 'الحالة', 'update-status' => 'تحديث الحالة', 'users' => 'المستخدمون', ], 'create' => [ 'confirm-password' => 'تأكيد كلمة المرور', 'email' => 'البريد الإلكتروني', 'general' => 'عام', 'global' => 'عالمي', 'group' => 'مجموعة', 'individual' => 'فردي', 'name' => 'الاسم', 'password' => 'كلمة المرور', 'permission' => 'الصلاحية', 'role' => 'الدور', 'save-btn' => 'حفظ المستخدم', 'status' => 'الحالة', 'title' => 'إنشاء مستخدم', 'view-permission' => 'عرض الصلاحية', 'select-at-lest-one-group' => 'Select at least one group', ], 'edit' => [ 'title' => 'تعديل المستخدم', ], ], ], 'pipelines' => [ 'index' => [ 'title' => 'خطوط الأنابيب', 'create-btn' => 'إنشاء خط أنابيب', 'create-success' => 'تم إنشاء خط الأنابيب بنجاح.', 'update-success' => 'تم تحديث خط الأنابيب بنجاح.', 'default-required' => 'مطلوب وجود خط أنابيب افتراضي واحد على الأقل.', 'delete-success' => 'تم حذف خط الأنابيب بنجاح.', 'delete-failed' => 'لا يمكن حذف خط الأنابيب.', 'default-delete-error' => 'لا يمكن حذف خط الأنابيب الافتراضي.', 'datagrid' => [ 'delete' => 'حذف', 'edit' => 'تعديل', 'id' => 'المعرف', 'is-default' => 'افتراضي', 'name' => 'الاسم', 'no' => 'لا', 'rotten-days' => 'أيام التعفن', 'yes' => 'نعم', ], ], 'create' => [ 'title' => 'إنشاء خط أنابيب', 'save-btn' => 'حفظ خط الأنابيب', 'name' => 'الاسم', 'rotten-days' => 'أيام التعفن', 'mark-as-default' => 'تعيين كافتراضي', 'general' => 'عام', 'probability' => 'الاحتمالية (%)', 'new-stage' => 'جديد', 'won-stage' => 'فاز', 'lost-stage' => 'خسر', 'stage-btn' => 'إضافة مرحلة', 'stages' => 'المراحل', 'duplicate-name' => 'لا يمكن تكرار حقل "الاسم"', 'delete-stage' => 'حذف المرحلة', 'add-new-stages' => 'إضافة مراحل جديدة', 'add-stage-info' => 'أضف مرحلة جديدة لخط الأنابيب الخاص بك', 'newly-added' => 'تمت الإضافة حديثًا', 'stage-delete-success' => 'تم حذف المرحلة بنجاح', ], 'edit' => [ 'title' => 'تعديل خط الأنابيب', 'save-btn' => 'حفظ خط الأنابيب', 'name' => 'الاسم', 'rotten-days' => 'أيام التعفن', 'mark-as-default' => 'تعيين كافتراضي', 'general' => 'عام', 'probability' => 'الاحتمالية (%)', 'new-stage' => 'جديد', 'won-stage' => 'فاز', 'lost-stage' => 'خسر', 'stage-btn' => 'إضافة مرحلة', 'stages' => 'المراحل', 'duplicate-name' => 'لا يمكن تكرار حقل "الاسم"', 'delete-stage' => 'حذف المرحلة', 'add-new-stages' => 'إضافة مراحل جديدة', 'add-stage-info' => 'أضف مرحلة جديدة لخط الأنابيب الخاص بك', 'stage-delete-success' => 'تم حذف المرحلة بنجاح', ], ], 'webhooks' => [ 'index' => [ 'title' => 'الويب هوكس', 'create-btn' => 'إنشاء ويب هوك', 'create-success' => 'تم إنشاء الويب هوك بنجاح.', 'update-success' => 'تم تحديث الويب هوك بنجاح.', 'delete-success' => 'تم حذف الويب هوك بنجاح.', 'delete-failed' => 'لا يمكن حذف الويب هوك.', 'datagrid' => [ 'id' => 'المعرف', 'delete' => 'حذف', 'edit' => 'تعديل', 'name' => 'الاسم', 'entity-type' => 'نوع الكيان', 'end-point' => 'نقطة النهاية', ], ], 'create' => [ 'title' => 'إنشاء ويب هوك', 'save-btn' => 'حفظ الويب هوك', 'info' => 'أدخل تفاصيل الويب هوكس', 'url-and-parameters' => 'الرابط والمعلمات', 'method' => 'الطريقة', 'post' => 'إرسال', 'put' => 'تحديث', 'url-endpoint' => 'نقطة نهاية الرابط', 'parameters' => 'المعلمات', 'add-new-parameter' => 'إضافة معلمة جديدة', 'url-preview' => 'معاينة الرابط:', 'headers' => 'الرؤوس', 'add-new-header' => 'إضافة رأس جديد', 'body' => 'الجسم', 'default' => 'افتراضي', 'x-www-form-urlencoded' => 'x-www-form-urlencoded', 'key-and-value' => 'المفتاح والقيمة', 'add-new-payload' => 'إضافة حمولة جديدة', 'raw' => 'خام', 'general' => 'عام', 'name' => 'الاسم', 'entity-type' => 'نوع الكيان', 'insert-placeholder' => 'إدراج العنصر النائب', 'description' => 'الوصف', 'json' => 'Json', 'text' => 'نص', ], 'edit' => [ 'title' => 'تعديل الويب هوك', 'edit-btn' => 'حفظ الويب هوك', 'save-btn' => 'حفظ الويب هوك', 'info' => 'أدخل تفاصيل الويب هوكس', 'url-and-parameters' => 'الرابط والمعلمات', 'method' => 'الطريقة', 'post' => 'إرسال', 'put' => 'تحديث', 'url-endpoint' => 'نقطة نهاية الرابط', 'parameters' => 'المعلمات', 'add-new-parameter' => 'إضافة معلمة جديدة', 'url-preview' => 'معاينة الرابط:', 'headers' => 'الرؤوس', 'add-new-header' => 'إضافة رأس جديد', 'body' => 'الجسم', 'default' => 'افتراضي', 'x-www-form-urlencoded' => 'x-www-form-urlencoded', 'key-and-value' => 'المفتاح والقيمة', 'add-new-payload' => 'إضافة حمولة جديدة', 'raw' => 'خام', 'general' => 'عام', 'name' => 'الاسم', 'entity-type' => 'نوع الكيان', 'insert-placeholder' => 'إدراج العنصر النائب', 'description' => 'الوصف', 'json' => 'Json', 'text' => 'نص', ], ], 'warehouses' => [ 'index' => [ 'title' => 'المستودعات', 'create-btn' => 'إنشاء مستودع', 'create-success' => 'تم إنشاء المستودع بنجاح.', 'name-exists' => 'اسم المستودع موجود بالفعل.', 'update-success' => 'تم تحديث المستودع بنجاح.', 'delete-success' => 'تم حذف المستودع بنجاح.', 'delete-failed' => 'لا يمكن حذف المستودع.', 'datagrid' => [ 'id' => 'المعرف', 'name' => 'الاسم', 'contact-name' => 'اسم جهة الاتصال', 'delete' => 'حذف', 'edit' => 'تعديل', 'view' => 'عرض', 'created-at' => 'تم الإنشاء في', 'products' => 'المنتجات', 'contact-emails' => 'البريد الإلكتروني للاتصال', 'contact-numbers' => 'أرقام الاتصال', ], ], 'create' => [ 'title' => 'إنشاء مستودع', 'save-btn' => 'حفظ المستودع', 'contact-info' => 'معلومات الاتصال', ], 'edit' => [ 'title' => 'تعديل المستودع', 'save-btn' => 'حفظ المستودع', 'contact-info' => 'معلومات الاتصال', ], 'view' => [ 'all' => 'الكل', 'notes' => 'الملاحظات', 'files' => 'الملفات', 'location' => 'الموقع', 'change-logs' => 'سجلات التغيير', 'locations' => [ 'action' => 'إجراء', 'add-location' => 'إضافة موقع', 'create-success' => 'تم إنشاء الموقع بنجاح.', 'delete' => 'حذف', 'delete-failed' => 'لا يمكن حذف الموقع.', 'delete-success' => 'تم حذف الموقع بنجاح.', 'name' => 'الاسم', 'save-btn' => 'حفظ', ], 'general-information' => [ 'title' => 'معلومات عامة', ], 'contact-information' => [ 'title' => 'معلومات الاتصال', ], ], ], 'attributes' => [ 'index' => [ 'title' => 'السمات', 'create-btn' => 'إنشاء سمة', 'create-success' => 'تم إنشاء السمة بنجاح.', 'update-success' => 'تم تحديث السمة بنجاح.', 'delete-success' => 'تم حذف السمة بنجاح.', 'delete-failed' => 'لا يمكن حذف السمة.', 'user-define-error' => 'لا يمكن حذف سمة النظام.', 'mass-delete-failed' => 'لا يمكن حذف سمات النظام.', 'datagrid' => [ 'yes' => 'نعم', 'no' => 'لا', 'id' => 'المعرف', 'code' => 'الرمز', 'name' => 'الاسم', 'entity-type' => 'نوع الكيان', 'type' => 'النوع', 'is-default' => 'افتراضي', 'edit' => 'تعديل', 'delete' => 'حذف', 'entity-types' => [ 'leads' => 'العملاء المحتملين', 'organizations' => 'المنظمات', 'persons' => 'الأشخاص', 'products' => 'المنتجات', 'quotes' => 'عروض الأسعار', 'warehouses' => 'المستودعات', ], 'types' => [ 'text' => 'نص', 'textarea' => 'منطقة نصية', 'price' => 'سعر', 'boolean' => 'قيمة منطقية', 'select' => 'قائمة منسدلة', 'multiselect' => 'اختيار متعدد', 'checkbox' => 'مربع اختيار', 'email' => 'بريد إلكتروني', 'address' => 'عنوان', 'phone' => 'هاتف', 'lookup' => 'بحث', 'datetime' => 'تاريخ ووقت', 'date' => 'تاريخ', 'image' => 'صورة', 'file' => 'ملف', ], ], ], 'create' => [ 'title' => 'إنشاء سمة', 'save-btn' => 'حفظ السمة', 'code' => 'الرمز', 'name' => 'الاسم', 'entity-type' => 'نوع الكيان', 'type' => 'النوع', 'validations' => 'التحققات', 'is-required' => 'مطلوب', 'input-validation' => 'التحقق من الإدخال', 'is-unique' => 'فريد', 'labels' => 'التسميات', 'general' => 'عام', 'numeric' => 'رقمي', 'decimal' => 'عشري', 'url' => 'رابط', 'options' => 'الخيارات', 'option-type' => 'نوع الخيار', 'lookup-type' => 'نوع البحث', 'add-option' => 'إضافة خيار', 'save-option' => 'حفظ الخيار', 'option-name' => 'اسم الخيار', 'add-attribute-options' => 'إضافة خيارات السمة', 'text' => 'نص', 'textarea' => 'منطقة نص', 'price' => 'السعر', 'boolean' => 'منطقي', 'select' => 'اختيار', 'multiselect' => 'اختيار متعدد', 'email' => 'بريد إلكتروني', 'address' => 'عنوان', 'phone' => 'هاتف', 'datetime' => 'تاريخ ووقت', 'date' => 'تاريخ', 'image' => 'صورة', 'file' => 'ملف', 'lookup' => 'بحث', 'entity_type' => 'نوع الكيان', 'checkbox' => 'خانة اختيار', 'is_required' => 'مطلوب', 'is_unique' => 'فريد', 'actions' => 'الإجراءات', ], 'edit' => [ 'actions' => 'الإجراءات', 'add-attribute-options' => 'إضافة خيارات السمة', 'add-option' => 'إضافة خيار', 'address' => 'عنوان', 'boolean' => 'منطقي', 'checkbox' => 'خانة اختيار', 'code' => 'الرمز', 'date' => 'تاريخ', 'datetime' => 'تاريخ ووقت', 'decimal' => 'عشري', 'email' => 'بريد إلكتروني', 'entity-type' => 'نوع الكيان', 'entity_type' => 'نوع الكيان', 'file' => 'ملف', 'general' => 'عام', 'image' => 'صورة', 'input-validation' => 'التحقق من الإدخال', 'is-required' => 'مطلوب', 'is-unique' => 'فريد', 'is_required' => 'مطلوب', 'is_unique' => 'فريد', 'labels' => 'التسميات', 'lookup' => 'بحث', 'lookup-type' => 'نوع البحث', 'multiselect' => 'اختيار متعدد', 'name' => 'الاسم', 'numeric' => 'رقمي', 'option-deleted' => 'Attribute Option is deleted successfully', 'option-name' => 'اسم الخيار', 'option-type' => 'نوع الخيار', 'options' => 'الخيارات', 'phone' => 'هاتف', 'price' => 'السعر', 'save-btn' => 'حفظ السمة', 'save-option' => 'حفظ الخيار', 'select' => 'اختيار', 'text' => 'نص', 'textarea' => 'منطقة نص', 'title' => 'تعديل السمة', 'type' => 'النوع', 'url' => 'رابط', 'validations' => 'التحققات', ], ], 'data-transfer' => [ 'imports' => [ 'create' => [ 'action' => 'إجراء', 'allowed-errors' => 'الأخطاء المسموح بها', 'back-btn' => 'عودة', 'create-update' => 'إنشاء/تحديث', 'delete' => 'حذف', 'download-sample' => 'تنزيل العينة', 'field-separator' => 'فاصل الحقول', 'file' => 'ملف', 'general' => 'عام', 'images-directory' => 'مسار دليل الصور', 'process-in-queue' => 'معالجة في قائمة الانتظار', 'results' => 'النتائج', 'save-btn' => 'حفظ الاستيراد', 'settings' => 'الإعدادات', 'skip-errors' => 'تخطي الأخطاء', 'stop-on-errors' => 'التوقف عند الأخطاء', 'title' => 'إنشاء استيراد', 'type' => 'النوع', 'validation-strategy' => 'استراتيجية التحقق', ], 'edit' => [ 'action' => 'إجراء', 'allowed-errors' => 'الأخطاء المسموح بها', 'back-btn' => 'عودة', 'create-update' => 'إنشاء/تحديث', 'delete' => 'حذف', 'download-sample' => 'تنزيل العينة', 'field-separator' => 'فاصل الحقول', 'file' => 'ملف', 'general' => 'عام', 'images-directory' => 'مسار دليل الصور', 'process-in-queue' => 'معالجة في قائمة الانتظار', 'results' => 'النتائج', 'save-btn' => 'حفظ الاستيراد', 'settings' => 'الإعدادات', 'skip-errors' => 'تخطي الأخطاء', 'stop-on-errors' => 'التوقف عند الأخطاء', 'title' => 'تحرير الاستيراد', 'type' => 'النوع', 'validation-strategy' => 'استراتيجية التحقق', ], 'index' => [ 'button-title' => 'إنشاء استيراد', 'title' => 'الاستيرادات', 'datagrid' => [ 'actions' => 'الإجراءات', 'completed-at' => 'اكتمل في', 'created' => 'تم الإنشاء', 'delete' => 'حذف', 'deleted' => 'تم الحذف', 'edit' => 'تحرير', 'error-file' => 'ملف الأخطاء', 'id' => 'الرقم التعريفي', 'started-at' => 'بدأ في', 'state' => 'الحالة', 'summary' => 'الملخص', 'type' => 'النوع', 'updated' => 'تم التحديث', 'uploaded-file' => 'الملف المرفوع', ], ], 'import' => [ 'back-btn' => 'عودة', 'completed-batches' => 'إجمالي الدفعات المكتملة:', 'download-error-report' => 'تنزيل التقرير الكامل', 'edit-btn' => 'تحرير', 'imported-info' => 'تهانينا! تم استيرادك بنجاح.', 'importing-info' => 'الاستيراد قيد المعالجة', 'indexing-info' => 'تجميع الموارد (الأسعار، المخزون و Elastic Search) جارٍ', 'linking-info' => 'ربط الموارد جارٍ', 'progress' => 'التقدم:', 'title' => 'استيراد', 'total-batches' => 'إجمالي الدفعات:', 'total-created' => 'إجمالي السجلات التي تم إنشاؤها:', 'total-deleted' => 'إجمالي السجلات المحذوفة:', 'total-errors' => 'إجمالي الأخطاء:', 'total-invalid-rows' => 'إجمالي الصفوف غير الصالحة:', 'total-rows-processed' => 'إجمالي الصفوف المعالجة:', 'total-updated' => 'إجمالي السجلات التي تم تحديثها:', 'validate' => 'التحقق', 'validate-info' => 'انقر على التحقق من البيانات لفحص الاستيراد.', 'validating-info' => 'بدأت قراءة البيانات والتحقق منها', 'validation-failed-info' => 'استيرادك غير صالح. يرجى إصلاح الأخطاء التالية والمحاولة مرة أخرى.', 'validation-success-info' => 'استيرادك صالح. انقر على استيراد لبدء عملية الاستيراد.', ], 'create-success' => 'تم إنشاء الاستيراد بنجاح.', 'delete-failed' => 'فشل حذف الاستيراد بشكل غير متوقع.', 'delete-success' => 'تم حذف الاستيراد بنجاح.', 'not-valid' => 'الاستيراد غير صالح', 'nothing-to-import' => 'لا توجد موارد لاستيرادها.', 'setup-queue-error' => 'يرجى تغيير برنامج تشغيل قائمة الانتظار إلى "قاعدة البيانات" أو "ريديس" لبدء عملية الاستيراد.', 'update-success' => 'تم تحديث الاستيراد بنجاح.', ], ], ], 'activities' => [ 'index' => [ 'title' => 'الأنشطة', 'datagrid' => [ 'comment' => 'تعليق', 'created_at' => 'تاريخ الإنشاء', 'created_by' => 'تم الإنشاء بواسطة', 'edit' => 'تحرير', 'id' => 'المعرف', 'done' => 'تم', 'not-done' => 'لم يتم', 'lead' => 'القيادة', 'mass-delete' => 'حذف جماعي', 'mass-update' => 'تحديث جماعي', 'schedule-from' => 'جدولة من', 'schedule-to' => 'جدولة إلى', 'schedule_from' => 'جدولة من', 'schedule_to' => 'جدولة إلى', 'title' => 'العنوان', 'is_done' => 'تم', 'type' => 'النوع', 'update' => 'تحديث', 'call' => 'مكالمة', 'meeting' => 'اجتماع', 'lunch' => 'غداء', ], ], 'edit' => [ 'title' => 'تحرير النشاط', 'back-btn' => 'رجوع', 'save-btn' => 'حفظ النشاط', 'type' => 'نوع النشاط', 'call' => 'مكالمة', 'meeting' => 'اجتماع', 'lunch' => 'غداء', 'schedule_to' => 'جدولة إلى', 'schedule_from' => 'جدولة من', 'location' => 'الموقع', 'comment' => 'تعليق', 'lead' => 'القيادة', 'participants' => 'المشاركون', 'general' => 'عام', 'persons' => 'الأشخاص', 'no-result-found' => 'لم يتم العثور على سجلات.', 'users' => 'المستخدمون', ], 'updated' => 'تم تحديث :attribute', 'created' => 'تم الإنشاء', 'duration-overlapping' => 'لدى المشاركين اجتماع آخر في هذا الوقت. هل تريد المتابعة؟', 'create-success' => 'تم إنشاء النشاط بنجاح.', 'update-success' => 'تم تحديث النشاط بنجاح.', 'overlapping-error' => 'لدى المشاركين اجتماع آخر في هذا الوقت.', 'destroy-success' => 'تم حذف النشاط بنجاح.', 'delete-failed' => 'لا يمكن حذف النشاط.', 'mass-update-success' => 'تم تحديث الأنشطة بنجاح.', 'mass-destroy-success' => 'تم حذف الأنشطة بنجاح.', 'mass-delete-failed' => 'لا يمكن حذف الأنشطة.', ], 'mail' => [ 'index' => [ 'compose' => 'إنشاء', 'draft' => 'مسودة', 'inbox' => 'الوارد', 'outbox' => 'الصادر', 'sent' => 'تم الإرسال', 'trash' => 'المهملات', 'compose-mail-btn' => 'إنشاء بريد', 'btn' => 'البريد', 'mail' => [ 'title' => 'إنشاء بريد', 'to' => 'إلى', 'enter-emails' => 'اضغط على Enter لإضافة البريد الإلكتروني', 'cc' => 'نسخة إلى', 'bcc' => 'نسخة مخفية إلى', 'subject' => 'الموضوع', 'send-btn' => 'إرسال', 'message' => 'الرسالة', 'draft' => 'مسودة', ], 'datagrid' => [ 'id' => 'المعرف', 'from' => 'من', 'to' => 'إلى', 'subject' => 'الموضوع', 'tags' => 'العلامات', 'content' => 'المحتوى', 'attachments' => 'المرفقات', 'date' => 'التاريخ', 'move-to-inbox' => 'نقل إلى البريد الوارد', 'move-to-trash' => 'تم النقل إلى سلة المهملات', 'edit' => 'تعديل', 'view' => 'عرض', 'delete' => 'حذف', ], ], 'create-success' => 'تم إرسال البريد الإلكتروني بنجاح.', 'update-success' => 'تم تحديث البريد الإلكتروني بنجاح.', 'mass-update-success' => 'تم تحديث البريد الإلكتروني بنجاح.', 'delete-success' => 'تم حذف البريد الإلكتروني بنجاح.', 'delete-failed' => 'لا يمكن حذف البريد الإلكتروني.', 'invalid-route' => 'مسار غير صالح للبريد.', 'unauthorized' => 'هذا الإجراء غير مصرح به.', 'view' => [ 'title' => 'الرسائل', 'subject' => ':الموضوع', 'link-mail' => 'ربط البريد', 'to' => 'إلى', 'cc' => 'نسخة إلى', 'bcc' => 'نسخة مخفية إلى', 'reply' => 'رد', 'reply-all' => 'رد على الجميع', 'forward' => 'إعادة توجيه', 'delete' => 'حذف', 'enter-mails' => 'أدخل معرف البريد الإلكتروني', 'rotten-days' => 'العميل المحتمل مهمل لمدة :days يومًا', 'search-an-existing-lead' => 'البحث عن عميل محتمل موجود', 'search-an-existing-contact' => 'البحث عن جهة اتصال موجودة', 'message' => 'الرسالة', 'add-attachments' => 'إضافة مرفقات', 'discard' => 'تجاهل', 'send' => 'إرسال', 'no-result-found' => 'لم يتم العثور على نتائج', 'add-new-contact' => 'إضافة جهة اتصال جديدة', 'description' => 'الوصف', 'search' => 'بحث...', 'add-new-lead' => 'إضافة عميل محتمل جديد', 'create-new-contact' => 'إنشاء جهة اتصال جديدة', 'save-contact' => 'حفظ جهة الاتصال', 'create-lead' => 'إنشاء عميل محتمل', 'linked-contact' => 'جهة الاتصال المرتبطة', 'link-to-contact' => 'ربط بجهة اتصال', 'link-to-lead' => 'ربط بالعميل المحتمل', 'linked-lead' => 'العميل المحتمل المرتبط', 'lead-details' => 'تفاصيل العميل المحتمل', 'contact-person' => 'الشخص المسؤول', 'product' => 'المنتج', 'tags' => [ 'create-success' => 'تم إنشاء العلامة بنجاح.', 'destroy-success' => 'تم حذف العلامة بنجاح.', ], ], ], 'common' => [ 'custom-attributes' => [ 'add-more' => 'أضف المزيد', 'address' => 'العنوان', 'city' => 'المدينة', 'contact' => 'أرقام الاتصال', 'country' => 'الدولة', 'email' => 'البريد الإلكتروني', 'home' => 'المنزل', 'postcode' => 'الرمز البريدي', 'save' => 'حفظ', 'select' => 'اختر', 'select-country' => 'اختر الدولة', 'select-state' => 'اختر الولاية', 'state' => 'الولاية', 'update-contact-title' => 'تحديث أرقام الاتصال', 'update-emails-title' => 'تحديث عناوين البريد الإلكتروني', 'work' => 'العمل', ], ], 'leads' => [ 'create-success' => 'تم إنشاء العميل المحتمل بنجاح.', 'update-success' => 'تم تحديث العميل المحتمل بنجاح.', 'update-failed' => 'Leads can not be deleted.', 'destroy-success' => 'تم حذف العميل المحتمل بنجاح.', 'destroy-failed' => 'لا يمكن حذف العميل المحتمل.', 'file' => [ 'data-not-found' => 'البيانات غير موجودة.', 'empty-content' => 'محتوى PDF فارغ أو لم يتم استخراجه.', 'failed-extract' => 'فشل في استخراج النص من الملف.', 'insufficient-info' => 'بسبب نقص البيانات، لا يمكننا معالجة طلبك في الوقت الحالي.', 'invalid-base64' => 'تنسيق base64 غير صالح.', 'invalid-format' => 'تنسيق JSON غير صالح.', 'invalid-response' => 'تنسيق استجابة الذكاء الاصطناعي غير صالح.', 'missing-api-key' => 'مفتاح API أو تكوين النموذج مفقود.', 'not-found' => 'الملف غير موجود.', 'recursive-call' => 'تم الكشف عن استدعاء متكرر.', 'text-generation-failed' => 'فشل استخراج النص. قد يكون الملف فارغًا أو غير قابل للقراءة.', ], 'index' => [ 'title' => 'العملاء المحتملون', 'create-btn' => 'إنشاء عميل محتمل', 'datagrid' => [ 'id' => 'ID', 'sales-person' => 'مندوب المبيعات', 'subject' => 'الموضوع', 'source' => 'المصدر', 'lead-value' => 'قيمة العميل المحتمل', 'lead-type' => 'نوع العميل المحتمل', 'tag-name' => 'اسم العلامة', 'contact-person' => 'شخص الاتصال', 'stage' => 'المرحلة', 'rotten-lead' => 'عميل محتمل متعفن', 'date-to' => 'تاريخ إلى', 'created-at' => 'تم الإنشاء في', 'no' => 'لا', 'yes' => 'نعم', 'delete' => 'حذف', 'mass-delete' => 'حذف جماعي', 'mass-update' => 'تحديث جماعي', ], 'kanban' => [ 'rotten-days' => 'العميل الفاسد منذ :days أيام', 'empty-list' => 'قائمة العملاء الخاصة بك فارغة', 'empty-list-description' => 'قم بإنشاء عميل لتنظيم أهدافك.', 'create-lead-btn' => 'إنشاء عميل', 'columns' => [ 'contact-person' => 'شخص الاتصال', 'id' => 'ID', 'lead-type' => 'نوع العميل المحتمل', 'lead-value' => 'قيمة العميل المحتمل', 'sales-person' => 'مندوب المبيعات', 'source' => 'المصدر', 'title' => 'العنوان', 'tags' => 'العلامات', 'expected-close-date' => 'تاريخ الإغلاق المتوقع', 'created-at' => 'تم الإنشاء في', ], 'toolbar' => [ 'search' => [ 'title' => 'البحث حسب العنوان', ], 'filters' => [ 'apply-filters' => 'تطبيق الفلاتر', 'clear-all' => 'مسح الكل', 'filter' => 'تصفية', 'filters' => 'الفلاتر', 'from' => 'من', 'select' => 'اختر', 'to' => 'إلى', ], ], ], 'view-switcher' => [ 'all-pipelines' => 'كل المسارات', 'create-new-pipeline' => 'إنشاء مسار جديد', ], 'upload' => [ 'create-lead' => 'إنشاء عميل محتمل باستخدام الذكاء الاصطناعي', 'file' => 'رفع ملف', 'file-info' => 'يتم قبول الملفات بتنسيق pdf, bmp, jpg, jpeg, png فقط.', 'file-required' => 'يرجى اختيار ملف صالح واحد على الأقل للمتابعة.', 'save-btn' => 'حفظ', 'upload-file' => 'رفع الملف', ], ], 'create' => [ 'title' => 'إنشاء عميل محتمل', 'save-btn' => 'حفظ', 'details' => 'التفاصيل', 'details-info' => 'ضع المعلومات الأساسية للعميل المحتمل', 'contact-person' => 'شخص الاتصال', 'contact-info' => 'معلومات عن شخص الاتصال', 'products' => 'المنتجات', 'products-info' => 'معلومات عن المنتجات', ], 'edit' => [ 'title' => 'تعديل العميل المحتمل', 'save-btn' => 'حفظ', 'details' => 'التفاصيل', 'details-info' => 'ضع المعلومات الأساسية للعميل المحتمل', 'contact-person' => 'شخص الاتصال', 'contact-info' => 'معلومات عن شخص الاتصال', 'products' => 'المنتجات', 'products-info' => 'معلومات عن المنتجات', ], 'common' => [ 'contact' => [ 'name' => 'الاسم', 'email' => 'البريد الإلكتروني', 'contact-number' => 'رقم الاتصال', 'organization' => 'المنظمة', ], 'products' => [ 'product-name' => 'اسم المنتج', 'quantity' => 'الكمية', 'price' => 'السعر', 'amount' => 'المبلغ', 'action' => 'الإجراء', 'add-more' => 'أضف المزيد', 'total' => 'الإجمالي', ], ], 'view' => [ 'title' => 'الفرصة: :title', 'rotten-days' => ':days أيام', 'tabs' => [ 'description' => 'الوصف', 'products' => 'المنتجات', 'quotes' => 'الاقتباسات', ], 'attributes' => [ 'title' => 'حول الفرصة', ], 'quotes' => [ 'subject' => 'الموضوع', 'expired-at' => 'تنتهي في', 'sub-total' => 'المجموع الفرعي', 'discount' => 'الخصم', 'tax' => 'الضريبة', 'adjustment' => 'التعديل', 'grand-total' => 'المجموع الكلي', 'delete' => 'حذف', 'edit' => 'تعديل', 'download' => 'تحميل', 'destroy-success' => 'تم حذف العرض بنجاح.', 'empty-title' => 'لا توجد عروض', 'empty-info' => 'لم يتم العثور على عروض لهذه الفرصة', 'add-btn' => 'إضافة عرض', ], 'products' => [ 'product-name' => 'اسم المنتج', 'quantity' => 'الكمية', 'price' => 'السعر', 'amount' => 'المبلغ', 'action' => 'الإجراء', 'add-more' => 'إضافة المزيد', 'total' => 'الإجمالي', 'empty-title' => 'لا توجد منتجات', 'empty-info' => 'لم يتم العثور على منتجات لهذه الفرصة', 'add-product' => 'إضافة منتج', ], 'persons' => [ 'title' => 'حول الأشخاص', 'job-title' => ':job_title في :organization', ], 'stages' => [ 'won' => 'ربحت', 'lost' => 'خسرت', 'need-more-info' => 'تحتاج إلى مزيد من التفاصيل', 'closed-at' => 'أغلقت في', 'won-value' => 'قيمة الربح', 'lost-reason' => 'سبب الخسارة', 'save-btn' => 'حفظ', ], 'tags' => [ 'create-success' => 'تم إنشاء العلامة بنجاح.', 'destroy-success' => 'تم حذف العلامة بنجاح.', ], ], ], 'configuration' => [ 'index' => [ 'back' => 'رجوع', 'delete' => 'حذف', 'save-btn' => 'حفظ التكوين', 'save-success' => 'تم حفظ التكوين بنجاح.', 'search' => 'بحث', 'select-country' => 'اختر الدولة', 'select-state' => 'اختر الولاية', 'title' => 'التكوين', 'general' => [ 'title' => 'عام', 'info' => 'تكوين عام', 'general' => [ 'title' => 'عام', 'info' => 'قم بتحديث إعداداتك العامة هنا.', 'locale-settings' => [ 'title' => 'إعدادات اللغة', 'title-info' => 'تعريف اللغة المستخدمة في واجهة المستخدم، مثل العربية (ar)، الإنجليزية (en)، الإسبانية (es)، الفارسية (fa) والتركية (tr).', ], 'admin-logo' => [ 'logo-image' => 'صورة الشعار', 'title' => 'شعار الإدارة', 'title-info' => 'تكوين صورة الشعار للوحة الإدارة الخاصة بك.', ], ], 'settings' => [ 'title' => 'Settings', 'info' => 'Update your settings here.', 'footer' => [ 'info' => 'We can configure the powered by section here.', 'powered-by' => 'Powered by text editor', 'title' => 'Powered by Section Configurations', ], 'menu' => [ 'activities' => 'Activities', 'configuration' => 'Configuration', 'contacts' => 'Contacts', 'dashboard' => 'Dashboard', 'draft' => 'Draft', 'inbox' => 'Inbox', 'info' => 'We can configure the menu items name here.', 'leads' => 'Leads', 'mail' => 'Mail', 'organizations' => 'Organizations', 'outbox' => 'Outbox', 'persons' => 'Persons', 'products' => 'Products', 'quotes' => 'Quotes', 'sent' => 'Sent', 'settings' => 'Settings', 'title' => 'Menu Item Configurations', 'trash' => 'Trash', ], 'menu-color' => [ 'brand-color' => 'Brand Color', 'info' => 'We can change the menu items colors here.', 'title' => 'Menu Item Color Configurations', ], ], ], 'email' => [ 'title' => 'إعدادات البريد الإلكتروني', 'info' => 'تكوين البريد الإلكتروني للتطبيق.', 'imap' => [ 'title' => 'إعدادات IMAP', 'info' => 'تكوين البريد الإلكتروني IMAP لتلقي الرسائل.', 'account' => [ 'title' => 'حساب IMAP', 'title-info' => 'قم بتكوين إعدادات حساب IMAP هنا.', 'host' => 'المضيف', 'port' => 'المنفذ', 'encryption' => 'نوع التشفير', 'validate-cert' => 'التحقق من شهادة SSL', 'username' => 'اسم مستخدم IMAP', 'password' => 'كلمة مرور IMAP', ], ], ], 'magic-ai' => [ 'title' => 'الذكاء الاصطناعي السحري', 'info' => 'تكوين الذكاء الاصطناعي السحري للتطبيق.', 'settings' => [ 'api-key' => 'مفتاح API', 'api-key-info' => 'تذكر استخدام مفتاح API من OpenRouter لكل نموذج. إنها خطوة بسيطة لتعزيز الأمان والأداء.', 'enable' => 'تمكين', 'info' => 'عزز تجربتك مع الذكاء الاصطناعي السحري باستخدام مفتاح API من OpenRouter. قم بدمجه الآن لتجربة ذكاء اصطناعي سلسة ومخصصة لك! قم بتخصيص الإعدادات بسهولة وتحكم في رحلتك مع الذكاء الاصطناعي.', 'other' => 'نموذج آخر', 'other-model' => 'بالنسبة للنماذج الأخرى، استخدم معرف النموذج من OpenRouter.', 'doc-generation' => 'توليد DOC', 'doc-generation-info' => 'قم بتمكين ميزة توليد DOC لاستخراج البيانات تلقائيًا من ملفات DOC وتحويلها إلى تنسيق نصي. عزز إنتاجيتك وكفاءتك من خلال تفعيل هذه الميزة لتبسيط سير عملك.', 'title' => 'الإعدادات العامة', 'models' => [ 'deepseek-r1' => 'Deepseek R1 Distill-llama-8b', 'gemini-2-0-flash-001' => 'Gemini 2.0 flash-001', 'gpt-4o' => 'GPT-4.0', 'gpt-4o-mini' => 'GPT-4.0 mini', 'grok-2-1212' => 'Grok 2.12', 'llama-3-2-3b-instruct' => 'Llama 3.2 3b Instruct', 'title' => 'النماذج', ], ], ], ], ], 'dashboard' => [ 'index' => [ 'title' => 'لوحة القيادة', 'start-date' => 'Start Date', 'end-date' => 'End Date', 'revenue' => [ 'lost-revenue' => 'الإيرادات المفقودة', 'won-revenue' => 'الإيرادات المكتسبة', ], 'over-all' => [ 'average-lead-value' => 'متوسط قيمة العملاء المحتملين', 'total-leads' => 'إجمالي العملاء المحتملين', 'average-leads-per-day' => 'متوسط العملاء المحتملين في اليوم', 'total-quotations' => 'إجمالي العروض', 'total-persons' => 'إجمالي الأشخاص', 'total-organizations' => 'إجمالي المؤسسات', ], 'total-leads' => [ 'title' => 'العملاء المحتملين', 'total' => 'إجمالي العملاء المحتملين', 'won' => 'العملاء المحتملين المكتسبين', 'lost' => 'العملاء المحتملين المفقودين', ], 'revenue-by-sources' => [ 'title' => 'الإيرادات حسب المصادر', 'empty-title' => 'لا توجد بيانات متاحة', 'empty-info' => 'لا توجد بيانات متاحة للفترة المختارة', ], 'revenue-by-types' => [ 'title' => 'الإيرادات حسب الأنواع', 'empty-title' => 'لا توجد بيانات متاحة', 'empty-info' => 'لا توجد بيانات متاحة للفترة المختارة', ], 'top-selling-products' => [ 'title' => 'أفضل المنتجات مبيعاً', 'empty-title' => 'لم يتم العثور على منتجات', 'empty-info' => 'لا توجد منتجات متاحة للفترة المختارة', ], 'top-persons' => [ 'title' => 'أفضل الأشخاص', 'empty-title' => 'لم يتم العثور على أشخاص', 'empty-info' => 'لا توجد أشخاص متاحة للفترة المختارة', ], 'open-leads-by-states' => [ 'title' => 'العملاء المحتملين المفتوحين حسب المراحل', 'empty-title' => 'لا توجد بيانات متاحة', 'empty-info' => 'لا توجد بيانات متاحة للفترة المختارة', ], ], ], 'layouts' => [ 'app-version' => 'الإصدار: :version', 'dashboard' => 'لوحة القيادة', 'leads' => 'العملاء المحتملين', 'quotes' => 'العروض', 'quote' => 'عرض', 'mail' => [ 'title' => 'البريد', 'compose' => 'إنشاء رسالة', 'inbox' => 'البريد الوارد', 'draft' => 'المسودات', 'outbox' => 'الصادر', 'sent' => 'المرسل', 'trash' => 'سلة المهملات', 'setting' => 'الإعدادات', ], 'activities' => 'الأنشطة', 'contacts' => 'جهات الاتصال', 'persons' => 'الأشخاص', 'person' => 'شخص', 'organizations' => 'المؤسسات', 'organization' => 'مؤسسة', 'products' => 'المنتجات', 'product' => 'منتج', 'settings' => 'الإعدادات', 'user' => 'المستخدم', 'user-info' => 'إدارة جميع مستخدميك وصلاحياتهم في نظام CRM، وما يُسمح لهم بفعله.', 'groups' => 'المجموعات', 'groups-info' => 'إضافة أو تعديل أو حذف المجموعات من نظام CRM', 'roles' => 'الأدوار', 'role' => 'دور', 'roles-info' => 'إضافة أو تعديل أو حذف الأدوار من نظام CRM', 'users' => 'المستخدمين', 'users-info' => 'إضافة أو تعديل أو حذف المستخدمين من نظام CRM', 'lead' => 'عميل محتمل', 'lead-info' => 'إدارة جميع إعدادات العملاء المحتملين الخاصة بك في نظام CRM', 'pipelines' => 'الأنابيب', 'pipelines-info' => 'إضافة أو تعديل أو حذف الأنابيب من نظام CRM', 'sources' => 'المصادر', 'sources-info' => 'إضافة أو تعديل أو حذف المصادر من نظام CRM', 'types' => 'الأنواع', 'types-info' => 'إضافة أو تعديل أو حذف الأنواع من نظام CRM', 'automation' => 'الأتمتة', 'automation-info' => 'إدارة جميع إعدادات الأتمتة الخاصة بك في نظام CRM', 'attributes' => 'الخصائص', 'attribute' => 'خاصية', 'attributes-info' => 'إضافة أو تعديل أو حذف الخصائص من نظام CRM', 'email-templates' => 'قوالب البريد الإلكتروني', 'email' => 'البريد الإلكتروني', 'email-templates-info' => 'إضافة أو تعديل أو حذف قوالب البريد الإلكتروني من نظام CRM', 'events' => 'الفعاليات', 'events-info' => 'إضافة، تعديل أو حذف الفعاليات من إدارة العلاقات', 'campaigns' => 'الحملات', 'campaigns-info' => 'إضافة، تعديل أو حذف الحملات من إدارة العلاقات', 'workflows' => 'سير العمل', 'workflows-info' => 'إضافة أو تعديل أو حذف سير العمل من نظام CRM', 'webhooks' => 'الويب هوك', 'webhooks-info' => 'إضافة، تحرير أو حذف الويب هوك من CRM', 'other-settings' => 'إعدادات أخرى', 'other-settings-info' => 'إدارة جميع إعداداتك الإضافية في نظام CRM', 'tags' => 'العلامات', 'tags-info' => 'إضافة أو تعديل أو حذف العلامات من نظام CRM', 'my-account' => 'حسابي', 'sign-out' => 'تسجيل الخروج', 'back' => 'رجوع', 'name' => 'الاسم', 'configuration' => 'الإعدادات', 'howdy' => 'مرحبا!', 'warehouses' => 'المستودعات', 'warehouse' => 'مستودع', 'warehouses-info' => 'إضافة أو تعديل أو حذف المستودعات من نظام CRM', 'inventory' => 'المخزون', 'inventory-info' => 'إدارة جميع إعدادات المخزون المتعلقة في نظام إدارة علاقات العملاء (CRM)', 'data_transfer' => 'نقل البيانات', 'data_transfer_info' => 'إدارة إعدادات نقل البيانات المتعلقة بالأشخاص والمنتجات والعملاء المحتملين في إدارة علاقات العملاء (CRM)', ], 'user' => [ 'account' => [ 'name' => 'الاسم', 'email' => 'البريد الإلكتروني', 'password' => 'كلمة المرور', 'my_account' => 'حسابي', 'update_details' => 'تحديث التفاصيل', 'current_password' => 'كلمة المرور الحالية', 'confirm_password' => 'تأكيد كلمة المرور', 'password-match' => 'كلمة المرور الحالية لا تطابق.', 'account-save' => 'تم حفظ تغييرات الحساب بنجاح.', 'permission-denied' => 'تم رفض الإذن', 'remove-image' => 'إزالة الصورة', 'upload_image_pix' => 'تحميل صورة الملف الشخصي (100 بكسل × 100 بكسل)', 'upload_image_format' => 'بصيغة PNG أو JPG', 'image_upload_message' => 'فقط الصور (.jpeg، .jpg، .png، ..) مسموح بها.', ], ], 'emails' => [ 'common' => [ 'dear' => 'عزيزي :name', 'cheers' => 'مع أطيب التحيات،
فريق :app_name', 'user' => [ 'dear' => 'عزيزي :username', 'create-subject' => 'تم إضافتك كعضو.', 'create-body' => 'تهانينا! أنت الآن عضو في فريقنا.', 'forget-password' => [ 'subject' => 'إعادة تعيين كلمة مرور العميل', 'dear' => 'عزيزي :username', 'reset-password' => 'إعادة تعيين كلمة المرور', 'info' => 'تتلقى هذا البريد الإلكتروني لأننا تلقينا طلب إعادة تعيين كلمة المرور لحسابك', 'final-summary' => 'إذا لم تكن قد طلبت إعادة تعيين كلمة المرور، فلا يلزم اتخاذ أي إجراء إضافي', 'thanks' => 'شكراً!', ], ], ], ], 'validations' => [ 'message' => [ 'decimal' => ':attribute يجب أن يكون رقمًا عشريًا.', ], ], 'errors' => [ 'dashboard' => 'لوحة التحكم', 'go-back' => 'العودة', 'support' => 'إذا استمرت المشكلة، تواصل معنا على
:email للحصول على المساعدة.', '404' => [ 'description' => 'عذرًا! الصفحة التي تبحث عنها في إجازة. يبدو أننا لم نجد ما كنت تبحث عنه.', 'title' => '404 الصفحة غير موجودة', ], '401' => [ 'description' => 'عذرًا! يبدو أنك غير مصرح لك بالوصول إلى هذه الصفحة. يبدو أنك تفتقد الأوراق اللازمة.', 'title' => '401 غير مصرح', ], '403' => [ 'description' => 'عذرًا! هذه الصفحة محظورة. يبدو أنك لا تملك الأذونات اللازمة لعرض هذا المحتوى.', 'title' => '403 ممنوع الدخول', ], '500' => [ 'description' => 'عذرًا! حدث خطأ ما. يبدو أننا نواجه مشكلة في تحميل الصفحة التي تبحث عنها.', 'title' => '500 خطأ داخلي في الخادم', ], '503' => [ 'description' => 'عذرًا! يبدو أننا متوقفون مؤقتًا للصيانة. يرجى العودة لاحقًا.', 'title' => '503 الخدمة غير متوفرة', ], ], 'export' => [ 'csv' => 'CSV', 'download' => 'تحميل', 'export' => 'تصدير', 'no-records' => 'لا توجد سجلات للتصدير', 'xls' => 'XLS', 'xlsx' => 'XLSX', ], ]; ================================================ FILE: packages/Webkul/Admin/src/Resources/lang/en/app.php ================================================ [ 'leads' => 'Leads', 'lead' => 'Lead', 'quotes' => 'Quotes', 'mail' => 'Mail', 'inbox' => 'Inbox', 'draft' => 'Draft', 'outbox' => 'Outbox', 'sent' => 'Sent', 'trash' => 'Trash', 'activities' => 'Activities', 'webhook' => 'Webhook', 'contacts' => 'Contacts', 'persons' => 'Persons', 'organizations' => 'Organizations', 'products' => 'Products', 'settings' => 'Settings', 'groups' => 'Groups', 'roles' => 'Roles', 'users' => 'Users', 'user' => 'User', 'automation' => 'Automation', 'attributes' => 'Attributes', 'pipelines' => 'Pipelines', 'sources' => 'Sources', 'types' => 'Types', 'email-templates' => 'Email Templates', 'workflows' => 'Workflows', 'other-settings' => 'Other Settings', 'tags' => 'Tags', 'configuration' => 'Configuration', 'create' => 'Create', 'edit' => 'Edit', 'view' => 'View', 'print' => 'Print', 'delete' => 'Delete', 'export' => 'Export', 'mass-delete' => 'Mass Delete', 'data-transfer' => 'Data Transfer', 'imports' => 'Imports', 'import' => 'Import', 'event' => 'Event', 'campaigns' => 'Campaigns', 'warehouses' => 'Warehouses', 'inventory' => 'Inventory', ], 'users' => [ 'activate-warning' => 'Your account is not activated yet. Please contact the administrator.', 'login-error' => 'The credentials do not match our records.', 'not-permission' => 'You do not have permission to access the admin panel.', 'login' => [ 'email' => 'Email Address', 'forget-password-link' => 'Forget Password ?', 'password' => 'Password', 'submit-btn' => 'Sign In', 'title' => 'Sign In', ], 'forget-password' => [ 'create' => [ 'email' => 'Registered Email', 'email-not-exist' => 'Email Not Exists', 'page-title' => 'Forget Password', 'reset-link-sent' => 'Reset Password link sent', 'sign-in-link' => 'Back to Sign In ?', 'submit-btn' => 'Reset', 'title' => 'Recover Password', ], ], 'reset-password' => [ 'back-link-title' => 'Back to Sign In ?', 'confirm-password' => 'Confirm Password', 'email' => 'Registered Email', 'password' => 'Password', 'submit-btn' => 'Reset Password', 'title' => 'Reset Password', ], ], 'account' => [ 'edit' => [ 'back-btn' => 'Back', 'change-password' => 'Change Password', 'confirm-password' => 'Confirm Password', 'current-password' => 'Current Password', 'email' => 'Email', 'general' => 'General', 'invalid-password' => 'The current password you entered is incorrect.', 'name' => 'Name', 'password' => 'Password', 'profile-image' => 'Profile Image', 'save-btn' => 'Save Account', 'title' => 'My Account', 'update-success' => 'Account updated successfully', 'upload-image-info' => 'Upload a Profile Image (110px X 110px) in PNG or JPG Format', ], ], 'components' => [ 'activities' => [ 'actions' => [ 'mail' => [ 'btn' => 'Mail', 'title' => 'Compose Mail', 'to' => 'To', 'enter-emails' => 'Press enter to add emails', 'cc' => 'CC', 'bcc' => 'BCC', 'subject' => 'Subject', 'send-btn' => 'Send', 'message' => 'Message', ], 'file' => [ 'btn' => 'File', 'title' => 'Add File', 'title-control' => 'Title', 'name' => 'Name', 'description' => 'Description', 'file' => 'File', 'save-btn' => 'Save File', ], 'note' => [ 'btn' => 'Note', 'title' => 'Add Note', 'comment' => 'Comment', 'save-btn' => 'Save Note', ], 'activity' => [ 'btn' => 'Activity', 'title' => 'Add Activity', 'title-control' => 'Title', 'description' => 'Description', 'schedule-from' => 'Schedule From', 'schedule-to' => 'Schedule To', 'location' => 'Location', 'call' => 'Call', 'meeting' => 'Meeting', 'lunch' => 'Lunch', 'save-btn' => 'Save Activity', 'participants' => [ 'title' => 'Participants', 'placeholder' => 'Type to search participants', 'users' => 'Users', 'persons' => 'Persons', 'no-results' => 'No result found...', ], ], ], 'index' => [ 'all' => 'All', 'bcc' => 'Bcc', 'by-user' => 'By :user', 'calls' => 'Calls', 'cc' => 'Cc', 'change-log' => 'Changelogs', 'delete' => 'Delete', 'edit' => 'Edit', 'emails' => 'Emails', 'empty' => 'Empty', 'files' => 'Files', 'from' => 'From', 'location' => 'Location', 'lunches' => 'Lunches', 'mark-as-done' => 'Mark as Done', 'meetings' => 'Meetings', 'notes' => 'Notes', 'participants' => 'Participants', 'planned' => 'Planned', 'quotes' => 'Quotes', 'scheduled-on' => 'Scheduled on', 'system' => 'System', 'to' => 'To', 'unlink' => 'Unlink', 'view' => 'View', 'empty-placeholders' => [ 'all' => [ 'title' => 'No Activities Found', 'description' => 'No activities found for this. You can add activities by clicking on the Activity button on the left panel.', ], 'planned' => [ 'title' => 'No Planned Activities Found', 'description' => 'No planned activities found for this. You can add planned activities by clicking on the Activity button on the left panel.', ], 'notes' => [ 'title' => 'No Notes Found', 'description' => 'No notes found for this. You can add notes by clicking on the Note button on the left panel.', ], 'calls' => [ 'title' => 'No Calls Found', 'description' => 'No calls found for this. You can add calls by clicking on the Activity button on the left panel and selecting the Call type.', ], 'meetings' => [ 'title' => 'No Meetings Found', 'description' => 'No meetings found for this. You can add meetings by clicking on the Activity button on the left panel and selecting the Meeting type.', ], 'lunches' => [ 'title' => 'No Lunches Found', 'description' => 'No lunches found for this. You can add lunches by clicking on the Activity button on the left panel and selecting the Lunch type.', ], 'files' => [ 'title' => 'No Files Found', 'description' => 'No files found for this. You can add files by clicking on the File button on the left panel.', ], 'emails' => [ 'title' => 'No Emails Found', 'description' => 'No emails found for this. You can add emails by clicking on the Mail button on the left panel.', ], 'system' => [ 'title' => 'No Changelogs Found', 'description' => 'No changelogs found for this.', ], ], ], ], 'media' => [ 'images' => [ 'add-image-btn' => 'Add Image', 'ai-add-image-btn' => 'Magic AI', 'allowed-types' => 'png, jpeg, jpg', 'not-allowed-error' => 'Only images files (.jpeg, .jpg, .png, ..) are allowed.', 'placeholders' => [ 'front' => 'Front', 'next' => 'Next', 'size' => 'Size', 'use-cases' => 'Use Cases', 'zoom' => 'Zoom', ], ], 'videos' => [ 'add-video-btn' => 'Add Video', 'allowed-types' => 'mp4, webm, mkv', 'not-allowed-error' => 'Only videos files (.mp4, .mov, .ogg ..) are allowed.', ], ], 'datagrid' => [ 'index' => [ 'no-records-selected' => 'No records have been selected.', 'must-select-a-mass-action-option' => 'You must select a mass action\'s option.', 'must-select-a-mass-action' => 'You must select a mass action.', ], 'toolbar' => [ 'length-of' => ':length of', 'of' => 'of', 'per-page' => 'Per Page', 'results' => ':total Results', 'delete' => 'Delete', 'selected' => ':total Items Selected', 'mass-actions' => [ 'submit' => 'Submit', 'select-option' => 'Select Option', 'select-action' => 'Select Action', ], 'filter' => [ 'apply-filters-btn' => 'Apply Filters', 'back-btn' => 'Back', 'create-new-filter' => 'Create New Filter', 'custom-filters' => 'Custom Filters', 'delete-error' => 'Something went wrong while deleting the filter, please try again.', 'delete-success' => 'Filter has been deleted successfully.', 'empty-description' => 'There is no selected filters available to save. Please select filters to save.', 'empty-title' => 'Add Filters to Save', 'name' => 'Name', 'quick-filters' => 'Quick Filters', 'save-btn' => 'Save', 'save-filter' => 'Save Filter', 'saved-success' => 'Filter has been saved successfully.', 'selected-filters' => 'Selected Filters', 'title' => 'Filter', 'update' => 'Update', 'update-filter' => 'Update Filter', 'updated-success' => 'Filter has been updated successfully.', ], 'search' => [ 'title' => 'Search', ], ], 'filters' => [ 'select' => 'Select', 'title' => 'Filters', 'dropdown' => [ 'searchable' => [ 'at-least-two-chars' => 'Type at least 2 characters...', 'no-results' => 'No result found...', ], ], 'custom-filters' => [ 'clear-all' => 'Clear All', 'title' => 'Custom Filters', ], 'boolean-options' => [ 'false' => 'False', 'true' => 'True', ], 'date-options' => [ 'last-month' => 'Last Month', 'last-six-months' => 'Last 6 Months', 'last-three-months' => 'Last 3 Months', 'this-month' => 'This Month', 'this-week' => 'This Week', 'this-year' => 'This Year', 'today' => 'Today', 'yesterday' => 'Yesterday', ], ], 'table' => [ 'actions' => 'Actions', 'no-records-available' => 'No Records Available.', ], ], 'modal' => [ 'confirm' => [ 'agree-btn' => 'Agree', 'disagree-btn' => 'Disagree', 'message' => 'Are you sure you want to perform this action?', 'title' => 'Are you sure?', ], ], 'tags' => [ 'index' => [ 'title' => 'Tags', 'added-tags' => 'Added Tags', 'save-btn' => 'Save Tag', 'placeholder' => 'Type to search tags', 'add-tag' => 'Add \":term\"...', 'aquarelle-red' => 'Aquarelle Red', 'crushed-cashew' => 'Crushed Cashew', 'beeswax' => 'Beeswax', 'lemon-chiffon' => 'Lemon Chiffon', 'snow-flurry' => 'Snow Flurry', 'honeydew' => 'Honeydew', ], ], 'layouts' => [ 'powered-by' => [ 'description' => 'Powered by :krayin, an open-source project by :webkul.', ], 'header' => [ 'mega-search' => [ 'title' => 'Mega Search', 'tabs' => [ 'leads' => 'Leads', 'quotes' => 'Quotes', 'persons' => 'Persons', 'products' => 'Products', ], 'explore-all-products' => 'Explore all Products', 'explore-all-leads' => 'Explore all Leads', 'explore-all-contacts' => 'Explore all Contacts', 'explore-all-quotes' => 'Explore all Quotes', 'explore-all-matching-products' => 'Explore all products matching ":query" (:count)', 'explore-all-matching-leads' => 'Explore all leads matching ":query" (:count)', 'explore-all-matching-contacts' => 'Explore all contacts matching ":query" (:count)', 'explore-all-matching-quotes' => 'Explore all quotes matching ":query" (:count)', ], ], ], 'attributes' => [ 'edit' => [ 'delete' => 'Delete', ], 'lookup' => [ 'click-to-add' => 'Click to add', 'search' => 'Search', 'no-result-found' => 'No result found', 'search' => 'Search...', ], ], 'lookup' => [ 'click-to-add' => 'Click to Add', 'no-results' => 'No Results Found', 'add-as-new' => 'Add as New', 'search' => 'Search...', ], 'flash-group' => [ 'success' => 'Success', 'error' => 'Error', 'warning' => 'Warning', 'info' => 'Info', ], 'tiny-mce' => [ 'http-error' => 'HTTP Error', 'invalid-json' => 'Invalid JSON response from the server.', 'upload-failed' => 'File upload failed. Please try again.', ], ], 'quotes' => [ 'index' => [ 'title' => 'Quotes', 'create-btn' => 'Create Quote', 'create-success' => 'Quote created successfully.', 'update-success' => 'Quote updated successfully.', 'delete-success' => 'Quote deleted successfully.', 'delete-failed' => 'Quote can not be deleted.', 'datagrid' => [ 'subject' => 'Subject', 'sales-person' => 'Sales Person', 'expired-at' => 'Expired At', 'created-at' => 'Created At', 'person' => 'Person', 'subtotal' => 'Subtotal', 'discount' => 'Discount', 'tax' => 'Tax', 'adjustment' => 'Adjustment', 'grand-total' => 'Grand Total', 'edit' => 'Edit', 'delete' => 'Delete', 'print' => 'Print', ], 'pdf' => [ 'adjustment' => 'Adjustment', 'amount' => 'Amount', 'billing-address' => 'Billing Address', 'date' => 'Date', 'discount' => 'Discount', 'expired-at' => 'Expired At', 'grand-total' => 'Grand Total', 'person' => 'Person', 'price' => 'Price', 'product-name' => 'Product Name', 'quantity' => 'Quantity', 'quote-id' => 'Quote ID', 'sales-person' => 'Sales Person', 'shipping-address' => 'Shipping Address', 'sku' => 'SKU', 'sub-total' => 'Sub Total', 'subject' => 'Subject', 'tax' => 'Tax', 'title' => 'Quote', ], ], 'create' => [ 'title' => 'Create Quote', 'save-btn' => 'Save Quote', 'quote-info' => 'Quote Information', 'quote-info-info' => 'Put the basic information of the quote.', 'address-info' => 'Address Information', 'address-info-info' => 'Information about the address related to quote.', 'quote-items' => 'Quote Items', 'search-products' => 'Search Products', 'link-to-lead' => 'Link to lead', 'quote-item-info' => 'Add Product Request for this quote.', 'quote-name' => 'Quote name', 'quantity' => 'Quantity', 'price' => 'Price', 'discount' => 'Discount', 'tax' => 'Tax', 'total' => 'Total', 'amount' => 'Amount', 'add-item' => '+ Add Item', 'sub-total' => 'Sub Total (:symbol)', 'total-discount' => 'Discount (:symbol)', 'total-tax' => 'Tax (:symbol)', 'total-adjustment' => 'Adjustment (:symbol)', 'grand-total' => 'Grand Total (:symbol)', 'discount-amount' => 'Discount amount', 'tax-amount' => 'Tax amount', 'adjustment-amount' => 'Adjustment amount', 'product-name' => 'Product Name', 'action' => 'Action', ], 'edit' => [ 'title' => 'Edit Quote', 'save-btn' => 'Save Quote', 'quote-info' => 'Quote Information', 'quote-info-info' => 'Put the basic information of the quote.', 'address-info' => 'Address Information', 'address-info-info' => 'Information about the address related to quote.', 'quote-items' => 'Quote Items', 'link-to-lead' => 'Link to lead', 'quote-item-info' => 'Add Product Request for this quote.', 'quote-name' => 'Quote name', 'quantity' => 'Quantity', 'price' => 'Price', 'search-products' => 'Search Products', 'discount' => 'Discount', 'tax' => 'Tax', 'total' => 'Total', 'amount' => 'Amount', 'add-item' => '+ Add Item', 'sub-total' => 'Sub Total (:symbol)', 'total-discount' => 'Discount (:symbol)', 'total-tax' => 'Tax (:symbol)', 'total-adjustment' => 'Adjustment (:symbol)', 'grand-total' => 'Grand Total (:symbol)', 'discount-amount' => 'Discount amount', 'tax-amount' => 'Tax amount', 'adjustment-amount' => 'Adjustment amount', 'product-name' => 'Product Name', 'action' => 'Action', ], ], 'contacts' => [ 'persons' => [ 'index' => [ 'title' => 'Persons', 'create-btn' => 'Create Person', 'create-success' => 'Person created successfully.', 'update-success' => 'Person updated successfully.', 'all-delete-success' => 'All selected persons were deleted successfully.', 'partial-delete-warning' => 'Some persons were deleted successfully. Others could not be deleted because they are linked to leads.', 'none-delete-warning' => 'None of the selected persons could be deleted because they are linked to leads.', 'no-selection' => 'No persons were selected for deletion.', 'delete-failed' => 'Failed to delete selected persons.', 'datagrid' => [ 'contact-numbers' => 'Contact Numbers', 'delete' => 'Delete', 'edit' => 'Edit', 'emails' => 'Emails', 'id' => 'ID', 'view' => 'View', 'name' => 'Name', 'organization-name' => 'Organization Name', ], ], 'view' => [ 'title' => ':name', 'about-person' => 'About Person', 'about-organization' => 'About Organization', 'activities' => [ 'index' => [ 'all' => 'All', 'calls' => 'Calls', 'meetings' => 'Meetings', 'lunches' => 'Lunches', 'files' => 'Files', 'quotes' => 'Quotes', 'notes' => 'Notes', 'emails' => 'Emails', 'by-user' => 'By :user', 'scheduled-on' => 'Scheduled on', 'location' => 'Location', 'participants' => 'Participants', 'mark-as-done' => 'Mark as Done', 'delete' => 'Delete', 'edit' => 'Edit', ], 'actions' => [ 'mail' => [ 'btn' => 'Mail', 'title' => 'Compose Mail', 'to' => 'To', 'cc' => 'CC', 'bcc' => 'BCC', 'subject' => 'Subject', 'send-btn' => 'Send', 'message' => 'Message', ], 'file' => [ 'btn' => 'File', 'title' => 'Add File', 'title-control' => 'Title', 'name' => 'File Name', 'description' => 'Description', 'file' => 'File', 'save-btn' => 'Save File', ], 'note' => [ 'btn' => 'Note', 'title' => 'Add Note', 'comment' => 'Comment', 'save-btn' => 'Save Note', ], 'activity' => [ 'btn' => 'Activity', 'title' => 'Add Activity', 'title-control' => 'Title', 'description' => 'Description', 'schedule-from' => 'Schedule From', 'schedule-to' => 'Schedule To', 'location' => 'Location', 'call' => 'Call', 'meeting' => 'Meeting', 'lunch' => 'Lunch', 'save-btn' => 'Save Activity', ], ], ], 'tags' => [ 'create-success' => 'Tag created successfully.', 'destroy-success' => 'Tag deleted successfully.', ], ], 'create' => [ 'title' => 'Create Person', 'save-btn' => 'Save Person', ], 'edit' => [ 'title' => 'Edit Person', 'save-btn' => 'Save Person', ], ], 'organizations' => [ 'index' => [ 'title' => 'Organizations', 'create-btn' => 'Create Organization', 'create-success' => 'Organization created successfully.', 'update-success' => 'Organization updated successfully.', 'delete-success' => 'Organization deleted successfully.', 'delete-failed' => 'Organization can not be deleted.', 'datagrid' => [ 'delete' => 'Delete', 'edit' => 'Edit', 'id' => 'ID', 'name' => 'Name', 'persons-count' => 'Person Count', ], ], 'create' => [ 'title' => 'Create Organization', 'save-btn' => 'Save Organization', ], 'edit' => [ 'title' => 'Edit Organization', 'save-btn' => 'Save Organization', ], ], ], 'products' => [ 'index' => [ 'title' => 'Products', 'create-btn' => 'Create Product', 'create-success' => 'Product created successfully.', 'update-success' => 'Product updated successfully.', 'delete-success' => 'Product deleted successfully.', 'delete-failed' => 'Product can not be deleted.', 'datagrid' => [ 'allocated' => 'Allocated', 'delete' => 'Delete', 'edit' => 'Edit', 'id' => 'ID', 'in-stock' => 'In Stock', 'name' => 'Name', 'on-hand' => 'On Hand', 'tag-name' => 'Tag Name', 'price' => 'Price', 'sku' => 'SKU', 'view' => 'View', ], ], 'create' => [ 'save-btn' => 'Save Products', 'title' => 'Create Products', 'general' => 'General', 'price' => 'Price', ], 'edit' => [ 'title' => 'Edit Products', 'save-btn' => 'Save Products', 'general' => 'General', 'price' => 'Price', ], 'view' => [ 'sku' => 'SKU', 'all' => 'All', 'notes' => 'Notes', 'files' => 'Files', 'inventories' => 'Inventory', 'change-logs' => 'Changelogs', 'attributes' => [ 'about-product' => 'About Product', ], 'inventory' => [ 'source' => 'Source', 'in-stock' => 'In Stock', 'allocated' => 'Allocated', 'on-hand' => 'On Hand', 'actions' => 'Actions', 'assign' => 'Assign', 'add-source' => 'Add Source', 'location' => 'Location', 'add-more' => 'Add More', 'save' => 'Save', ], ], ], 'settings' => [ 'title' => 'Settings', 'groups' => [ 'index' => [ 'create-btn' => 'Create Group', 'title' => 'Groups', 'create-success' => 'Group created successfully.', 'update-success' => 'Group updated successfully.', 'destroy-success' => 'Group deleted successfully.', 'delete-failed' => 'Group can not be deleted.', 'delete-failed-associated-users' => 'Group can not be deleted, as this is being used by users.', 'datagrid' => [ 'delete' => 'Delete', 'description' => 'Description', 'edit' => 'Edit', 'id' => 'ID', 'name' => 'Name', ], 'edit' => [ 'title' => 'Edit Group', ], 'create' => [ 'name' => 'Name', 'title' => 'Create Group', 'description' => 'Description', 'save-btn' => 'Save Group', ], ], ], 'roles' => [ 'index' => [ 'being-used' => 'Role can not be deleted, as this is being used in admin user.', 'create-btn' => 'Create Roles', 'create-success' => 'Role created successfully.', 'current-role-delete-error' => 'Can not delete role assigned to the current user.', 'delete-failed' => 'Role can not be deleted.', 'delete-success' => 'Role deleted successfully.', 'last-delete-error' => 'At least one role is required.', 'settings' => 'Settings', 'title' => 'Roles', 'update-success' => 'Role updated successfully.', 'user-define-error' => 'Can not delete system role.', 'datagrid' => [ 'all' => 'All', 'custom' => 'Custom', 'delete' => 'Delete', 'description' => 'Description', 'edit' => 'Edit', 'id' => 'ID', 'name' => 'Name', 'permission-type' => 'Permission Type', ], ], 'create' => [ 'access-control' => 'Access Control', 'all' => 'All', 'back-btn' => 'Back', 'custom' => 'Custom', 'description' => 'Description', 'general' => 'General', 'name' => 'Name', 'permissions' => 'Permissions', 'save-btn' => 'Save Role', 'title' => 'Create Role', ], 'edit' => [ 'access-control' => 'Access Control', 'all' => 'All', 'back-btn' => 'Back', 'custom' => 'Custom', 'description' => 'Description', 'general' => 'General', 'name' => 'Name', 'permissions' => 'Permissions', 'save-btn' => 'Save Role', 'title' => 'Edit Role', ], ], 'types' => [ 'index' => [ 'create-btn' => 'Create Type', 'create-success' => 'Type created successfully.', 'delete-failed' => 'Type can not be deleted.', 'delete-success' => 'Type deleted successfully.', 'title' => 'Types', 'update-success' => 'Type updated successfully.', 'datagrid' => [ 'delete' => 'Delete', 'description' => 'Description', 'edit' => 'Edit', 'id' => 'ID', 'name' => 'Name', ], 'create' => [ 'name' => 'Name', 'save-btn' => 'Save Type', 'title' => 'Create Type', ], 'edit' => [ 'title' => 'Edit Type', ], ], ], 'sources' => [ 'index' => [ 'title' => 'Sources', 'create-btn' => 'Create Source', 'create-success' => 'Source created successfully.', 'delete-failed' => 'Source can not be deleted.', 'delete-success' => 'Source deleted successfully.', 'update-success' => 'Source updated successfully.', 'delete-failed-associated-leads' => 'Source cannot be deleted because it is associated with existing leads. Please detach or update those leads before deletion.', 'datagrid' => [ 'delete' => 'Delete', 'edit' => 'Edit', 'id' => 'ID', 'name' => 'Name', ], 'create' => [ 'name' => 'Name', 'save-btn' => 'Save Source', 'title' => 'Create Source', ], 'edit' => [ 'title' => 'Edit Source', ], ], ], 'workflows' => [ 'index' => [ 'title' => 'Workflows', 'create-btn' => 'Create Workflow', 'create-success' => 'Workflow created successfully.', 'update-success' => 'Workflow updated successfully.', 'delete-success' => 'Workflow deleted successfully.', 'delete-failed' => 'Workflow can not be deleted.', 'datagrid' => [ 'delete' => 'Delete', 'description' => 'Description', 'edit' => 'Edit', 'id' => 'ID', 'name' => 'Name', ], ], 'helpers' => [ 'update-related-leads' => 'Update related leads', 'send-email-to-sales-owner' => 'Send email to sales owner', 'send-email-to-participants' => 'Send email to participants', 'add-webhook' => 'Add Webhook', 'update-lead' => 'Update Lead', 'update-person' => 'Update Person', 'send-email-to-person' => 'Send email to person', 'add-tag' => 'Add Tag', 'add-note-as-activity' => 'Add Note as Activity', 'update-quote' => 'Update Quote', ], 'create' => [ 'title' => 'Create Workflow', 'event' => 'Event', 'back-btn' => 'Back', 'save-btn' => 'Save Workflow', 'name' => 'Name', 'basic-details' => 'Basic Details', 'description' => 'Description', 'actions' => 'Actions', 'basic-details-info' => 'Put the basic information of the workflow.', 'event-info' => 'An event triggers, checks, conditions, and performs predefined actions.', 'conditions' => 'Conditions', 'conditions-info' => 'Conditions are rules checking scenarios, triggered on specific occasions.', 'actions-info' => 'An action not only reduces the workload but also make it quite easier for CRM automation', 'value' => 'Value', 'condition-type' => 'Condition Type', 'all-condition-are-true' => 'All condition are true', 'any-condition-are-true' => 'Any condition are true', 'add-condition' => 'Add Condition', 'add-action' => 'Add Action', 'yes' => 'Yes', 'no' => 'No', 'email' => 'Email', 'is-equal-to' => 'Is equal to', 'is-not-equal-to' => 'Is not equal to', 'equals-or-greater-than' => 'Equals or greater than', 'equals-or-less-than' => 'Equals or less than', 'greater-than' => 'Greater than', 'less-than' => 'Less than', 'type' => 'Type', 'contain' => 'Contain', 'contains' => 'Contains', 'does-not-contain' => 'Does not contain', ], 'edit' => [ 'title' => 'Edit Workflow', 'event' => 'Event', 'back-btn' => 'Back', 'save-btn' => 'Save Workflow', 'name' => 'Name', 'basic-details' => 'Basic Details', 'description' => 'Description', 'actions' => 'Actions', 'type' => 'Type', 'basic-details-info' => 'Put the basic information of the workflow.', 'event-info' => 'An event triggers, checks, conditions, and performs predefined actions.', 'conditions' => 'Conditions', 'conditions-info' => 'Conditions are rules checking scenarios, triggered on specific occasions.', 'actions-info' => 'An action not only reduces the workload but also make it quite easier for CRM automation', 'value' => 'Value', 'condition-type' => 'Condition Type', 'all-condition-are-true' => 'All condition are true', 'any-condition-are-true' => 'Any condition are true', 'add-condition' => 'Add Condition', 'add-action' => 'Add Action', 'yes' => 'Yes', 'no' => 'No', 'email' => 'Email', 'is-equal-to' => 'Is equal to', 'is-not-equal-to' => 'Is not equal to', 'equals-or-greater-than' => 'Equals or greater than', 'equals-or-less-than' => 'Equals or less than', 'greater-than' => 'Greater than', 'less-than' => 'Less than', 'contain' => 'Contain', 'contains' => 'Contains', 'does-not-contain' => 'Does not contain', ], ], 'webforms' => [ 'index' => [ 'title' => 'Webforms', 'create-btn' => 'Create Webform', 'create-success' => 'Webform created successfully.', 'update-success' => 'Webform updated successfully.', 'delete-success' => 'Webform deleted successfully.', 'delete-failed' => 'Webform can not be deleted.', 'datagrid' => [ 'id' => 'ID', 'title' => 'Title', 'edit' => 'Edit', 'delete' => 'Delete', ], ], 'create' => [ 'title' => 'Create Webform', 'add-attribute-btn' => 'Add Attribute Button', 'attribute-label-color' => 'Attribute Label Color', 'attributes' => 'Attributes', 'attributes-info' => 'Add custom attributes to the form.', 'background-color' => 'Background Color', 'create-lead' => 'Create Lead', 'customize-webform' => 'Customize Webform', 'customize-webform-info' => 'Customize your web form with element colors of your choosing.', 'description' => 'Description', 'display-custom-message' => 'Display custom message', 'form-background-color' => 'Form Background Color', 'form-submit-btn-color' => 'Form Submit Button Color', 'form-submit-button-color' => 'Form Submit Button Color', 'form-title-color' => 'Form Title Color', 'general' => 'General', 'leads' => 'Leads', 'person' => 'Person', 'save-btn' => 'Save Webform', 'submit-button-label' => 'Submit Button Label', 'submit-success-action' => 'Submit Success Action', 'redirect-to-url' => 'Redirect To Url', 'choose-value' => 'Choose Value', 'select-file' => 'Select File', 'select-image' => 'Select Image', 'enter-value' => 'Enter Value', ], 'edit' => [ 'add-attribute-btn' => 'Add Attribute Button', 'attribute-label-color' => 'Attribute Label Color', 'attributes' => 'Attributes', 'attributes-info' => 'Add custom attributes to the form.', 'background-color' => 'Background Color', 'choose-value' => 'Choose Value', 'code-snippet' => 'Code Snippet', 'copied' => 'Copied', 'copy' => 'Copy', 'create-lead' => 'Create Lead', 'customize-webform' => 'Customize Webform', 'customize-webform-info' => 'Customize your web form with element colors of your choosing.', 'description' => 'Description', 'display-custom-message' => 'Display custom message', 'embed' => 'Embed', 'enter-value' => 'Enter Value', 'form-background-color' => 'Form Background Color', 'form-submit-btn-color' => 'Form Submit Button Color', 'form-submit-button-color' => 'Form Submit Button Color', 'form-title-color' => 'Form Title Color', 'general' => 'General', 'leads' => 'Leads', 'person' => 'Person', 'preview' => 'Preview', 'public-url' => 'Public URL', 'redirect-to-url' => 'Redirect To URL', 'save-btn' => 'Save Webform', 'select-file' => 'Select File', 'select-image' => 'Select Image', 'submit-button-label' => 'Submit Button Label', 'submit-success-action' => 'Submit Success Action', 'title' => 'Edit Webform', ], ], 'email-template' => [ 'index' => [ 'create-btn' => 'Create Email Template', 'title' => 'Email Templates', 'create-success' => 'Email Template created successfully.', 'update-success' => 'Email Template updated successfully.', 'delete-success' => 'Email Template deleted successfully.', 'delete-failed' => 'Email Template can not be deleted.', 'datagrid' => [ 'delete' => 'Delete', 'edit' => 'Edit', 'id' => 'ID', 'name' => 'Name', 'subject' => 'Subject', ], ], 'create' => [ 'title' => 'Create Email Template', 'save-btn' => 'Save Email Template', 'email-template' => 'Email Template', 'subject' => 'Subject', 'content' => 'Content', 'subject-placeholders' => 'Subject Placeholders', 'general' => 'General', 'name' => 'Name', ], 'edit' => [ 'title' => 'Edit Email Template', 'save-btn' => 'Save Email Template', 'email-template' => 'Email Template', 'subject' => 'Subject', 'content' => 'Content', 'subject-placeholders' => 'Subject Placeholders', 'general' => 'General', 'name' => 'Name', ], ], 'marketing' => [ 'events' => [ 'index' => [ 'create-btn' => 'Create Event', 'title' => 'Events', 'create-success' => 'Event created successfully.', 'update-success' => 'Event updated successfully.', 'delete-success' => 'Event deleted successfully.', 'delete-failed' => 'Event can not be deleted.', 'mass-delete-success' => 'Events deleted successfully', 'delete-failed-associated-campaigns' => 'Event can not be deleted as it is associated with existing campaigns. Please detach or update those campaigns before deletion.', 'datagrid' => [ 'delete' => 'Delete', 'edit' => 'Edit', 'id' => 'ID', 'name' => 'Name', 'description' => 'Description', 'date' => 'Date', ], 'create' => [ 'title' => 'Create Event', 'name' => 'Name', 'date' => 'Date', 'description' => 'Description', 'save-btn' => 'Save Event', ], 'edit' => [ 'title' => 'Edit Event', ], ], ], 'campaigns' => [ 'index' => [ 'create-btn' => 'Create Campaigns', 'title' => 'Campaigns', 'create-success' => 'Campaign created successfully.', 'update-success' => 'Campaign updated successfully.', 'delete-success' => 'Campaign deleted successfully.', 'delete-failed' => 'Campaign can not be deleted.', 'mass-delete-success' => 'Campaigns deleted successfully.', 'datagrid' => [ 'id' => 'ID', 'name' => 'Name', 'subject' => 'Subject', 'status' => 'Status', 'active' => 'Active', 'inactive' => 'Inactive', 'edit' => 'Edit', 'delete' => 'Delete', ], 'create' => [ 'title' => 'Create Campaign', 'name' => 'Name', 'type' => 'Type', 'subject' => 'Subject', 'event' => 'Event', 'email-template' => 'Email Template', 'status' => 'Status', ], 'edit' => [ 'title' => 'Edit Campaign', ], ], ], ], 'tags' => [ 'index' => [ 'create-btn' => 'Create Tag', 'title' => 'Tags', 'create-success' => 'Tag created successfully.', 'update-success' => 'Tag updated successfully.', 'delete-success' => 'Tag deleted successfully.', 'delete-failed' => 'Tag can not be deleted.', 'datagrid' => [ 'delete' => 'Delete', 'edit' => 'Edit', 'id' => 'ID', 'name' => 'Name', 'users' => 'Users', 'created-at' => 'Created At', ], 'create' => [ 'name' => 'Name', 'save-btn' => 'Save Tag', 'title' => 'Create Tag', 'color' => 'Color', ], 'edit' => [ 'title' => 'Edit Tag', ], ], ], 'users' => [ 'index' => [ 'create-btn' => 'Create User', 'create-success' => 'User created successfully.', 'delete-failed' => 'User can not be deleted.', 'delete-success' => 'User deleted successfully.', 'last-delete-error' => 'At least one user is required.', 'mass-delete-failed' => 'Users can not be deleted.', 'mass-delete-success' => 'Users deleted successfully.', 'mass-update-failed' => 'Users can not be updated.', 'mass-update-success' => 'Users updated successfully.', 'title' => 'Users', 'update-success' => 'User updated successfully.', 'user-define-error' => 'Can not delete system user.', 'active' => 'Active', 'inactive' => 'Inactive', 'datagrid' => [ 'active' => 'Active', 'created-at' => 'Created At', 'delete' => 'Delete', 'edit' => 'Edit', 'email' => 'Email', 'id' => 'ID', 'inactive' => 'Inactive', 'name' => 'Name', 'status' => 'Status', 'update-status' => 'Update Status', 'users' => 'Users', ], 'create' => [ 'confirm-password' => 'Confirm Password', 'email' => 'Email', 'general' => 'General', 'global' => 'Global', 'group' => 'Group', 'individual' => 'Individual', 'name' => 'Name', 'password' => 'Password', 'permission' => 'Permission', 'role' => 'Role', 'save-btn' => 'Save User', 'status' => 'Status', 'title' => 'Create User', 'view-permission' => 'View Permission', 'select-at-lest-one-group' => 'Select at least one group', ], 'edit' => [ 'title' => 'Edit User', ], ], ], 'pipelines' => [ 'index' => [ 'title' => 'Pipelines', 'create-btn' => 'Create Pipeline', 'create-success' => 'Pipeline created successfully.', 'update-success' => 'Pipeline updated successfully.', 'default-required' => 'At least one default pipeline is required.', 'delete-success' => 'Pipeline deleted successfully.', 'delete-failed' => 'Pipeline can not be deleted.', 'default-delete-error' => 'Default pipeline can not be deleted.', 'datagrid' => [ 'delete' => 'Delete', 'edit' => 'Edit', 'id' => 'ID', 'is-default' => 'Is Default', 'name' => 'Name', 'no' => 'No', 'rotten-days' => 'Rotten Days', 'yes' => 'Yes', ], ], 'create' => [ 'title' => 'Create Pipeline', 'save-btn' => 'Save Pipeline', 'name' => 'Name', 'rotten-days' => 'Rotten Days', 'mark-as-default' => 'Mark as Default', 'general' => 'General', 'probability' => 'Probability(%)', 'new-stage' => 'New', 'won-stage' => 'Won', 'lost-stage' => 'Lost', 'stage-btn' => 'Add Stage', 'stages' => 'Stages', 'duplicate-name' => 'The "Name" field cannot be duplicate', 'delete-stage' => 'Delete Stage', 'add-new-stages' => 'Add New Stages', 'add-stage-info' => 'Add new stage for your Pipeline', 'newly-added' => 'Newly Added', 'stage-delete-success' => 'Stage Deleted Successfully', ], 'edit' => [ 'title' => 'Edit Pipeline', 'save-btn' => 'Save Pipeline', 'name' => 'Name', 'rotten-days' => 'Rotten Days', 'mark-as-default' => 'Mark as Default', 'general' => 'General', 'probability' => 'Probability(%)', 'new-stage' => 'New', 'won-stage' => 'Won', 'lost-stage' => 'Lost', 'stage-btn' => 'Add Stage', 'stages' => 'Stages', 'duplicate-name' => 'The "Name" field cannot be duplicate', 'delete-stage' => 'Delete Stage', 'add-new-stages' => 'Add New Stages', 'add-stage-info' => 'Add new stage for your Pipeline', 'stage-delete-success' => 'Stage Deleted Successfully', ], ], 'webhooks' => [ 'index' => [ 'title' => 'Webhooks', 'create-btn' => 'Create Webhook', 'create-success' => 'Webhook created successfully.', 'update-success' => 'Webhook updated successfully.', 'delete-success' => 'Webhook deleted successfully.', 'delete-failed' => 'Webhook can not be deleted.', 'datagrid' => [ 'id' => 'ID', 'delete' => 'Delete', 'edit' => 'Edit', 'name' => 'Name', 'entity-type' => 'Entity Type', 'end-point' => 'End Point', ], ], 'create' => [ 'title' => 'Create Webhook', 'save-btn' => 'Save Webhook', 'info' => 'Enter the details of webhooks', 'url-and-parameters' => 'URL And Parameters', 'method' => 'Method', 'post' => 'Post', 'put' => 'Put', 'url-endpoint' => 'Url Endpoint', 'parameters' => 'Parameters', 'add-new-parameter' => 'Add New Parameter', 'url-preview' => 'Url Preview:', 'headers' => 'Headers', 'add-new-header' => 'Add New Header', 'body' => 'Body', 'default' => 'Default', 'x-www-form-urlencoded' => 'x-www-form-urlencoded', 'key-and-value' => 'Key and Value', 'add-new-payload' => 'Add new payload', 'raw' => 'Raw', 'general' => 'General', 'name' => 'Name', 'entity-type' => 'Entity Type', 'insert-placeholder' => 'Insert Placeholder', 'description' => 'Description', 'json' => 'Json', 'text' => 'Text', ], 'edit' => [ 'title' => 'Edit Webhook', 'edit-btn' => 'Save Webhook', 'save-btn' => 'Save Webhook', 'info' => 'Enter the details of webhooks', 'url-and-parameters' => 'URL And Parameters', 'method' => 'Method', 'post' => 'Post', 'put' => 'Put', 'url-endpoint' => 'Url Endpoint', 'parameters' => 'Parameters', 'add-new-parameter' => 'Add New Parameter', 'url-preview' => 'Url Preview:', 'headers' => 'Headers', 'add-new-header' => 'Add New Header', 'body' => 'Body', 'default' => 'Default', 'x-www-form-urlencoded' => 'x-www-form-urlencoded', 'key-and-value' => 'Key and Value', 'add-new-payload' => 'Add new payload', 'raw' => 'Raw', 'general' => 'General', 'name' => 'Name', 'entity-type' => 'Entity Type', 'insert-placeholder' => 'Insert Placeholder', 'description' => 'Description', 'json' => 'Json', 'text' => 'Text', ], ], 'warehouses' => [ 'index' => [ 'title' => 'Warehouses', 'create-btn' => 'Create Warehouse', 'create-success' => 'Warehouse created successfully.', 'name-exists' => 'Warehouse name already exists.', 'update-success' => 'Warehouse updated successfully.', 'delete-success' => 'Warehouse deleted successfully.', 'delete-failed' => 'Warehouse can not be deleted.', 'datagrid' => [ 'id' => 'ID', 'name' => 'Name', 'contact-name' => 'Contact Name', 'delete' => 'Delete', 'edit' => 'Edit', 'view' => 'View', 'created-at' => 'Created At', 'products' => 'Products', 'contact-emails' => 'Contact Emails', 'contact-numbers' => 'Contact Numbers', ], ], 'create' => [ 'title' => 'Create Warehouse', 'save-btn' => 'Save Warehouse', 'contact-info' => 'Contact Information', ], 'edit' => [ 'title' => 'Edit Warehouse', 'save-btn' => 'Save Warehouse', 'contact-info' => 'Contact Information', ], 'view' => [ 'all' => 'All', 'notes' => 'Notes', 'files' => 'Files', 'location' => 'Location', 'change-logs' => 'Changelogs', 'locations' => [ 'action' => 'Action', 'add-location' => 'Add Location', 'create-success' => 'Location created successfully.', 'delete' => 'Delete', 'delete-failed' => 'Location can not be deleted.', 'delete-success' => 'Location deleted successfully.', 'name' => 'Name', 'save-btn' => 'Save', ], 'general-information' => [ 'title' => 'General Information', ], 'contact-information' => [ 'title' => 'Contact Information', ], ], ], 'attributes' => [ 'index' => [ 'title' => 'Attributes', 'create-btn' => 'Create Attribute', 'create-success' => 'Attribute created successfully.', 'update-success' => 'Attribute updated successfully.', 'delete-success' => 'Attribute deleted successfully.', 'delete-failed' => 'Attribute can not be deleted.', 'user-define-error' => 'Can not delete system attribute.', 'mass-delete-failed' => 'System attributes can not be deleted.', 'datagrid' => [ 'yes' => 'Yes', 'no' => 'No', 'id' => 'ID', 'code' => 'Code', 'name' => 'Name', 'entity-type' => 'Entity Type', 'type' => 'Type', 'is-default' => 'Is Default', 'edit' => 'Edit', 'delete' => 'Delete', 'entity-types' => [ 'leads' => 'Leads', 'organizations' => 'Organizations', 'persons' => 'Persons', 'products' => 'Products', 'quotes' => 'Quotes', 'warehouses' => 'Warehouses', ], 'types' => [ 'text' => 'Text', 'textarea' => 'Textarea', 'price' => 'Price', 'boolean' => 'Boolean', 'select' => 'Select', 'multiselect' => 'Multiselect', 'checkbox' => 'Checkbox', 'email' => 'Email', 'address' => 'Address', 'phone' => 'Phone', 'lookup' => 'Lookup', 'datetime' => 'Datetime', 'date' => 'Date', 'image' => 'Image', 'file' => 'File', ], ], ], 'create' => [ 'title' => 'Create Attribute', 'save-btn' => 'Save Attribute', 'code' => 'Code', 'name' => 'Name', 'entity-type' => 'Entity Type', 'type' => 'Type', 'validations' => 'Validations', 'is-required' => 'Is Required', 'input-validation' => 'Input Validation', 'is-unique' => 'Is Unique', 'labels' => 'Labels', 'general' => 'General', 'numeric' => 'Numeric', 'decimal' => 'Decimal', 'url' => 'Url', 'options' => 'Options', 'option-type' => 'Option Type', 'lookup-type' => 'Lookup Type', 'add-option' => 'Add Option', 'save-option' => 'Save Option', 'option-name' => 'Option Name', 'add-attribute-options' => 'Add Attribute Options', 'text' => 'Text', 'textarea' => 'Textarea', 'price' => 'Price', 'boolean' => 'Boolean', 'select' => 'Select', 'multiselect' => 'Multiselect', 'email' => 'Email', 'address' => 'Address', 'phone' => 'Phone', 'datetime' => 'Datetime', 'date' => 'Date', 'image' => 'Image', 'file' => 'File', 'lookup' => 'Lookup', 'entity_type' => 'Entity type', 'checkbox' => 'Checkbox', 'is_required' => 'Is Required', 'is_unique' => 'Is Unique', 'actions' => 'Actions', ], 'edit' => [ 'actions' => 'Actions', 'add-attribute-options' => 'Add Attribute Options', 'add-option' => 'Add Option', 'address' => 'Address', 'boolean' => 'Boolean', 'checkbox' => 'Checkbox', 'code' => 'Code', 'date' => 'Date', 'datetime' => 'Datetime', 'decimal' => 'Decimal', 'email' => 'Email', 'entity-type' => 'Entity Type', 'entity_type' => 'Entity type', 'file' => 'File', 'general' => 'General', 'image' => 'Image', 'input-validation' => 'Input Validation', 'is-required' => 'Is Required', 'is-unique' => 'Is Unique', 'is_required' => 'Is Required', 'is_unique' => 'Is Unique', 'labels' => 'Labels', 'lookup' => 'Lookup', 'lookup-type' => 'Lookup Type', 'multiselect' => 'Multiselect', 'name' => 'Name', 'numeric' => 'Numeric', 'option-deleted' => 'Attribute Option is deleted successfully', 'option-name' => 'Option Name', 'option-type' => 'Option Type', 'options' => 'Options', 'phone' => 'Phone', 'price' => 'Price', 'save-btn' => 'Save Attribute', 'save-option' => 'Save Option', 'select' => 'Select', 'text' => 'Text', 'textarea' => 'Textarea', 'title' => 'Edit Attribute', 'type' => 'Type', 'url' => 'Url', 'validations' => 'Validations', ], ], 'data-transfer' => [ 'imports' => [ 'create' => [ 'action' => 'Action', 'allowed-errors' => 'Allowed Errors', 'back-btn' => 'Back', 'create-update' => 'Create/Update', 'delete' => 'Delete', 'download-sample' => 'Download Sample', 'field-separator' => 'Field Separator', 'file' => 'File', 'general' => 'General', 'images-directory' => 'Images Directory Path', 'process-in-queue' => 'Process In Queue', 'results' => 'Results', 'save-btn' => 'Save Import', 'settings' => 'Settings', 'skip-errors' => 'Skip Errors', 'stop-on-errors' => 'Stop on Errors', 'title' => 'Create Import', 'type' => 'Type', 'validation-strategy' => 'Validation Strategy', ], 'edit' => [ 'action' => 'Action', 'allowed-errors' => 'Allowed Errors', 'back-btn' => 'Back', 'create-update' => 'Create/Update', 'delete' => 'Delete', 'download-sample' => 'Download Sample', 'field-separator' => 'Field Separator', 'file' => 'File', 'general' => 'General', 'images-directory' => 'Images Directory Path', 'process-in-queue' => 'Process In Queue', 'results' => 'Results', 'save-btn' => 'Save Import', 'settings' => 'Settings', 'skip-errors' => 'Skip Errors', 'stop-on-errors' => 'Stop on Errors', 'title' => 'Edit Import', 'type' => 'Type', 'validation-strategy' => 'Validation Strategy', ], 'index' => [ 'button-title' => 'Create Import', 'title' => 'Imports', 'datagrid' => [ 'actions' => 'Actions', 'completed-at' => 'Completed At', 'created' => 'Created', 'delete' => 'Delete', 'deleted' => 'Deleted', 'edit' => 'Edit', 'error-file' => 'Error File', 'id' => 'ID', 'started-at' => 'Started At', 'state' => 'State', 'summary' => 'Summary', 'type' => 'Type', 'updated' => 'Updated', 'uploaded-file' => 'Uploaded File', ], ], 'import' => [ 'back-btn' => 'Back', 'completed-batches' => 'Total Batches Completed:', 'download-error-report' => 'Download Full Report', 'edit-btn' => 'Edit', 'imported-info' => 'Congratulations! Your import was successful.', 'importing-info' => 'Import In Process', 'indexing-info' => 'Resources Indexing (Price, Inventory and Elastic Search) In Progress', 'linking-info' => 'Resources Linking In Progress', 'progress' => 'Progress:', 'title' => 'Import', 'total-batches' => 'Total Batches:', 'total-created' => 'Total Records Created:', 'total-deleted' => 'Total Records Deleted:', 'total-errors' => 'Total Errors:', 'total-invalid-rows' => 'Total Invalid Rows:', 'total-rows-processed' => 'Total Rows Processed:', 'total-updated' => 'Total Records Updated:', 'validate' => 'Validate', 'validate-info' => 'Click on Validate Data to check your import.', 'validating-info' => 'The data started reading and Validating', 'validation-failed-info' => 'Your import is invalid. Please fix the following errors and try again.', 'validation-success-info' => 'Your import is valid. Click on Import to start the import process.', ], 'create-success' => 'Import created successfully.', 'delete-failed' => 'Import deletion failed unexpectedly.', 'delete-success' => 'Import deleted successfully.', 'not-valid' => 'Import is invalid', 'nothing-to-import' => 'There are no resources to import.', 'setup-queue-error' => 'Please change your queue driver to "database" or "redis" to start the import process.', 'update-success' => 'Import updated successfully.', ], ], ], 'activities' => [ 'index' => [ 'title' => 'Activities', 'datagrid' => [ 'comment' => 'Comment', 'created_at' => 'Created At', 'created_by' => 'Created By', 'edit' => 'Edit', 'id' => 'ID', 'done' => 'Is Done', 'not-done' => 'Not Done', 'lead' => 'Lead', 'mass-delete' => 'Mass Delete', 'mass-update' => 'Mass Update', 'schedule-from' => 'Schedule From', 'schedule-to' => 'Schedule To', 'schedule_from' => 'Schedule From', 'schedule_to' => 'Schedule To', 'title' => 'Title', 'is_done' => 'Is Done', 'type' => 'Type', 'update' => 'Update', 'call' => 'Call', 'meeting' => 'Meeting', 'lunch' => 'Lunch', ], ], 'edit' => [ 'title' => 'Edit Activity', 'back-btn' => 'Back', 'save-btn' => 'Save Activity', 'type' => 'Activity Type', 'call' => 'Call', 'meeting' => 'Meeting', 'lunch' => 'Lunch', 'schedule_to' => 'Schedule To', 'schedule_from' => 'Schedule From', 'location' => 'Location', 'comment' => 'Comment', 'lead' => 'Lead', 'participants' => 'Participants', 'general' => 'General', 'persons' => 'Persons', 'no-result-found' => 'Records not found.', 'users' => 'Users', ], 'updated' => 'Updated :attribute', 'created' => 'Created', 'duration-overlapping' => 'Participants have another meeting at this time. Do you want to continue?', 'create-success' => 'Activity created successfully.', 'update-success' => 'Activity updated successfully.', 'overlapping-error' => 'Participants have another meeting at this time.', 'destroy-success' => 'Activity deleted successfully.', 'delete-failed' => 'Activity can not be deleted.', 'mass-update-success' => 'Activities updated successfully.', 'mass-destroy-success' => 'Activities deleted successfully.', 'mass-delete-failed' => 'Activities can not be deleted.', ], 'mail' => [ 'index' => [ 'compose' => 'Compose', 'draft' => 'Draft', 'inbox' => 'Inbox', 'outbox' => 'Outbox', 'sent' => 'Sent', 'trash' => 'Trash', 'compose-mail-btn' => 'Compose Mail', 'btn' => 'Mail', 'mail' => [ 'title' => 'Compose Mail', 'to' => 'To', 'enter-emails' => 'Press enter to add emails', 'cc' => 'CC', 'bcc' => 'BCC', 'subject' => 'Subject', 'send-btn' => 'Send', 'message' => 'Message', 'draft' => 'Draft', ], 'datagrid' => [ 'id' => 'ID', 'from' => 'From', 'to' => 'To', 'subject' => 'Subject', 'tags' => 'Tags', 'content' => 'Content', 'attachments' => 'Attachments', 'date' => 'Date', 'move-to-inbox' => 'Moved To Inbox', 'move-to-trash' => 'Moved To Trash', 'edit' => 'Edit', 'view' => 'View', 'delete' => 'Delete', ], ], 'create-success' => 'Email sent successfully.', 'update-success' => 'Email updated successfully.', 'mass-update-success' => 'Emails updated successfully.', 'delete-success' => 'Email deleted successfully.', 'delete-failed' => 'Email can not be deleted.', 'invalid-route' => 'Invalid route for mail.', 'unauthorized' => 'This action is unauthorized.', 'view' => [ 'title' => 'Mails', 'subject' => ':subject', 'link-mail' => 'Link Mail', 'to' => 'To', 'cc' => 'CC', 'bcc' => 'BCC', 'reply' => 'Reply', 'reply-all' => 'Reply All', 'forward' => 'Forward', 'delete' => 'Delete', 'enter-mails' => 'Enter email id', 'rotten-days' => 'Lead is rotten for :days days', 'search-an-existing-lead' => 'Search an existing lead', 'search-an-existing-contact' => 'Search an existing contact', 'message' => 'Message', 'add-attachments' => 'Add Attachments', 'discard' => 'Discard', 'send' => 'Send', 'no-result-found' => 'No Results found', 'add-new-contact' => 'Add New Contact', 'description' => 'Description', 'search' => 'Search...', 'add-new-lead' => 'Add New Lead', 'create-new-contact' => 'Create New Contact', 'save-contact' => 'Save Contact', 'create-lead' => 'Create Lead', 'linked-contact' => 'Linked Contact', 'link-to-contact' => 'Link To Contact', 'link-to-lead' => 'Link To Lead', 'linked-lead' => 'Linked Lead', 'lead-details' => 'Lead Details', 'contact-person' => 'Contact Person', 'product' => 'Product', 'tags' => [ 'create-success' => 'Tag created successfully.', 'destroy-success' => 'Tag deleted successfully.', ], ], ], 'common' => [ 'custom-attributes' => [ 'add-more' => 'Add More', 'address' => 'Address', 'city' => 'City', 'contact' => 'Contact Numbers', 'country' => 'Country', 'email' => 'Email', 'home' => 'Home', 'postcode' => 'Postcode', 'save' => 'Save', 'select' => 'Select', 'select-country' => 'Select Country', 'select-state' => 'Select State', 'state' => 'State', 'update-contact-title' => 'Update Contact Numbers', 'update-emails-title' => 'Update Contact Emails', 'work' => 'Work', ], ], 'leads' => [ 'create-success' => 'Lead created successfully.', 'update-success' => 'Leads updated successfully.', 'update-failed' => 'Leads can not be deleted.', 'destroy-success' => 'Lead deleted successfully.', 'destroy-failed' => 'Lead can not be deleted.', 'file' => [ 'data-not-found' => 'Data not found.', 'empty-content' => 'PDF content is empty or could not be extracted.', 'failed-extract' => 'Failed to extract text from file.', 'insufficient-info' => 'Due to insufficient data, we are unable to process your request at the moment.', 'invalid-base64' => 'Invalid base64 format.', 'invalid-format' => 'Invalid JSON format.', 'invalid-response' => 'Invalid AI response format.', 'missing-api-key' => 'Missing API key or model configuration.', 'not-found' => 'File not found.', 'recursive-call' => 'Recursive call detected.', 'text-generation-failed' => 'Text extraction failed. The file might be empty or unreadable.', ], 'index' => [ 'title' => 'Leads', 'create-btn' => 'Create Lead', 'datagrid' => [ 'id' => 'ID', 'sales-person' => 'Sales Person', 'subject' => 'Subject', 'source' => 'Source', 'lead-value' => 'Lead Value', 'lead-type' => 'Lead Type', 'tag-name' => 'Tag Name', 'contact-person' => 'Contact Person', 'stage' => 'Stage', 'rotten-lead' => 'Rotten Lead', 'date-to' => 'Date To', 'created-at' => 'Created At', 'no' => 'No', 'yes' => 'Yes', 'delete' => 'Delete', 'mass-delete' => 'Mass Delete', 'mass-update' => 'Mass Update', ], 'kanban' => [ 'rotten-days' => 'Lead is rotten for :days days', 'empty-list' => 'Your Leads List is Empty', 'empty-list-description' => 'Create a lead to organize your goals.', 'create-lead-btn' => 'Create Lead', 'columns' => [ 'contact-person' => 'Contact Person', 'id' => 'ID', 'lead-type' => 'Lead Type', 'lead-value' => 'Lead Value', 'sales-person' => 'Sales Person', 'source' => 'Source', 'title' => 'Title', 'tags' => 'Tags', 'expected-close-date' => 'Expected Close Date', 'created-at' => 'Created At', ], 'toolbar' => [ 'search' => [ 'title' => 'Search by Title', ], 'filters' => [ 'apply-filters' => 'Apply Filters', 'clear-all' => 'Clear All', 'filter' => 'Filter', 'filters' => 'Filters', 'from' => 'From', 'select' => 'Select', 'to' => 'To', ], ], ], 'view-switcher' => [ 'all-pipelines' => 'All Pipelines', 'create-new-pipeline' => 'Create New Pipeline', ], 'upload' => [ 'create-lead' => 'Create Lead Using AI', 'file' => 'File Upload', 'file-info' => 'Only pdf,bmp,jpg,jpeg,png format files are accepted.', 'file-required' => 'Please select at least one valid file to proceed.', 'save-btn' => 'Save', 'upload-file' => 'Upload File', ], ], 'create' => [ 'title' => 'Create Lead', 'save-btn' => 'Save', 'details' => 'Details', 'details-info' => 'Put The Basic Information of the Lead', 'contact-person' => 'Contact Person', 'contact-info' => 'Information About the Contact Person', 'products' => 'Products', 'products-info' => 'Information About the Products', ], 'edit' => [ 'title' => 'Edit Lead', 'save-btn' => 'Save', 'details' => 'Details', 'details-info' => 'Put The Basic Information of the Lead', 'contact-person' => 'Contact Person', 'contact-info' => 'Information About the Contact Person', 'products' => 'Products', 'products-info' => 'Information About the Products', ], 'common' => [ 'contact' => [ 'name' => 'Name', 'email' => 'Email', 'contact-number' => 'Contact Number', 'organization' => 'Organization', ], 'products' => [ 'product-name' => 'Product Name', 'quantity' => 'Quantity', 'price' => 'Price', 'amount' => 'Amount', 'action' => 'Action', 'add-more' => 'Add More', 'total' => 'Total', ], ], 'view' => [ 'title' => 'Lead: :title', 'rotten-days' => ':days Days', 'tabs' => [ 'description' => 'Description', 'products' => 'Products', 'quotes' => 'Quotes', ], 'attributes' => [ 'title' => 'About Lead', ], 'quotes' => [ 'subject' => 'Subject', 'expired-at' => 'Expired At', 'sub-total' => 'Sub Total', 'discount' => 'Discount', 'tax' => 'Tax', 'adjustment' => 'Adjustment', 'grand-total' => 'Grand Total', 'delete' => 'Delete', 'edit' => 'Edit', 'download' => 'Download', 'destroy-success' => 'Quote deleted successfully.', 'empty-title' => 'No Quotes Found', 'empty-info' => 'No Quotes Found for this Lead', 'add-btn' => 'Add Quote', ], 'products' => [ 'product-name' => 'Product Name', 'quantity' => 'Quantity', 'price' => 'Price', 'amount' => 'Amount', 'action' => 'Action', 'add-more' => 'Add More', 'total' => 'Total', 'empty-title' => 'No Products Found', 'empty-info' => 'No Products Found for this Lead', 'add-product' => 'Add Product', ], 'persons' => [ 'title' => 'About Persons', 'job-title' => ':job_title at :organization', ], 'stages' => [ 'won' => 'Won', 'lost' => 'Lost', 'need-more-info' => 'Need More Details', 'closed-at' => 'Closed At', 'won-value' => 'Won Value', 'lost-reason' => 'Lost Reason', 'save-btn' => 'Save', ], 'tags' => [ 'create-success' => 'Tag created successfully.', 'destroy-success' => 'Tag deleted successfully.', ], ], ], 'configuration' => [ 'index' => [ 'back' => 'Back', 'delete' => 'Delete', 'save-btn' => 'Save Configuration', 'save-success' => 'Configuration Saved Successfully.', 'search' => 'Search', 'select-country' => 'Select Country', 'select-state' => 'Select State', 'title' => 'Configuration', 'general' => [ 'title' => 'General', 'info' => 'General Configuration', 'general' => [ 'title' => 'General', 'info' => 'Update your general settings here.', 'locale-settings' => [ 'title' => 'Locale Settings', 'title-info' => 'Defines the language used in the user interface, such as Arabic (ar), English (en), Spanish (es), Persian(fa) and Turkish (tr).', ], 'admin-logo' => [ 'logo-image' => 'Logo Image', 'title' => 'Admin Logo', 'title-info' => 'Configure logo image for your admin panel.', ], ], 'settings' => [ 'title' => 'Settings', 'info' => 'Update your settings here.', 'footer' => [ 'info' => 'We can configure the powered by section here.', 'powered-by' => 'Powered by text editor', 'title' => 'Powered by Section Configurations', ], 'menu' => [ 'activities' => 'Activities', 'configuration' => 'Configuration', 'contacts' => 'Contacts', 'dashboard' => 'Dashboard', 'draft' => 'Draft', 'inbox' => 'Inbox', 'info' => 'We can configure the menu items name here.', 'leads' => 'Leads', 'mail' => 'Mail', 'organizations' => 'Organizations', 'outbox' => 'Outbox', 'persons' => 'Persons', 'products' => 'Products', 'quotes' => 'Quotes', 'sent' => 'Sent', 'settings' => 'Settings', 'title' => 'Menu Item Configurations', 'trash' => 'Trash', ], 'menu-color' => [ 'brand-color' => 'Brand Color', 'info' => 'We can change the menu items colors here.', 'title' => 'Menu Item Color Configurations', ], ], ], 'email' => [ 'title' => 'Email Settings', 'info' => 'Email configuration for the application.', 'imap' => [ 'title' => 'IMAP Settings', 'info' => 'IMAP email configuration for receiving emails.', 'account' => [ 'title' => 'IMAP Account', 'title-info' => 'Configure your IMAP account settings here.', 'host' => 'Host', 'port' => 'Port', 'encryption' => 'Encryption Type', 'validate-cert' => 'Validate SSL Certificate', 'username' => 'IMAP Username', 'password' => 'IMAP Password', ], ], ], 'magic-ai' => [ 'title' => 'Magic AI', 'info' => 'Magic AI configuration for the application.', 'settings' => [ 'api-key' => 'API Key', 'api-key-info' => 'Remember to use a OpenRouter API key for each model. It\'s a simple step to enhance security and performance.', 'enable' => 'Enable', 'info' => 'Enhance your Magic AI experience with your OpenRouter API Key. Integrate it now for a seamless, personalized AI adventure tailored just for you! Effortlessly customize settings and take control of your AI journey.', 'other' => 'Other Model', 'other-model' => 'For other models, use the Model ID from OpenRouter.', 'doc-generation' => 'DOC Generation', 'doc-generation-info' => 'Enable the DOC Generation feature to automatically extract data from DOC files and convert them into text format. Enhance your productivity and efficiency by enabling this feature to streamline your workflow.', 'title' => 'General Settings', 'models' => [ 'deepseek-r1' => 'Deepseek R1 Distill-llama-8b', 'gemini-2-0-flash-001' => 'Gemini 2.0 flash-001', 'gpt-4o' => 'GPT-4.0', 'gpt-4o-mini' => 'GPT-4.0 mini', 'grok-2-1212' => 'Grok 2.12', 'llama-3-2-3b-instruct' => 'Llama 3.2 3b Instruct', 'title' => 'Models', ], ], ], ], ], 'dashboard' => [ 'index' => [ 'title' => 'Dashboard', 'start-date' => 'Start Date', 'end-date' => 'End Date', 'revenue' => [ 'lost-revenue' => 'Lost Revenue', 'won-revenue' => 'Won Revenue', ], 'over-all' => [ 'average-lead-value' => 'Average Lead Value', 'total-leads' => 'Total Leads', 'average-leads-per-day' => 'Average Leads Per Day', 'total-quotations' => 'Total Quotations', 'total-persons' => 'Total Persons', 'total-organizations' => 'Total Organizations', ], 'total-leads' => [ 'title' => 'Leads', 'total' => 'Total Leads', 'won' => 'Won Leads', 'lost' => 'Lost Leads', ], 'revenue-by-sources' => [ 'title' => 'Revenue By Sources', 'empty-title' => 'No Data Available', 'empty-info' => 'No data available for selected interval', ], 'revenue-by-types' => [ 'title' => 'Revenue By Types', 'empty-title' => 'No Data Available', 'empty-info' => 'No data available for selected interval', ], 'top-selling-products' => [ 'title' => 'Top Products', 'empty-title' => 'No Products Found', 'empty-info' => 'No products available for selected interval', ], 'top-persons' => [ 'title' => 'Top Persons', 'empty-title' => 'No Persons Found', 'empty-info' => 'No persons available for selected interval', ], 'open-leads-by-states' => [ 'title' => 'Open Leads By Stages', 'empty-title' => 'No Data Available', 'empty-info' => 'No data available for selected interval', ], ], ], 'layouts' => [ 'app-version' => 'Version: :version', 'dashboard' => 'Dashboard', 'leads' => 'Leads', 'quotes' => 'Quotes', 'quote' => 'Quote', 'mail' => [ 'title' => 'Mail', 'compose' => 'Compose', 'inbox' => 'Inbox', 'draft' => 'Draft', 'outbox' => 'Outbox', 'sent' => 'Sent', 'trash' => 'Trash', 'setting' => 'Setting', ], 'activities' => 'Activities', 'contacts' => 'Contacts', 'persons' => 'Persons', 'person' => 'Person', 'organizations' => 'Organizations', 'organization' => 'Organization', 'products' => 'Products', 'product' => 'Product', 'settings' => 'Settings', 'user' => 'User', 'user-info' => 'Manage all your users and their permissions in the CRM, what they’re allowed to do.', 'groups' => 'Groups', 'groups-info' => 'Add, edit or delete groups from CRM', 'roles' => 'Roles', 'role' => 'Role', 'roles-info' => 'Add, edit or delete roles from CRM', 'users' => 'Users', 'users-info' => 'Add, edit or delete users from CRM', 'lead' => 'Lead', 'lead-info' => 'Manage all your leads related settings in the CRM', 'pipelines' => 'Pipelines', 'pipelines-info' => 'Add, edit or delete pipelines from CRM', 'sources' => 'Sources', 'sources-info' => 'Add, edit or delete sources from CRM', 'types' => 'Types', 'types-info' => 'Add, edit or delete types from CRM', 'automation' => 'Automation', 'automation-info' => 'Manage all your automation related settings in the CRM', 'attributes' => 'Attributes', 'attribute' => 'Attribute', 'attributes-info' => 'Add, edit or delete attributes from CRM', 'email-templates' => 'Email Templates', 'email' => 'Email', 'email-templates-info' => 'Add, edit or delete email templates from CRM', 'events' => 'Events', 'events-info' => 'Add, edit or delete events from CRM', 'campaigns' => 'Campaigns', 'campaigns-info' => 'Add, edit or delete campaigns from CRM', 'workflows' => 'Workflows', 'workflows-info' => 'Add, edit or delete workflows from CRM', 'webhooks' => 'Webhooks', 'webhooks-info' => 'Add, edit or delete webhooks from CRM', 'other-settings' => 'Other Settings', 'other-settings-info' => 'Manage all your extra settings in the CRM', 'tags' => 'Tags', 'tags-info' => 'Add, edit or delete tags from CRM', 'my-account' => 'My Account', 'sign-out' => 'Sign Out', 'back' => 'Back', 'name' => 'Name', 'configuration' => 'Configuration', 'activities' => 'Activities', 'howdy' => 'Howdy!', 'warehouses' => 'Warehouses', 'warehouse' => 'Warehouse', 'warehouses-info' => 'Add, edit or delete warehouses from CRM', 'inventory' => 'Inventory', 'inventory-info' => 'Manage all your inventory related settings in the CRM', 'data_transfer' => 'Data Transfer', 'data_transfer_info' => 'Manage persons, products and leads data transfer related settings in the CRM', ], 'user' => [ 'account' => [ 'name' => 'Name', 'email' => 'Email', 'password' => 'Password', 'my_account' => 'My account', 'update_details' => 'Update Details', 'current_password' => 'Current password', 'confirm_password' => 'Confirm password', 'password-match' => 'Current password does not match.', 'account-save' => 'Account changes saved successfully.', 'permission-denied' => 'Permission Denied', 'remove-image' => 'Remove Image', 'upload_image_pix' => 'Upload a Profile Image (100px x 100px)', 'upload_image_format' => 'in PNG or JPG Format', 'image_upload_message' => 'Only images (.jpeg, .jpg, .png, ..) are allowed.', ], ], 'emails' => [ 'common' => [ 'dear' => 'Dear :name', 'cheers' => 'Cheers,
Team :app_name', 'user' => [ 'dear' => 'Dear :username', 'create-subject' => 'You are added as a member.', 'create-body' => 'Congratulations! You are now a member of our team.', 'forget-password' => [ 'subject' => 'Customer Reset Password', 'dear' => 'Dear :username', 'reset-password' => 'Reset Password', 'info' => 'You are receiving this email because we received a password reset request for your account', 'final-summary' => 'If you did not request a password reset, no further action is required', 'thanks' => 'Thanks!', ], ], ], ], 'validations' => [ 'message' => [ 'decimal' => 'The :attribute must be a decimal.', ], ], 'errors' => [ 'dashboard' => 'Dashboard', 'go-back' => 'Go Back', 'support' => 'If the problem persists, reach out to us at :email for assistance.', '404' => [ 'description' => 'Oops! The page you\'re looking for is on vacation. It seems we couldn\'t find what you were searching for.', 'title' => '404 Page Not Found', ], '401' => [ 'description' => 'Oops! Looks like you\'re not allowed to access this page. It seems you\'re missing the necessary credentials.', 'title' => '401 Unauthorized', ], '403' => [ 'description' => 'Oops! This page is off-limits. It appears you don\'t have the required permissions to view this content.', 'title' => '403 Forbidden', ], '500' => [ 'description' => 'Oops! Something went wrong. It seems we\'re having trouble loading the page you\'re looking for.', 'title' => '500 Internal Server Error', ], '503' => [ 'description' => 'Oops! Looks like we\'re temporarily down for maintenance. Please check back in a bit.', 'title' => '503 Service Unavailable', ], ], 'export' => [ 'csv' => 'CSV', 'download' => 'Download', 'export' => 'Export', 'no-records' => 'Nothing to export', 'xls' => 'XLS', 'xlsx' => 'XLSX', ], ]; ================================================ FILE: packages/Webkul/Admin/src/Resources/lang/es/app.php ================================================ [ 'leads' => 'Clientes potenciales', 'lead' => 'Cliente potencial', 'quotes' => 'Cotizaciones', 'mail' => 'Correo', 'inbox' => 'Bandeja de entrada', 'draft' => 'Borradores', 'outbox' => 'Bandeja de salida', 'sent' => 'Enviados', 'trash' => 'Papelera', 'activities' => 'Actividades', 'webhook' => 'Webhook', 'contacts' => 'Contactos', 'persons' => 'Personas', 'organizations' => 'Organizaciones', 'products' => 'Productos', 'settings' => 'Configuraciones', 'groups' => 'Grupos', 'roles' => 'Roles', 'users' => 'Usuarios', 'user' => 'Usuario', 'automation' => 'Automatización', 'attributes' => 'Atributos', 'pipelines' => 'Canales', 'sources' => 'Fuentes', 'types' => 'Tipos', 'email-templates' => 'Plantillas de correo electrónico', 'workflows' => 'Flujos de trabajo', 'other-settings' => 'Otras configuraciones', 'tags' => 'Etiquetas', 'configuration' => 'Configuración', 'create' => 'Crear', 'edit' => 'Editar', 'view' => 'Ver', 'print' => 'Imprimir', 'delete' => 'Eliminar', 'export' => 'Exportar', 'mass-delete' => 'Eliminar en masa', 'data-transfer' => 'Transferencia de Datos', 'imports' => 'Importaciones', 'import' => 'Importar', 'event' => 'Evento', 'campaigns' => 'Campañas', 'warehouses' => 'Almacenes', 'inventory' => 'Inventario', ], 'users' => [ 'activate-warning' => 'Tu cuenta aún no está activada. Por favor, contacta al administrador.', 'login-error' => 'Las credenciales no coinciden con nuestros registros.', 'not-permission' => 'No tienes permiso para acceder al panel de administración.', 'login' => [ 'email' => 'Dirección de correo electrónico', 'forget-password-link' => '¿Olvidaste tu contraseña?', 'password' => 'Contraseña', 'submit-btn' => 'Iniciar sesión', 'title' => 'Iniciar sesión', ], 'forget-password' => [ 'create' => [ 'email' => 'Correo electrónico registrado', 'email-not-exist' => 'El correo electrónico no existe', 'page-title' => 'Olvidé mi contraseña', 'reset-link-sent' => 'Enlace para restablecer la contraseña enviado', 'sign-in-link' => '¿Volver a iniciar sesión?', 'submit-btn' => 'Restablecer', 'title' => 'Recuperar contraseña', ], ], 'reset-password' => [ 'back-link-title' => '¿Volver a iniciar sesión?', 'confirm-password' => 'Confirmar contraseña', 'email' => 'Correo electrónico registrado', 'password' => 'Contraseña', 'submit-btn' => 'Restablecer contraseña', 'title' => 'Restablecer contraseña', ], ], 'account' => [ 'edit' => [ 'back-btn' => 'Regresar', 'change-password' => 'Cambiar contraseña', 'confirm-password' => 'Confirmar contraseña', 'current-password' => 'Contraseña actual', 'email' => 'Correo electrónico', 'general' => 'General', 'invalid-password' => 'La contraseña actual que ingresaste es incorrecta.', 'name' => 'Nombre', 'password' => 'Contraseña', 'profile-image' => 'Imagen de perfil', 'save-btn' => 'Guardar cuenta', 'title' => 'Mi cuenta', 'update-success' => 'Cuenta actualizada con éxito', 'upload-image-info' => 'Sube una imagen de perfil (110 px × 110 px) en formato PNG o JPG', ], ], 'components' => [ 'activities' => [ 'actions' => [ 'mail' => [ 'btn' => 'Correo', 'title' => 'Redactar correo', 'to' => 'Para', 'enter-emails' => 'Presiona Enter para agregar direcciones de correo electrónico', 'cc' => 'CC', 'bcc' => 'CCO', 'subject' => 'Asunto', 'send-btn' => 'Enviar', 'message' => 'Mensaje', ], 'file' => [ 'btn' => 'Archivo', 'title' => 'Agregar archivo', 'title-control' => 'Título', 'name' => 'Nombre', 'description' => 'Descripción', 'file' => 'Archivo', 'save-btn' => 'Guardar archivo', ], 'note' => [ 'btn' => 'Nota', 'title' => 'Agregar nota', 'comment' => 'Comentario', 'save-btn' => 'Guardar nota', ], 'activity' => [ 'btn' => 'Actividad', 'title' => 'Agregar actividad', 'title-control' => 'Título', 'description' => 'Descripción', 'schedule-from' => 'Programar desde', 'schedule-to' => 'Programar hasta', 'location' => 'Ubicación', 'call' => 'Llamada', 'meeting' => 'Reunión', 'lunch' => 'Almuerzo', 'save-btn' => 'Guardar actividad', 'participants' => [ 'title' => 'Participantes', 'placeholder' => 'Escribe para buscar participantes', 'users' => 'Usuarios', 'persons' => 'Personas', 'no-results' => 'No se encontraron resultados...', ], ], ], 'index' => [ 'all' => 'Todo', 'bcc' => 'CCO', 'by-user' => 'Por :user', 'calls' => 'Llamadas', 'cc' => 'CC', 'change-log' => 'Registros de cambios', 'delete' => 'Eliminar', 'edit' => 'Editar', 'emails' => 'Correos electrónicos', 'empty' => 'Vacío', 'files' => 'Archivos', 'from' => 'De', 'location' => 'Ubicación', 'lunches' => 'Almuerzos', 'mark-as-done' => 'Marcar como hecho', 'meetings' => 'Reuniones', 'notes' => 'Notas', 'participants' => 'Participantes', 'planned' => 'Planificado', 'quotes' => 'Cotizaciones', 'scheduled-on' => 'Programado en', 'system' => 'Sistema', 'to' => 'A', 'unlink' => 'Desvincular', 'view' => 'Ver', 'empty-placeholders' => [ 'all' => [ 'title' => 'No se encontraron actividades', 'description' => 'No se encontraron actividades para esto. Puedes agregar actividades haciendo clic en el botón Actividad en el panel izquierdo.', ], 'planned' => [ 'title' => 'No se encontraron actividades planificadas', 'description' => 'No se encontraron actividades planificadas para esto. Puedes agregarlas haciendo clic en el botón Actividad en el panel izquierdo.', ], 'notes' => [ 'title' => 'No se encontraron notas', 'description' => 'No se encontraron notas para esto. Puedes agregarlas haciendo clic en el botón Nota en el panel izquierdo.', ], 'calls' => [ 'title' => 'No se encontraron llamadas', 'description' => 'No se encontraron llamadas para esto. Puedes agregarlas haciendo clic en el botón Actividad en el panel izquierdo y seleccionando el tipo Llamada.', ], 'meetings' => [ 'title' => 'No se encontraron reuniones', 'description' => 'No se encontraron reuniones para esto. Puedes agregarlas haciendo clic en el botón Actividad en el panel izquierdo y seleccionando el tipo Reunión.', ], 'lunches' => [ 'title' => 'No se encontraron almuerzos', 'description' => 'No se encontraron almuerzos para esto. Puedes agregarlos haciendo clic en el botón Actividad en el panel izquierdo y seleccionando el tipo Almuerzo.', ], 'files' => [ 'title' => 'No se encontraron archivos', 'description' => 'No se encontraron archivos para esto. Puedes agregarlos haciendo clic en el botón Archivo en el panel izquierdo.', ], 'emails' => [ 'title' => 'No se encontraron correos electrónicos', 'description' => 'No se encontraron correos electrónicos para esto. Puedes agregarlos haciendo clic en el botón Correo en el panel izquierdo.', ], 'system' => [ 'title' => 'No se encontraron registros de cambios', 'description' => 'No se encontraron registros de cambios para esto.', ], ], ], ], 'media' => [ 'images' => [ 'add-image-btn' => 'Agregar imagen', 'ai-add-image-btn' => 'Inteligencia artificial mágica', 'allowed-types' => 'png, jpeg, jpg', 'not-allowed-error' => 'Solo se aceptan archivos de imagen (.jpeg, .jpg, .png, ..)', 'placeholders' => [ 'front' => 'Frontal', 'next' => 'Siguiente', 'size' => 'Tamaño', 'use-cases' => 'Casos de uso', 'zoom' => 'Acercar', ], ], 'videos' => [ 'add-video-btn' => 'Agregar video', 'allowed-types' => 'mp4, webm, mkv', 'not-allowed-error' => 'Solo se aceptan archivos de video (.mp4, .mov, .ogg ..)', ], ], 'datagrid' => [ 'index' => [ 'no-records-selected' => 'No se han seleccionado registros.', 'must-select-a-mass-action-option' => 'Debes seleccionar una opción de acción en masa.', 'must-select-a-mass-action' => 'Debes seleccionar una acción en masa.', ], 'toolbar' => [ 'length-of' => ':length de', 'of' => 'de', 'per-page' => 'Por Página', 'results' => ':total Resultados', 'delete' => 'Eliminar', 'selected' => ':total Elementos Seleccionados', 'mass-actions' => [ 'submit' => 'Enviar', 'select-option' => 'Seleccionar Opción', 'select-action' => 'Seleccionar Acción', ], 'filter' => [ 'apply-filters-btn' => 'Aplicar Filtros', 'back-btn' => 'Regresar', 'create-new-filter' => 'Crear Nuevo Filtro', 'custom-filters' => 'Filtros Personalizados', 'delete-error' => 'Hubo un error al eliminar el filtro, por favor intente de nuevo.', 'delete-success' => 'Filtro eliminado con éxito.', 'empty-description' => 'No hay filtros seleccionados disponibles para guardar. Por favor, seleccione filtros para guardar.', 'empty-title' => 'Agregar Filtros para Guardar', 'name' => 'Nombre', 'quick-filters' => 'Filtros Rápidos', 'save-btn' => 'Guardar', 'save-filter' => 'Guardar Filtro', 'saved-success' => 'Filtro guardado con éxito.', 'selected-filters' => 'Filtros Seleccionados', 'title' => 'Filtro', 'update' => 'Actualizar', 'update-filter' => 'Actualizar Filtro', 'updated-success' => 'Filtro actualizado con éxito.', ], 'search' => [ 'title' => 'Buscar', ], ], 'filters' => [ 'select' => 'Seleccionar', 'title' => 'Filtros', 'dropdown' => [ 'searchable' => [ 'at-least-two-chars' => 'Escribe al menos 2 caracteres...', 'no-results' => 'No se encontraron resultados...', ], ], 'custom-filters' => [ 'clear-all' => 'Borrar Todo', 'title' => 'Filtros Personalizados', ], 'boolean-options' => [ 'false' => 'Falso', 'true' => 'Verdadero', ], 'date-options' => [ 'last-month' => 'Mes Pasado', 'last-six-months' => 'Últimos 6 Meses', 'last-three-months' => 'Últimos 3 Meses', 'this-month' => 'Este Mes', 'this-week' => 'Esta Semana', 'this-year' => 'Este Año', 'today' => 'Hoy', 'yesterday' => 'Ayer', ], ], 'table' => [ 'actions' => 'Acciones', 'no-records-available' => 'No hay Registros Disponibles.', ], ], 'modal' => [ 'confirm' => [ 'agree-btn' => 'Aceptar', 'disagree-btn' => 'Rechazar', 'message' => '¿Estás seguro de que quieres realizar esta acción?', 'title' => '¿Estás seguro?', ], ], 'tags' => [ 'index' => [ 'title' => 'Etiquetas', 'added-tags' => 'Etiquetas Agregadas', 'save-btn' => 'Guardar Etiqueta', 'placeholder' => 'Escribe para buscar etiquetas', 'add-tag' => 'Agregar \\":term\\"...', 'aquarelle-red' => 'Rojo Aquarelle', 'crushed-cashew' => 'Anacardo Triturado', 'beeswax' => 'Cera de Abejas', 'lemon-chiffon' => 'Chiffon de Limón', 'snow-flurry' => 'Tormenta de Nieve', 'honeydew' => 'Melón', ], ], 'layouts' => [ 'powered-by' => [ 'description' => 'Desarrollado por :krayin, un proyecto de código abierto de :webkul.', ], 'header' => [ 'mega-search' => [ 'title' => 'Búsqueda Mega', 'tabs' => [ 'leads' => 'Oportunidades', 'quotes' => 'Cotizaciones', 'persons' => 'Personas', 'products' => 'Productos', ], 'explore-all-products' => 'Explorar todos los Productos', 'explore-all-leads' => 'Explorar todas las Oportunidades', 'explore-all-contacts' => 'Explorar todos los Contactos', 'explore-all-quotes' => 'Explorar todas las Cotizaciones', 'explore-all-matching-products' => 'Explorar todos los productos que coinciden con ":query" (:count)', 'explore-all-matching-leads' => 'Explorar todas las oportunidades que coinciden con ":query" (:count)', 'explore-all-matching-contacts' => 'Explorar todos los contactos que coinciden con ":query" (:count)', 'explore-all-matching-quotes' => 'Explorar todas las cotizaciones que coinciden con ":query" (:count)', ], ], ], 'attributes' => [ 'edit' => [ 'delete' => 'Eliminar', ], 'lookup' => [ 'click-to-add' => 'Haz clic para agregar', 'search' => 'Buscar...', 'no-result-found' => 'No se encontraron resultados', ], ], 'lookup' => [ 'click-to-add' => 'Haz clic para agregar', 'no-results' => 'No se encontraron resultados', 'add-as-new' => 'Agregar como nuevo', 'search' => 'Buscar...', ], 'flash-group' => [ 'success' => 'Éxito', 'error' => 'Error', 'warning' => 'Advertencia', 'info' => 'Información', ], 'tiny-mce' => [ 'http-error' => 'Error HTTP', 'invalid-json' => 'Respuesta JSON no válida del servidor.', 'upload-failed' => 'Error al subir el archivo. Por favor, inténtelo de nuevo.', ], ], 'quotes' => [ 'index' => [ 'title' => 'Cotizaciones', 'create-btn' => 'Crear Cotización', 'create-success' => 'Cotización creada con éxito.', 'update-success' => 'Cotización actualizada con éxito.', 'delete-success' => 'Cotización eliminada con éxito.', 'delete-failed' => 'No se puede eliminar la cotización.', 'datagrid' => [ 'subject' => 'Asunto', 'sales-person' => 'Vendedor', 'expired-at' => 'Vence en', 'created-at' => 'Creado en', 'person' => 'Persona', 'subtotal' => 'Subtotal', 'discount' => 'Descuento', 'tax' => 'Impuesto', 'adjustment' => 'Ajuste', 'grand-total' => 'Total General', 'edit' => 'Editar', 'delete' => 'Eliminar', 'print' => 'Imprimir', ], 'pdf' => [ 'adjustment' => 'Ajuste', 'amount' => 'Monto', 'billing-address' => 'Dirección de Facturación', 'date' => 'Fecha', 'discount' => 'Descuento', 'expired-at' => 'Vence en', 'grand-total' => 'Total General', 'person' => 'Persona', 'price' => 'Precio', 'product-name' => 'Nombre del Producto', 'quantity' => 'Cantidad', 'quote-id' => 'ID de Cotización', 'sales-person' => 'Vendedor', 'shipping-address' => 'Dirección de Envío', 'sku' => 'SKU', 'sub-total' => 'Subtotal', 'subject' => 'Asunto', 'tax' => 'Impuesto', 'title' => 'Cotización', ], ], 'create' => [ 'title' => 'Crear Cotización', 'save-btn' => 'Guardar Cotización', 'quote-info' => 'Información de la Cotización', 'quote-info-info' => 'Introduce la información básica de la cotización.', 'address-info' => 'Información de la Dirección', 'address-info-info' => 'Información sobre la dirección relacionada con la cotización.', 'quote-items' => 'Artículos de la Cotización', 'search-products' => 'Buscar Productos', 'link-to-lead' => 'Vincular a cliente potencial', 'quote-item-info' => 'Agregar Solicitud de Producto para esta cotización.', 'quote-name' => 'Nombre de la Cotización', 'quantity' => 'Cantidad', 'price' => 'Precio', 'discount' => 'Descuento', 'tax' => 'Impuesto', 'total' => 'Total', 'amount' => 'Monto', 'add-item' => '+ Agregar Artículo', 'sub-total' => 'Subtotal (:symbol)', 'total-discount' => 'Descuento (:symbol)', 'total-tax' => 'Impuesto (:symbol)', 'total-adjustment' => 'Ajuste (:symbol)', 'grand-total' => 'Total General (:symbol)', 'discount-amount' => 'Monto del Descuento', 'tax-amount' => 'Monto del Impuesto', 'adjustment-amount' => 'Monto del Ajuste', 'product-name' => 'Nombre del Producto', 'action' => 'Acción', ], 'edit' => [ 'title' => 'Editar Cotización', 'save-btn' => 'Guardar Cotización', 'quote-info' => 'Información de la Cotización', 'quote-info-info' => 'Introduce la información básica de la cotización.', 'address-info' => 'Información de la Dirección', 'address-info-info' => 'Información sobre la dirección relacionada con la cotización.', 'quote-items' => 'Artículos de la Cotización', 'link-to-lead' => 'Vincular a cliente potencial', 'quote-item-info' => 'Agregar Solicitud de Producto para esta cotización.', 'quote-name' => 'Nombre de la Cotización', 'quantity' => 'Cantidad', 'price' => 'Precio', 'search-products' => 'Buscar Productos', 'discount' => 'Descuento', 'tax' => 'Impuesto', 'total' => 'Total', 'amount' => 'Monto', 'add-item' => '+ Agregar Artículo', 'sub-total' => 'Subtotal (:symbol)', 'total-discount' => 'Descuento (:symbol)', 'total-tax' => 'Impuesto (:symbol)', 'total-adjustment' => 'Ajuste (:symbol)', 'grand-total' => 'Total General (:symbol)', 'discount-amount' => 'Monto del Descuento', 'tax-amount' => 'Monto del Impuesto', 'adjustment-amount' => 'Monto del Ajuste', 'product-name' => 'Nombre del Producto', 'action' => 'Acción', ], ], 'contacts' => [ 'persons' => [ 'index' => [ 'title' => 'Personas', 'create-btn' => 'Crear Persona', 'create-success' => 'Persona creada con éxito.', 'update-success' => 'Persona actualizada con éxito.', 'all-delete-success' => 'Todas las personas seleccionadas fueron eliminadas exitosamente.', 'partial-delete-warning' => 'Algunas personas fueron eliminadas con éxito. Otras no se pudieron eliminar porque están vinculadas a clientes potenciales.', 'none-delete-warning' => 'Ninguna de las personas seleccionadas pudo ser eliminada porque están vinculadas a clientes potenciales.', 'no-selection' => 'No se seleccionaron personas para eliminar.', 'delete-failed' => 'No se pudieron eliminar las personas seleccionadas.', 'datagrid' => [ 'contact-numbers' => 'Números de Contacto', 'delete' => 'Eliminar', 'edit' => 'Editar', 'emails' => 'Correos Electrónicos', 'id' => 'ID', 'view' => 'Ver', 'name' => 'Nombre', 'organization-name' => 'Nombre de la Organización', ], ], 'view' => [ 'title' => ':name', 'about-person' => 'Sobre la Persona', 'about-organization' => 'Acerca de la organización', 'activities' => [ 'index' => [ 'all' => 'Todos', 'calls' => 'Llamadas', 'meetings' => 'Reuniones', 'lunches' => 'Almuerzos', 'files' => 'Archivos', 'quotes' => 'Cotizaciones', 'notes' => 'Notas', 'emails' => 'Correos Electrónicos', 'by-user' => 'Por :user', 'scheduled-on' => 'Programado para', 'location' => 'Ubicación', 'participants' => 'Participantes', 'mark-as-done' => 'Marcar como Hecho', 'delete' => 'Eliminar', 'edit' => 'Editar', ], 'actions' => [ 'mail' => [ 'btn' => 'Correo', 'title' => 'Redactar Correo', 'to' => 'Para', 'cc' => 'CC', 'bcc' => 'CCO', 'subject' => 'Asunto', 'send-btn' => 'Enviar', 'message' => 'Mensaje', ], 'file' => [ 'btn' => 'Archivo', 'title' => 'Agregar Archivo', 'title-control' => 'Título', 'name' => 'Nombre del Archivo', 'description' => 'Descripción', 'file' => 'Archivo', 'save-btn' => 'Guardar Archivo', ], 'note' => [ 'btn' => 'Nota', 'title' => 'Agregar Nota', 'comment' => 'Comentario', 'save-btn' => 'Guardar Nota', ], 'activity' => [ 'btn' => 'Actividad', 'title' => 'Agregar Actividad', 'title-control' => 'Título', 'description' => 'Descripción', 'schedule-from' => 'Programar Desde', 'schedule-to' => 'Programar Hasta', 'location' => 'Ubicación', 'call' => 'Llamada', 'meeting' => 'Reunión', 'lunch' => 'Almuerzo', 'save-btn' => 'Guardar Actividad', ], ], ], 'tags' => [ 'create-success' => 'Etiqueta creada con éxito.', 'destroy-success' => 'Etiqueta eliminada con éxito.', ], ], 'create' => [ 'title' => 'Crear Persona', 'save-btn' => 'Guardar Persona', ], 'edit' => [ 'title' => 'Editar Persona', 'save-btn' => 'Guardar Persona', ], ], 'organizations' => [ 'index' => [ 'title' => 'Organizaciones', 'create-btn' => 'Crear Organización', 'create-success' => 'Organización creada con éxito.', 'update-success' => 'Organización actualizada con éxito.', 'delete-success' => 'Organización eliminada con éxito.', 'delete-failed' => 'No se puede eliminar la organización.', 'datagrid' => [ 'delete' => 'Eliminar', 'edit' => 'Editar', 'id' => 'ID', 'name' => 'Nombre', 'persons-count' => 'Número de Personas', ], ], 'create' => [ 'title' => 'Crear Organización', 'save-btn' => 'Guardar Organización', ], 'edit' => [ 'title' => 'Editar Organización', 'save-btn' => 'Guardar Organización', ], ], ], 'products' => [ 'index' => [ 'title' => 'Productos', 'create-btn' => 'Crear Producto', 'create-success' => 'Producto creado con éxito.', 'update-success' => 'Producto actualizado con éxito.', 'delete-success' => 'Producto eliminado con éxito.', 'delete-failed' => 'No se puede eliminar el producto.', 'datagrid' => [ 'allocated' => 'Asignado', 'delete' => 'Eliminar', 'edit' => 'Editar', 'id' => 'ID', 'in-stock' => 'En Stock', 'name' => 'Nombre', 'on-hand' => 'Disponible', 'tag-name' => 'Nombre de etiqueta', 'price' => 'Precio', 'sku' => 'SKU', 'view' => 'Ver', ], ], 'create' => [ 'save-btn' => 'Guardar Productos', 'title' => 'Crear Productos', 'general' => 'General', 'price' => 'Precio', ], 'edit' => [ 'title' => 'Editar Productos', 'save-btn' => 'Guardar Productos', 'general' => 'General', 'price' => 'Precio', ], 'view' => [ 'sku' => 'SKU', 'all' => 'Todos', 'notes' => 'Notas', 'files' => 'Archivos', 'inventories' => 'Inventario', 'change-logs' => 'Registros de Cambios', 'attributes' => [ 'about-product' => 'Sobre el Producto', ], 'inventory' => [ 'source' => 'Fuente', 'in-stock' => 'En Stock', 'allocated' => 'Asignado', 'on-hand' => 'Disponible', 'actions' => 'Acciones', 'assign' => 'Asignar', 'add-source' => 'Agregar Fuente', 'location' => 'Ubicación', 'add-more' => 'Agregar Más', 'save' => 'Guardar', ], ], ], 'settings' => [ 'title' => 'Configuraciones', 'groups' => [ 'index' => [ 'create-btn' => 'Crear Grupo', 'title' => 'Grupos', 'create-success' => 'Grupo creado con éxito.', 'update-success' => 'Grupo actualizado con éxito.', 'destroy-success' => 'Grupo eliminado con éxito.', 'delete-failed' => 'No se puede eliminar el grupo.', 'delete-failed-associated-users' => 'No se puede eliminar el grupo, ya que está siendo utilizado por usuarios.', 'datagrid' => [ 'delete' => 'Eliminar', 'description' => 'Descripción', 'edit' => 'Editar', 'id' => 'ID', 'name' => 'Nombre', ], 'edit' => [ 'title' => 'Editar Grupo', ], 'create' => [ 'name' => 'Nombre', 'title' => 'Crear Grupo', 'description' => 'Descripción', 'save-btn' => 'Guardar Grupo', ], ], ], 'roles' => [ 'index' => [ 'being-used' => 'El rol no se puede eliminar, ya que está siendo utilizado por un usuario administrador.', 'create-btn' => 'Crear Roles', 'create-success' => 'Rol creado con éxito.', 'current-role-delete-error' => 'No se puede eliminar el rol asignado al usuario actual.', 'delete-failed' => 'No se puede eliminar el rol.', 'delete-success' => 'Rol eliminado con éxito.', 'last-delete-error' => 'Se requiere al menos un rol.', 'settings' => 'Configuraciones', 'title' => 'Roles', 'update-success' => 'Rol actualizado con éxito.', 'user-define-error' => 'No se puede eliminar el rol del sistema.', 'datagrid' => [ 'all' => 'Todos', 'custom' => 'Personalizado', 'delete' => 'Eliminar', 'description' => 'Descripción', 'edit' => 'Editar', 'id' => 'ID', 'name' => 'Nombre', 'permission-type' => 'Tipo de Permiso', ], ], 'create' => [ 'access-control' => 'Control de Acceso', 'all' => 'Todos', 'back-btn' => 'Volver', 'custom' => 'Personalizado', 'description' => 'Descripción', 'general' => 'General', 'name' => 'Nombre', 'permissions' => 'Permisos', 'save-btn' => 'Guardar Rol', 'title' => 'Crear Rol', ], 'edit' => [ 'access-control' => 'Control de Acceso', 'all' => 'Todos', 'back-btn' => 'Volver', 'custom' => 'Personalizado', 'description' => 'Descripción', 'general' => 'General', 'name' => 'Nombre', 'permissions' => 'Permisos', 'save-btn' => 'Guardar Rol', 'title' => 'Editar Rol', ], ], 'types' => [ 'index' => [ 'create-btn' => 'Crear Tipo', 'create-success' => 'Tipo creado con éxito.', 'delete-failed' => 'No se puede eliminar el tipo.', 'delete-success' => 'Tipo eliminado con éxito.', 'title' => 'Tipos', 'update-success' => 'Tipo actualizado con éxito.', 'datagrid' => [ 'delete' => 'Eliminar', 'description' => 'Descripción', 'edit' => 'Editar', 'id' => 'ID', 'name' => 'Nombre', ], 'create' => [ 'name' => 'Nombre', 'save-btn' => 'Guardar Tipo', 'title' => 'Crear Tipo', ], 'edit' => [ 'title' => 'Editar Tipo', ], ], ], 'sources' => [ 'index' => [ 'title' => 'Fuentes', 'create-btn' => 'Crear Fuente', 'create-success' => 'Fuente creada con éxito.', 'delete-failed' => 'No se puede eliminar la fuente.', 'delete-success' => 'Fuente eliminada con éxito.', 'update-success' => 'Fuente actualizada con éxito.', 'delete-failed-associated-leads' => 'No se puede eliminar la fuente porque está asociada a clientes potenciales existentes. Por favor, desvincúlelos o actualícelos antes de eliminar.', 'datagrid' => [ 'delete' => 'Eliminar', 'edit' => 'Editar', 'id' => 'ID', 'name' => 'Nombre', ], 'create' => [ 'name' => 'Nombre', 'save-btn' => 'Guardar fuente', 'title' => 'Crear fuente', ], 'edit' => [ 'title' => 'Editar fuente', ], ], ], 'workflows' => [ 'index' => [ 'title' => 'Flujos de Trabajo', 'create-btn' => 'Crear Flujo de Trabajo', 'create-success' => 'Flujo de trabajo creado con éxito.', 'update-success' => 'Flujo de trabajo actualizado con éxito.', 'delete-success' => 'Flujo de trabajo eliminado con éxito.', 'delete-failed' => 'No se puede eliminar el flujo de trabajo.', 'datagrid' => [ 'delete' => 'Eliminar', 'description' => 'Descripción', 'edit' => 'Editar', 'id' => 'ID', 'name' => 'Nombre', ], ], 'helpers' => [ 'update-related-leads' => 'Actualizar leads relacionados', 'send-email-to-sales-owner' => 'Enviar correo al propietario de ventas', 'send-email-to-participants' => 'Enviar correo a los participantes', 'add-webhook' => 'Agregar Webhook', 'update-lead' => 'Actualizar Lead', 'update-person' => 'Actualizar Persona', 'send-email-to-person' => 'Enviar correo a la persona', 'add-tag' => 'Agregar Etiqueta', 'add-note-as-activity' => 'Agregar Nota como Actividad', 'update-quote' => 'Actualizar cotización', ], 'create' => [ 'title' => 'Crear Flujo de Trabajo', 'event' => 'Evento', 'back-btn' => 'Volver', 'save-btn' => 'Guardar Flujo de Trabajo', 'name' => 'Nombre', 'basic-details' => 'Detalles Básicos', 'description' => 'Descripción', 'actions' => 'Acciones', 'basic-details-info' => 'Coloque la información básica del flujo de trabajo.', 'event-info' => 'Un evento desencadena, verifica, condiciones y realiza acciones predefinidas.', 'conditions' => 'Condiciones', 'conditions-info' => 'Las condiciones son reglas que verifican escenarios, desencadenadas en ocasiones específicas.', 'actions-info' => 'Una acción no solo reduce la carga de trabajo, sino que también facilita la automatización de CRM.', 'value' => 'Valor', 'condition-type' => 'Tipo de Condición', 'all-condition-are-true' => 'Todas las condiciones son verdaderas', 'any-condition-are-true' => 'Cualquiera de las condiciones es verdadera', 'add-condition' => 'Agregar Condición', 'add-action' => 'Agregar Acción', 'yes' => 'Sí', 'no' => 'No', 'email' => 'Correo Electrónico', 'is-equal-to' => 'Es igual a', 'is-not-equal-to' => 'No es igual a', 'equals-or-greater-than' => 'Es igual o mayor que', 'equals-or-less-than' => 'Es igual o menor que', 'greater-than' => 'Mayor que', 'less-than' => 'Menor que', 'type' => 'Tipo', 'contain' => 'Contiene', 'contains' => 'Contiene', 'does-not-contain' => 'No contiene', ], 'edit' => [ 'title' => 'Editar Flujo de Trabajo', 'event' => 'Evento', 'back-btn' => 'Volver', 'save-btn' => 'Guardar Flujo de Trabajo', 'name' => 'Nombre', 'basic-details' => 'Detalles Básicos', 'description' => 'Descripción', 'actions' => 'Acciones', 'type' => 'Tipo', 'basic-details-info' => 'Coloque la información básica del flujo de trabajo.', 'event-info' => 'Un evento desencadena, verifica, condiciones y realiza acciones predefinidas.', 'conditions' => 'Condiciones', 'conditions-info' => 'Las condiciones son reglas que verifican escenarios, desencadenadas en ocasiones específicas.', 'actions-info' => 'Una acción no solo reduce la carga de trabajo, sino que también facilita la automatización de CRM.', 'value' => 'Valor', 'condition-type' => 'Tipo de Condición', 'all-condition-are-true' => 'Todas las condiciones son verdaderas', 'any-condition-are-true' => 'Cualquiera de las condiciones es verdadera', 'add-condition' => 'Agregar Condición', 'add-action' => 'Agregar Acción', 'yes' => 'Sí', 'no' => 'No', 'email' => 'Correo Electrónico', 'is-equal-to' => 'Es igual a', 'is-not-equal-to' => 'No es igual a', 'equals-or-greater-than' => 'Es igual o mayor que', 'equals-or-less-than' => 'Es igual o menor que', 'greater-than' => 'Mayor que', 'less-than' => 'Menor que', 'contain' => 'Contiene', 'contains' => 'Contiene', 'does-not-contain' => 'No contiene', ], ], 'webforms' => [ 'index' => [ 'title' => 'Webforms', 'create-btn' => 'Crear Webform', 'create-success' => 'Webform creado con éxito.', 'update-success' => 'Webform actualizado con éxito.', 'delete-success' => 'Webform eliminado con éxito.', 'delete-failed' => 'No se puede eliminar el Webform.', 'datagrid' => [ 'id' => 'ID', 'title' => 'Título', 'edit' => 'Editar', 'delete' => 'Eliminar', ], ], 'create' => [ 'title' => 'Crear formulario web', 'add-attribute-btn' => 'Agregar Botón de Atributo', 'attribute-label-color' => 'Color de Etiqueta del Atributo', 'attributes' => 'Atributos', 'attributes-info' => 'Agregue atributos personalizados al formulario.', 'background-color' => 'Color de Fondo', 'create-lead' => 'Crear Lead', 'customize-webform' => 'Personalizar Webform', 'customize-webform-info' => 'Personalice su formulario web con los colores de los elementos de su elección.', 'description' => 'Descripción', 'display-custom-message' => 'Mostrar mensaje personalizado', 'form-background-color' => 'Color de Fondo del Formulario', 'form-submit-btn-color' => 'Color del Botón de Enviar del Formulario', 'form-submit-button-color' => 'Color del Botón de Enviar del Formulario', 'form-title-color' => 'Color del Título del Formulario', 'general' => 'General', 'leads' => 'Leads', 'person' => 'Persona', 'save-btn' => 'Guardar Webform', 'submit-button-label' => 'Etiqueta del Botón de Enviar', 'submit-success-action' => 'Acción en caso de éxito de envío', 'redirect-to-url' => 'Redirigir a la URL', 'choose-value' => 'Elige un valor', 'select-file' => 'Seleccionar archivo', 'select-image' => 'Seleccionar imagen', 'enter-value' => 'Introducir valor', ], 'edit' => [ 'add-attribute-btn' => 'Agregar Botón de Atributo', 'attribute-label-color' => 'Color de Etiqueta del Atributo', 'attributes' => 'Atributos', 'attributes-info' => 'Agregue atributos personalizados al formulario.', 'background-color' => 'Color de Fondo', 'choose-value' => 'Elige un valor', 'code-snippet' => 'Fragmento de Código', 'copied' => 'Copiado', 'copy' => 'Copiar', 'create-lead' => 'Crear Lead', 'customize-webform' => 'Personalizar Webform', 'customize-webform-info' => 'Personalice su formulario web con los colores de los elementos de su elección.', 'description' => 'Descripción', 'display-custom-message' => 'Mostrar mensaje personalizado', 'embed' => 'Incrustar', 'enter-value' => 'Introducir valor', 'form-background-color' => 'Color de Fondo del Formulario', 'form-submit-btn-color' => 'Color del Botón de Enviar del Formulario', 'form-submit-button-color' => 'Color del Botón de Enviar del Formulario', 'form-title-color' => 'Color del Título del Formulario', 'general' => 'General', 'leads' => 'Leads', 'person' => 'Persona', 'preview' => 'Vista Previa', 'public-url' => 'URL Pública', 'redirect-to-url' => 'Redirigir a la URL', 'save-btn' => 'Guardar Webform', 'select-file' => 'Seleccionar archivo', 'select-image' => 'Seleccionar imagen', 'submit-button-label' => 'Etiqueta del Botón de Enviar', 'submit-success-action' => 'Acción en caso de éxito de envío', 'title' => 'Editar formulario web', ], ], 'email-template' => [ 'index' => [ 'create-btn' => 'Crear Plantilla de Correo Electrónico', 'title' => 'Plantillas de Correo Electrónico', 'create-success' => 'Plantilla de Correo Electrónico creada exitosamente.', 'update-success' => 'Plantilla de Correo Electrónico actualizada exitosamente.', 'delete-success' => 'Plantilla de Correo Electrónico eliminada exitosamente.', 'delete-failed' => 'No se puede eliminar la Plantilla de Correo Electrónico.', 'datagrid' => [ 'delete' => 'Eliminar', 'edit' => 'Editar', 'id' => 'ID', 'name' => 'Nombre', 'subject' => 'Asunto', ], ], 'create' => [ 'title' => 'Crear Plantilla de Correo Electrónico', 'save-btn' => 'Guardar Plantilla de Correo Electrónico', 'email-template' => 'Plantilla de Correo Electrónico', 'subject' => 'Asunto', 'content' => 'Contenido', 'subject-placeholders' => 'Marcadores de Posición del Asunto', 'general' => 'General', 'name' => 'Nombre', ], 'edit' => [ 'title' => 'Editar Plantilla de Correo Electrónico', 'save-btn' => 'Guardar Plantilla de Correo Electrónico', 'email-template' => 'Plantilla de Correo Electrónico', 'subject' => 'Asunto', 'content' => 'Contenido', 'subject-placeholders' => 'Marcadores de Posición del Asunto', 'general' => 'General', 'name' => 'Nombre', ], ], 'marketing' => [ 'events' => [ 'index' => [ 'create-btn' => 'Crear Evento', 'title' => 'Eventos', 'create-success' => 'Evento creado con éxito.', 'update-success' => 'Evento actualizado con éxito.', 'delete-success' => 'Evento eliminado con éxito.', 'delete-failed' => 'No se puede eliminar el evento.', 'mass-delete-success' => 'Eventos eliminados con éxito', 'delete-failed-associated-campaigns' => 'No se puede eliminar el evento porque está asociado a campañas existentes. Por favor, desvincúlelas o actualícelas antes de eliminar.', 'datagrid' => [ 'delete' => 'Eliminar', 'edit' => 'Editar', 'id' => 'ID', 'name' => 'Nombre', 'description' => 'Descripción', 'date' => 'Fecha', ], 'create' => [ 'title' => 'Crear Evento', 'name' => 'Nombre', 'date' => 'Fecha', 'description' => 'Descripción', 'save-btn' => 'Guardar Evento', ], 'edit' => [ 'title' => 'Editar Evento', ], ], ], 'campaigns' => [ 'index' => [ 'create-btn' => 'Crear Campaña', 'title' => 'Campañas', 'create-success' => 'Campaña creada con éxito.', 'update-success' => 'Campaña actualizada con éxito.', 'delete-success' => 'Campaña eliminada con éxito.', 'delete-failed' => 'No se puede eliminar la campaña.', 'mass-delete-success' => 'Campañas eliminadas con éxito.', 'datagrid' => [ 'id' => 'ID', 'name' => 'Nombre', 'subject' => 'Asunto', 'status' => 'Estado', 'active' => 'Activo', 'inactive' => 'Inactivo', 'edit' => 'Editar', 'delete' => 'Eliminar', ], 'create' => [ 'title' => 'Crear Campaña', 'name' => 'Nombre', 'type' => 'Tipo', 'subject' => 'Asunto', 'event' => 'Evento', 'email-template' => 'Plantilla de Correo', 'status' => 'Estado', ], 'edit' => [ 'title' => 'Editar Campaña', ], ], ], ], 'tags' => [ 'index' => [ 'create-btn' => 'Crear Etiqueta', 'title' => 'Etiquetas', 'create-success' => 'Etiqueta creada exitosamente.', 'update-success' => 'Etiqueta actualizada exitosamente.', 'delete-success' => 'Etiqueta eliminada exitosamente.', 'delete-failed' => 'No se puede eliminar la Etiqueta.', 'datagrid' => [ 'delete' => 'Eliminar', 'edit' => 'Editar', 'id' => 'ID', 'name' => 'Nombre', 'users' => 'Usuarios', 'created-at' => 'Creado En', ], 'create' => [ 'name' => 'Nombre', 'save-btn' => 'Guardar Etiqueta', 'title' => 'Crear Etiqueta', 'color' => 'Color', ], 'edit' => [ 'title' => 'Editar Etiqueta', ], ], ], 'users' => [ 'index' => [ 'create-btn' => 'Crear Usuario', 'create-success' => 'Usuario creado exitosamente.', 'delete-failed' => 'No se puede eliminar el Usuario.', 'delete-success' => 'Usuario eliminado exitosamente.', 'last-delete-error' => 'Se requiere al menos un usuario.', 'mass-delete-failed' => 'No se pueden eliminar los Usuarios.', 'mass-delete-success' => 'Usuarios eliminados exitosamente.', 'mass-update-failed' => 'No se pueden actualizar los Usuarios.', 'mass-update-success' => 'Usuarios actualizados exitosamente.', 'title' => 'Usuarios', 'update-success' => 'Usuario actualizado exitosamente.', 'user-define-error' => 'No se puede eliminar el usuario del sistema.', 'active' => 'Activo', 'inactive' => 'Inactivo', 'datagrid' => [ 'active' => 'Activo', 'created-at' => 'Creado En', 'delete' => 'Eliminar', 'edit' => 'Editar', 'email' => 'Correo Electrónico', 'id' => 'ID', 'inactive' => 'Inactivo', 'name' => 'Nombre', 'status' => 'Estado', 'update-status' => 'Actualizar Estado', 'users' => 'Usuarios', ], 'create' => [ 'confirm-password' => 'Confirmar Contraseña', 'email' => 'Correo Electrónico', 'general' => 'General', 'global' => 'Global', 'group' => 'Grupo', 'individual' => 'Individual', 'name' => 'Nombre', 'password' => 'Contraseña', 'permission' => 'Permiso', 'role' => 'Rol', 'save-btn' => 'Guardar Usuario', 'status' => 'Estado', 'title' => 'Crear Usuario', 'view-permission' => 'Ver Permiso', 'select-at-lest-one-group' => 'Select at least one group', ], 'edit' => [ 'title' => 'Editar Usuario', ], ], ], 'pipelines' => [ 'index' => [ 'title' => 'Canales', 'create-btn' => 'Crear Canal', 'create-success' => 'Canal creado exitosamente.', 'update-success' => 'Canal actualizado exitosamente.', 'default-required' => 'Se requiere al menos una canalización predeterminada.', 'delete-success' => 'Canal eliminado exitosamente.', 'delete-failed' => 'No se puede eliminar el Canal.', 'default-delete-error' => 'No se puede eliminar el canal predeterminado.', 'datagrid' => [ 'delete' => 'Eliminar', 'edit' => 'Editar', 'id' => 'ID', 'is-default' => 'Es Predeterminado', 'name' => 'Nombre', 'no' => 'No', 'rotten-days' => 'Días de Pudrición', 'yes' => 'Sí', ], ], 'create' => [ 'title' => 'Crear Canal', 'save-btn' => 'Guardar Canal', 'name' => 'Nombre', 'rotten-days' => 'Días de Pudrición', 'mark-as-default' => 'Marcar como Predeterminado', 'general' => 'General', 'probability' => 'Probabilidad (%)', 'new-stage' => 'Nuevo', 'won-stage' => 'Ganado', 'lost-stage' => 'Perdido', 'stage-btn' => 'Añadir Etapa', 'stages' => 'Etapas', 'duplicate-name' => 'El campo "Nombre" no puede ser duplicado', 'delete-stage' => 'Eliminar Etapa', 'add-new-stages' => 'Añadir Nuevas Etapas', 'add-stage-info' => 'Añadir nueva etapa para tu Canal', 'newly-added' => 'Añadido Recientemente', 'stage-delete-success' => 'Etapa Eliminada Exitosamente', ], 'edit' => [ 'title' => 'Editar Canal', 'save-btn' => 'Guardar Canal', 'name' => 'Nombre', 'rotten-days' => 'Días de Pudrición', 'mark-as-default' => 'Marcar como Predeterminado', 'general' => 'General', 'probability' => 'Probabilidad (%)', 'new-stage' => 'Nuevo', 'won-stage' => 'Ganado', 'lost-stage' => 'Perdido', 'stage-btn' => 'Añadir Etapa', 'stages' => 'Etapas', 'duplicate-name' => 'El campo "Nombre" no puede ser duplicado', 'delete-stage' => 'Eliminar Etapa', 'add-new-stages' => 'Añadir Nuevas Etapas', 'add-stage-info' => 'Añadir nueva etapa para tu Canal', 'stage-delete-success' => 'Etapa Eliminada Exitosamente', ], ], 'webhooks' => [ 'index' => [ 'title' => 'Webhooks', 'create-btn' => 'Crear Webhook', 'create-success' => 'Webhook creado exitosamente.', 'update-success' => 'Webhook actualizado exitosamente.', 'delete-success' => 'Webhook eliminado exitosamente.', 'delete-failed' => 'No se puede eliminar el Webhook.', 'datagrid' => [ 'id' => 'ID', 'delete' => 'Eliminar', 'edit' => 'Editar', 'name' => 'Nombre', 'entity-type' => 'Tipo de Entidad', 'end-point' => 'Punto Final', ], ], 'create' => [ 'title' => 'Crear Webhook', 'save-btn' => 'Guardar Webhook', 'info' => 'Ingrese los detalles del webhook', 'url-and-parameters' => 'URL Y Parámetros', 'method' => 'Método', 'post' => 'Post', 'put' => 'Put', 'url-endpoint' => 'Punto Final de URL', 'parameters' => 'Parámetros', 'add-new-parameter' => 'Añadir Nuevo Parámetro', 'url-preview' => 'Vista Previa de URL:', 'headers' => 'Encabezados', 'add-new-header' => 'Añadir Nuevo Encabezado', 'body' => 'Cuerpo', 'default' => 'Predeterminado', 'x-www-form-urlencoded' => 'x-www-form-urlencoded', 'key-and-value' => 'Clave y Valor', 'add-new-payload' => 'Añadir nueva carga', 'raw' => 'Crudo', 'general' => 'General', 'name' => 'Nombre', 'entity-type' => 'Tipo de Entidad', 'insert-placeholder' => 'Insertar Marcador de Posición', 'description' => 'Descripción', 'json' => 'Json', 'text' => 'Texto', ], 'edit' => [ 'title' => 'Editar Webhook', 'edit-btn' => 'Guardar Webhook', 'save-btn' => 'Guardar Webhook', 'info' => 'Ingrese los detalles del webhook', 'url-and-parameters' => 'URL Y Parámetros', 'method' => 'Método', 'post' => 'Post', 'put' => 'Put', 'url-endpoint' => 'Punto Final de URL', 'parameters' => 'Parámetros', 'add-new-parameter' => 'Añadir Nuevo Parámetro', 'url-preview' => 'Vista Previa de URL:', 'headers' => 'Encabezados', 'add-new-header' => 'Añadir Nuevo Encabezado', 'body' => 'Cuerpo', 'default' => 'Predeterminado', 'x-www-form-urlencoded' => 'x-www-form-urlencoded', 'key-and-value' => 'Clave y Valor', 'add-new-payload' => 'Añadir nueva carga', 'raw' => 'Crudo', 'general' => 'General', 'name' => 'Nombre', 'entity-type' => 'Tipo de Entidad', 'insert-placeholder' => 'Insertar Marcador de Posición', 'description' => 'Descripción', 'json' => 'Json', 'text' => 'Texto', ], ], 'warehouses' => [ 'index' => [ 'title' => 'Almacenes', 'create-btn' => 'Crear Almacén', 'create-success' => 'Almacén creado con éxito.', 'name-exists' => 'El nombre del almacén ya existe.', 'update-success' => 'Almacén actualizado con éxito.', 'delete-success' => 'Almacén eliminado con éxito.', 'delete-failed' => 'No se puede eliminar el almacén.', 'datagrid' => [ 'id' => 'ID', 'name' => 'Nombre', 'contact-name' => 'Nombre del Contacto', 'delete' => 'Eliminar', 'edit' => 'Editar', 'view' => 'Ver', 'created-at' => 'Creado En', 'products' => 'Productos', 'contact-emails' => 'Correos Electrónicos de Contacto', 'contact-numbers' => 'Números de Teléfono de Contacto', ], ], 'create' => [ 'title' => 'Crear Almacén', 'save-btn' => 'Guardar Almacén', 'contact-info' => 'Información de Contacto', ], 'edit' => [ 'title' => 'Editar Almacén', 'save-btn' => 'Guardar Almacén', 'contact-info' => 'Información de Contacto', ], 'view' => [ 'all' => 'Todos', 'notes' => 'Notas', 'files' => 'Archivos', 'location' => 'Ubicación', 'change-logs' => 'Registros de Cambios', 'locations' => [ 'action' => 'Acción', 'add-location' => 'Agregar Ubicación', 'create-success' => 'Ubicación creada con éxito.', 'delete' => 'Eliminar', 'delete-failed' => 'No se puede eliminar la ubicación.', 'delete-success' => 'Ubicación eliminada con éxito.', 'name' => 'Nombre', 'save-btn' => 'Guardar', ], 'general-information' => [ 'title' => 'Información General', ], 'contact-information' => [ 'title' => 'Información de Contacto', ], ], ], 'attributes' => [ 'index' => [ 'title' => 'Atributos', 'create-btn' => 'Crear Atributo', 'create-success' => 'Atributo creados con éxito.', 'update-success' => 'Atributo actualizados con éxito.', 'delete-success' => 'Atributo eliminados con éxito.', 'delete-failed' => 'No se pueden eliminar los atributo.', 'user-define-error' => 'No se puede eliminar el atributo del sistema.', 'mass-delete-failed' => 'No se pueden eliminar los atributos del sistema.', 'datagrid' => [ 'yes' => 'Sí', 'no' => 'No', 'id' => 'ID', 'code' => 'Código', 'name' => 'Nombre', 'entity-type' => 'Tipo de Entidad', 'type' => 'Tipo', 'is-default' => 'Es Predeterminado', 'edit' => 'Editar', 'delete' => 'Eliminar', 'entity-types' => [ 'leads' => 'Clientes potenciales', 'organizations' => 'Organizaciones', 'persons' => 'Personas', 'products' => 'Productos', 'quotes' => 'Cotizaciones', 'warehouses' => 'Almacenes', ], 'types' => [ 'text' => 'Texto', 'textarea' => 'Área de texto', 'price' => 'Precio', 'boolean' => 'Booleano', 'select' => 'Seleccionar', 'multiselect' => 'Selección múltiple', 'checkbox' => 'Casilla de verificación', 'email' => 'Correo electrónico', 'address' => 'Dirección', 'phone' => 'Teléfono', 'lookup' => 'Búsqueda', 'datetime' => 'Fecha y hora', 'date' => 'Fecha', 'image' => 'Imagen', 'file' => 'Archivo', ], ], ], 'create' => [ 'title' => 'Crear Atributo', 'save-btn' => 'Guardar Atributo', 'code' => 'Código', 'name' => 'Nombre', 'entity-type' => 'Tipo de Entidad', 'type' => 'Tipo', 'validations' => 'Validaciones', 'is-required' => 'Es Requerido', 'input-validation' => 'Validación de Entrada', 'is-unique' => 'Es Único', 'labels' => 'Etiquetas', 'general' => 'General', 'numeric' => 'Numérico', 'decimal' => 'Decimal', 'url' => 'Url', 'options' => 'Opciones', 'option-type' => 'Tipo de Opción', 'lookup-type' => 'Tipo de Búsqueda', 'add-option' => 'Agregar Opción', 'save-option' => 'Guardar Opción', 'option-name' => 'Nombre de Opción', 'add-attribute-options' => 'Agregar Opciones de Atributo', 'text' => 'Texto', 'textarea' => 'Área de Texto', 'price' => 'Precio', 'boolean' => 'Booleano', 'select' => 'Seleccionar', 'multiselect' => 'Selección Múltiple', 'email' => 'Correo Electrónico', 'address' => 'Dirección', 'phone' => 'Teléfono', 'datetime' => 'Fecha y Hora', 'date' => 'Fecha', 'image' => 'Imagen', 'file' => 'Archivo', 'lookup' => 'Búsqueda', 'entity_type' => 'Tipo de Entidad', 'checkbox' => 'Casilla de Verificación', 'is_required' => 'Es Requerido', 'is_unique' => 'Es Único', 'actions' => 'Acciones', ], 'edit' => [ 'actions' => 'Acciones', 'add-attribute-options' => 'Agregar Opciones de Atributo', 'add-option' => 'Agregar Opción', 'address' => 'Dirección', 'boolean' => 'Booleano', 'checkbox' => 'Casilla de Verificación', 'code' => 'Código', 'date' => 'Fecha', 'datetime' => 'Fecha y Hora', 'decimal' => 'Decimal', 'email' => 'Correo Electrónico', 'entity-type' => 'Tipo de Entidad', 'entity_type' => 'Tipo de Entidad', 'file' => 'Archivo', 'general' => 'General', 'image' => 'Imagen', 'input-validation' => 'Validación de Entrada', 'is-required' => 'Es Requerido', 'is-unique' => 'Es Único', 'is_required' => 'Es Requerido', 'is_unique' => 'Es Único', 'labels' => 'Etiquetas', 'lookup' => 'Búsqueda', 'lookup-type' => 'Tipo de Búsqueda', 'multiselect' => 'Selección Múltiple', 'name' => 'Nombre', 'numeric' => 'Numérico', 'option-deleted' => 'Attribute Option is deleted successfully', 'option-name' => 'Nombre de Opción', 'option-type' => 'Tipo de Opción', 'options' => 'Opciones', 'phone' => 'Teléfono', 'price' => 'Precio', 'save-btn' => 'Guardar Atributo', 'save-option' => 'Guardar Opción', 'select' => 'Seleccionar', 'text' => 'Texto', 'textarea' => 'Área de Texto', 'title' => 'Editar Atributo', 'type' => 'Tipo', 'url' => 'Url', 'validations' => 'Validaciones', ], ], 'data-transfer' => [ 'imports' => [ 'create' => [ 'action' => 'Acción', 'allowed-errors' => 'Errores Permitidos', 'back-btn' => 'Atrás', 'create-update' => 'Crear/Actualizar', 'delete' => 'Eliminar', 'download-sample' => 'Descargar Muestra', 'field-separator' => 'Separador de Campos', 'file' => 'Archivo', 'general' => 'General', 'images-directory' => 'Ruta del Directorio de Imágenes', 'process-in-queue' => 'Procesar en Cola', 'results' => 'Resultados', 'save-btn' => 'Guardar Importación', 'settings' => 'Configuraciones', 'skip-errors' => 'Omitir Errores', 'stop-on-errors' => 'Detener en Errores', 'title' => 'Crear Importación', 'type' => 'Tipo', 'validation-strategy' => 'Estrategia de Validación', ], 'edit' => [ 'action' => 'Acción', 'allowed-errors' => 'Errores Permitidos', 'back-btn' => 'Atrás', 'create-update' => 'Crear/Actualizar', 'delete' => 'Eliminar', 'download-sample' => 'Descargar Muestra', 'field-separator' => 'Separador de Campos', 'file' => 'Archivo', 'general' => 'General', 'images-directory' => 'Ruta del Directorio de Imágenes', 'process-in-queue' => 'Procesar en Cola', 'results' => 'Resultados', 'save-btn' => 'Guardar Importación', 'settings' => 'Configuraciones', 'skip-errors' => 'Omitir Errores', 'stop-on-errors' => 'Detener en Errores', 'title' => 'Editar Importación', 'type' => 'Tipo', 'validation-strategy' => 'Estrategia de Validación', ], 'index' => [ 'button-title' => 'Crear Importación', 'title' => 'Importaciones', 'datagrid' => [ 'actions' => 'Acciones', 'completed-at' => 'Completado en', 'created' => 'Creado', 'delete' => 'Eliminar', 'deleted' => 'Eliminado', 'edit' => 'Editar', 'error-file' => 'Archivo de Errores', 'id' => 'ID', 'started-at' => 'Iniciado en', 'state' => 'Estado', 'summary' => 'Resumen', 'type' => 'Tipo', 'updated' => 'Actualizado', 'uploaded-file' => 'Archivo Subido', ], ], 'import' => [ 'back-btn' => 'Atrás', 'completed-batches' => 'Total de Lotes Completados:', 'download-error-report' => 'Descargar Informe Completo', 'edit-btn' => 'Editar', 'imported-info' => '¡Felicidades! Tu importación fue exitosa.', 'importing-info' => 'Importación en Proceso', 'indexing-info' => 'Indexación de Recursos (Precios, Inventario y Elastic Search) en Progreso', 'linking-info' => 'Vinculación de Recursos en Progreso', 'progress' => 'Progreso:', 'title' => 'Importación', 'total-batches' => 'Total de Lotes:', 'total-created' => 'Total de Registros Creados:', 'total-deleted' => 'Total de Registros Eliminados:', 'total-errors' => 'Total de Errores:', 'total-invalid-rows' => 'Total de Filas Inválidas:', 'total-rows-processed' => 'Total de Filas Procesadas:', 'total-updated' => 'Total de Registros Actualizados:', 'validate' => 'Validar', 'validate-info' => 'Haz clic en Validar Datos para comprobar tu importación.', 'validating-info' => 'La lectura y validación de los datos ha comenzado', 'validation-failed-info' => 'Tu importación no es válida. Por favor, corrige los siguientes errores e intenta de nuevo.', 'validation-success-info' => 'Tu importación es válida. Haz clic en Importar para iniciar el proceso de importación.', ], 'create-success' => 'Importación creada exitosamente.', 'delete-failed' => 'La eliminación de la importación falló inesperadamente.', 'delete-success' => 'Importación eliminada exitosamente.', 'not-valid' => 'La importación no es válida', 'nothing-to-import' => 'No hay recursos para importar.', 'setup-queue-error' => 'Por favor, cambia tu controlador de cola a "database" o "redis" para iniciar el proceso de importación.', 'update-success' => 'Importación actualizada exitosamente.', ], ], ], 'activities' => [ 'index' => [ 'title' => 'Actividades', 'datagrid' => [ 'comment' => 'Comentario', 'created_at' => 'Creado En', 'created_by' => 'Creado Por', 'edit' => 'Editar', 'id' => 'ID', 'done' => 'Completado', 'not-done' => 'No Completado', 'lead' => 'Oportunidad', 'mass-delete' => 'Eliminación Masiva', 'mass-update' => 'Actualización Masiva', 'schedule-from' => 'Programar Desde', 'schedule-to' => 'Programar Hasta', 'schedule_from' => 'Programar Desde', 'schedule_to' => 'Programar Hasta', 'title' => 'Título', 'is_done' => 'Completado', 'type' => 'Tipo', 'update' => 'Actualizar', 'call' => 'Llamada', 'meeting' => 'Reunión', 'lunch' => 'Almuerzo', ], ], 'edit' => [ 'title' => 'Editar Actividad', 'back-btn' => 'Volver', 'save-btn' => 'Guardar Actividad', 'type' => 'Tipo de Actividad', 'call' => 'Llamada', 'meeting' => 'Reunión', 'lunch' => 'Almuerzo', 'schedule_to' => 'Programar Hasta', 'schedule_from' => 'Programar Desde', 'location' => 'Ubicación', 'comment' => 'Comentario', 'lead' => 'Oportunidad', 'participants' => 'Participantes', 'general' => 'General', 'persons' => 'Personas', 'no-result-found' => 'No se encontraron registros.', 'users' => 'Usuarios', ], 'updated' => 'Actualizado :attribute', 'created' => 'Creado', 'duration-overlapping' => 'Los participantes tienen otra reunión en este momento. ¿Deseas continuar?', 'create-success' => 'Actividad creada con éxito.', 'update-success' => 'Actividad actualizada con éxito.', 'overlapping-error' => 'Los participantes tienen otra reunión en este momento.', 'destroy-success' => 'Actividad eliminada con éxito.', 'delete-failed' => 'No se puede eliminar la actividad.', 'mass-update-success' => 'Actividades actualizadas con éxito.', 'mass-destroy-success' => 'Actividades eliminadas con éxito.', 'mass-delete-failed' => 'No se pueden eliminar las actividades.', ], 'mail' => [ 'index' => [ 'compose' => 'Redactar', 'draft' => 'Borrador', 'inbox' => 'Bandeja de entrada', 'outbox' => 'Bandeja de salida', 'sent' => 'Enviados', 'trash' => 'Papelera', 'compose-mail-btn' => 'Redactar Correo', 'btn' => 'Correo', 'mail' => [ 'title' => 'Redactar Correo', 'to' => 'Para', 'enter-emails' => 'Presiona enter para añadir correos', 'cc' => 'CC', 'bcc' => 'BCC', 'subject' => 'Asunto', 'send-btn' => 'Enviar', 'message' => 'Mensaje', 'draft' => 'Borrador', ], 'datagrid' => [ 'id' => 'ID', 'from' => 'De', 'to' => 'Para', 'subject' => 'Asunto', 'tags' => 'Etiquetas', 'content' => 'Contenido', 'attachments' => 'Archivos adjuntos', 'date' => 'Fecha', 'move-to-inbox' => 'Mover a la bandeja de entrada', 'move-to-trash' => 'Movido a la papelera', 'edit' => 'Editar', 'view' => 'Ver', 'delete' => 'Eliminar', ], ], 'create-success' => 'Correo enviado con éxito.', 'update-success' => 'Correo actualizado con éxito.', 'mass-update-success' => 'Correos actualizados con éxito.', 'delete-success' => 'Correo eliminado con éxito.', 'delete-failed' => 'No se puede eliminar el correo.', 'invalid-route' => 'Ruta inválida para el correo.', 'unauthorized' => 'Esta acción no está autorizada.', 'view' => [ 'title' => 'Correos', 'subject' => ':subject', 'link-mail' => 'Enlace de Correo', 'to' => 'Para', 'cc' => 'CC', 'bcc' => 'BCC', 'reply' => 'Responder', 'reply-all' => 'Responder a Todos', 'forward' => 'Reenviar', 'delete' => 'Eliminar', 'enter-mails' => 'Ingresar id de correo', 'rotten-days' => 'El lead está en estado crítico por :days días', 'search-an-existing-lead' => 'Buscar un lead existente', 'search-an-existing-contact' => 'Buscar un contacto existente', 'message' => 'Mensaje', 'add-attachments' => 'Añadir Archivos Adjuntos', 'discard' => 'Descartar', 'send' => 'Enviar', 'no-result-found' => 'No se encontraron resultados', 'add-new-contact' => 'Añadir Nuevo Contacto', 'description' => 'Descripción', 'search' => 'Buscar...', 'add-new-lead' => 'Añadir Nuevo Lead', 'create-new-contact' => 'Crear Nuevo Contacto', 'save-contact' => 'Guardar Contacto', 'create-lead' => 'Crear Lead', 'linked-contact' => 'Contacto Vinculado', 'link-to-contact' => 'Vincular a Contacto', 'link-to-lead' => 'Vincular a Lead', 'linked-lead' => 'Lead Vinculado', 'lead-details' => 'Detalles del Lead', 'contact-person' => 'Persona de Contacto', 'product' => 'Producto', 'tags' => [ 'create-success' => 'Etiqueta creada con éxito.', 'destroy-success' => 'Etiqueta eliminada con éxito.', ], ], ], 'common' => [ 'custom-attributes' => [ 'add-more' => 'Añadir Más', 'address' => 'Dirección', 'city' => 'Ciudad', 'contact' => 'Números de Contacto', 'country' => 'País', 'email' => 'Correo Electrónico', 'home' => 'Hogar', 'postcode' => 'Código Postal', 'save' => 'Guardar', 'select' => 'Seleccionar', 'select-country' => 'Seleccionar País', 'select-state' => 'Seleccionar Estado', 'state' => 'Estado', 'update-contact-title' => 'Actualizar Números de Contacto', 'update-emails-title' => 'Actualizar Correos Electrónicos de Contacto', 'work' => 'Trabajo', ], ], 'leads' => [ 'create-success' => 'Lead creado exitosamente.', 'update-success' => 'Lead actualizado exitosamente.', 'update-failed' => 'No se pueden eliminar los clientes potenciales.', 'destroy-success' => 'Lead eliminado exitosamente.', 'destroy-failed' => 'No se puede eliminar el lead.', 'file' => [ 'data-not-found' => 'Datos no encontrados.', 'empty-content' => 'El contenido del PDF está vacío o no se pudo extraer.', 'failed-extract' => 'No se pudo extraer el texto del archivo.', 'insufficient-info' => 'Debido a datos insuficientes, no podemos procesar su solicitud en este momento.', 'invalid-base64' => 'Formato base64 inválido.', 'invalid-format' => 'Formato JSON inválido.', 'invalid-response' => 'Formato de respuesta de IA inválido.', 'missing-api-key' => 'Falta la clave API o la configuración del modelo.', 'not-found' => 'Archivo no encontrado.', 'recursive-call' => 'Se detectó una llamada recursiva.', 'text-generation-failed' => 'La extracción de texto falló. El archivo podría estar vacío o ilegible.', ], 'index' => [ 'title' => 'Leads', 'create-btn' => 'Crear Lead', 'datagrid' => [ 'id' => 'ID', 'sales-person' => 'Persona de Ventas', 'subject' => 'Asunto', 'source' => 'Fuente', 'lead-value' => 'Valor del Lead', 'lead-type' => 'Tipo de cliente potencial', 'tag-name' => 'Nombre de la etiqueta', 'contact-person' => 'Persona de Contacto', 'stage' => 'Etapa', 'rotten-lead' => 'Lead Podrido', 'date-to' => 'Fecha Hasta', 'created-at' => 'Creado En', 'no' => 'No', 'yes' => 'Sí', 'delete' => 'Eliminar', 'mass-delete' => 'Eliminar en Masa', 'mass-update' => 'Actualizar en Masa', ], 'kanban' => [ 'rotten-days' => 'El lead está podrido desde hace :days días', 'empty-list' => 'Tu lista de leads está vacía', 'empty-list-description' => 'Crea un lead para organizar tus objetivos.', 'create-lead-btn' => 'Crear Lead', 'columns' => [ 'contact-person' => 'Persona de Contacto', 'id' => 'ID', 'lead-type' => 'Tipo de Lead', 'lead-value' => 'Valor del Lead', 'sales-person' => 'Persona de Ventas', 'source' => 'Fuente', 'title' => 'Título', 'tags' => 'Etiquetas', 'expected-close-date' => 'Fecha de Cierre Esperada', 'created-at' => 'Creado En', ], 'toolbar' => [ 'search' => [ 'title' => 'Buscar por título', ], 'filters' => [ 'apply-filters' => 'Aplicar Filtros', 'clear-all' => 'Limpiar Todo', 'filter' => 'Filtrar', 'filters' => 'Filtros', 'from' => 'De', 'select' => 'Seleccionar', 'to' => 'A', ], ], ], 'view-switcher' => [ 'all-pipelines' => 'Todos los Canales', 'create-new-pipeline' => 'Crear Nuevo Canal', ], 'upload' => [ 'create-lead' => 'Crear Lead Usando IA', 'file' => 'Carga de archivo', 'file-info' => 'Solo se aceptan archivos en formato pdf, bmp, jpg, jpeg, png.', 'file-required' => 'Por favor, selecciona al menos un archivo válido para continuar.', 'save-btn' => 'Guardar', 'upload-file' => 'Subir archivo', ], ], 'create' => [ 'title' => 'Crear Lead', 'save-btn' => 'Guardar', 'details' => 'Detalles', 'details-info' => 'Introduce la Información Básica del Lead', 'contact-person' => 'Persona de Contacto', 'contact-info' => 'Información Sobre la Persona de Contacto', 'products' => 'Productos', 'products-info' => 'Información Sobre los Productos', ], 'edit' => [ 'title' => 'Editar Lead', 'save-btn' => 'Guardar', 'details' => 'Detalles', 'details-info' => 'Introduce la Información Básica del Lead', 'contact-person' => 'Persona de Contacto', 'contact-info' => 'Información Sobre la Persona de Contacto', 'products' => 'Productos', 'products-info' => 'Información Sobre los Productos', ], 'common' => [ 'contact' => [ 'name' => 'Nombre', 'email' => 'Correo Electrónico', 'contact-number' => 'Número de Contacto', 'organization' => 'Organización', ], 'products' => [ 'product-name' => 'Nombre del Producto', 'quantity' => 'Cantidad', 'price' => 'Precio', 'amount' => 'Monto', 'action' => 'Acción', 'add-more' => 'Agregar Más', 'total' => 'Total', ], ], 'view' => [ 'title' => 'Lead: :title', 'rotten-days' => ':days Días', 'tabs' => [ 'description' => 'Descripción', 'products' => 'Productos', 'quotes' => 'Cotizaciones', ], 'attributes' => [ 'title' => 'Sobre el Lead', ], 'quotes' => [ 'subject' => 'Asunto', 'expired-at' => 'Expirado En', 'sub-total' => 'Subtotal', 'discount' => 'Descuento', 'tax' => 'Impuesto', 'adjustment' => 'Ajuste', 'grand-total' => 'Total General', 'delete' => 'Eliminar', 'edit' => 'Editar', 'download' => 'Descargar', 'destroy-success' => 'Cotización eliminada exitosamente.', 'empty-title' => 'No se Encontraron Cotizaciones', 'empty-info' => 'No se Encontraron Cotizaciones para este Lead', 'add-btn' => 'Agregar Cotización', ], 'products' => [ 'product-name' => 'Nombre del Producto', 'quantity' => 'Cantidad', 'price' => 'Precio', 'amount' => 'Monto', 'action' => 'Acción', 'add-more' => 'Agregar Más', 'total' => 'Total', 'empty-title' => 'No se Encontraron Productos', 'empty-info' => 'No se Encontraron Productos para este Lead', 'add-product' => 'Agregar Producto', ], 'persons' => [ 'title' => 'Sobre las Personas', 'job-title' => ':job_title en :organization', ], 'stages' => [ 'won' => 'Ganado', 'lost' => 'Perdido', 'need-more-info' => 'Necesita Más Información', 'closed-at' => 'Cerrado En', 'won-value' => 'Valor Ganado', 'lost-reason' => 'Razón de la Pérdida', 'save-btn' => 'Guardar', ], 'tags' => [ 'create-success' => 'Etiqueta creada con éxito.', 'destroy-success' => 'Etiqueta eliminada con éxito.', ], ], ], 'configuration' => [ 'index' => [ 'back' => 'Regresar', 'delete' => 'Eliminar', 'save-btn' => 'Guardar Configuración', 'save-success' => 'Configuración Guardada Exitosamente.', 'search' => 'Buscar', 'select-country' => 'Seleccionar País', 'select-state' => 'Seleccionar Estado', 'title' => 'Configuración', 'general' => [ 'title' => 'General', 'info' => 'Configuración General', 'general' => [ 'title' => 'General', 'info' => 'Actualiza tus configuraciones generales aquí.', 'locale-settings' => [ 'title' => 'Configuraciones de Idioma', 'title-info' => 'Define el idioma utilizado en la interfaz de usuario, como Árabe (ar), Inglés (en), Español (es), Persa (fa) y Turco (tr).', ], 'admin-logo' => [ 'logo-image' => 'Imagen del Logo', 'title' => 'Logo del Administrador', 'title-info' => 'Configura la imagen del logo para tu panel de administración.', ], ], 'settings' => [ 'title' => 'Configuraciones', 'info' => 'Actualiza tus configuraciones aquí.', 'footer' => [ 'info' => 'Podemos configurar la sección de powered by aquí.', 'powered-by' => 'Editor de texto impulsado por', 'title' => 'Configuraciones de la Sección Powered by', ], 'menu' => [ 'activities' => 'Actividades', 'configuration' => 'Configuración', 'contacts' => 'Contactos', 'dashboard' => 'Tablero', 'draft' => 'Borrador', 'inbox' => 'Bandeja de Entrada', 'info' => 'Podemos configurar los nombres de los elementos del menú aquí.', 'leads' => 'Leads', 'mail' => 'Correo', 'organizations' => 'Organizaciones', 'outbox' => 'Bandeja de Salida', 'persons' => 'Personas', 'products' => 'Productos', 'quotes' => 'Cotizaciones', 'sent' => 'Enviados', 'settings' => 'Configuraciones', 'title' => 'Configuraciones de Elementos del Menú', 'trash' => 'Papelera', ], 'menu-color' => [ 'brand-color' => 'Brand Color', 'info' => 'Podemos cambiar los colores de los elementos del menú aquí.', 'title' => 'Configuraciones de Color de Elementos del Menú', ], ], ], 'email' => [ 'title' => 'Configuración de Correo Electrónico', 'info' => 'Configuración de correo electrónico para la aplicación.', 'imap' => [ 'title' => 'Configuración IMAP', 'info' => 'Configuración de correo electrónico IMAP para recibir correos electrónicos.', 'account' => [ 'title' => 'Cuenta IMAP', 'title-info' => 'Configura los ajustes de tu cuenta IMAP aquí.', 'host' => 'Host', 'port' => 'Puerto', 'encryption' => 'Tipo de Cifrado', 'validate-cert' => 'Validar Certificado SSL', 'username' => 'Nombre de Usuario IMAP', 'password' => 'Contraseña IMAP', ], ], ], 'magic-ai' => [ 'title' => 'Magic AI', 'info' => 'Configuración de Magic AI para la aplicación.', 'settings' => [ 'api-key' => 'Clave API', 'api-key-info' => 'Recuerda usar una clave API de OpenRouter para cada modelo. Es un paso simple para mejorar la seguridad y el rendimiento.', 'enable' => 'Habilitar', 'info' => 'Mejora tu experiencia con Magic AI con tu clave API de OpenRouter. ¡Intégrala ahora para una aventura de IA personalizada y sin problemas, hecha a tu medida! Personaliza la configuración sin esfuerzo y toma el control de tu viaje de IA.', 'other' => 'Otro Modelo', 'other-model' => 'Para otros modelos, usa el ID del Modelo de OpenRouter.', 'doc-generation' => 'Generación de DOC', 'doc-generation-info' => 'Habilita la función de generación de DOC para extraer automáticamente datos de archivos DOC y convertirlos a formato de texto. Mejora tu productividad y eficiencia habilitando esta función para agilizar tu flujo de trabajo.', 'title' => 'Configuraciones Generales', 'models' => [ 'deepseek-r1' => 'Deepseek R1 Distill-llama-8b', 'gemini-2-0-flash-001' => 'Gemini 2.0 flash-001', 'gpt-4o' => 'GPT-4.0', 'gpt-4o-mini' => 'GPT-4.0 mini', 'grok-2-1212' => 'Grok 2.12', 'llama-3-2-3b-instruct' => 'Llama 3.2 3b Instruct', 'title' => 'Modelos', ], ], ], ], ], 'dashboard' => [ 'index' => [ 'title' => 'Tablero', 'start-date' => 'Start Date', 'end-date' => 'End Date', 'revenue' => [ 'lost-revenue' => 'Ingresos Perdidos', 'won-revenue' => 'Ingresos Ganados', ], 'over-all' => [ 'average-lead-value' => 'Valor Promedio de Lead', 'total-leads' => 'Total de Leads', 'average-leads-per-day' => 'Leads Promedio por Día', 'total-quotations' => 'Total de Cotizaciones', 'total-persons' => 'Total de Personas', 'total-organizations' => 'Total de Organizaciones', ], 'total-leads' => [ 'title' => 'Leads', 'total' => 'Total de Leads', 'won' => 'Leads Ganados', 'lost' => 'Leads Perdidos', ], 'revenue-by-sources' => [ 'title' => 'Ingresos por Fuentes', 'empty-title' => 'No Hay Datos Disponibles', 'empty-info' => 'No hay datos disponibles para el intervalo seleccionado', ], 'revenue-by-types' => [ 'title' => 'Ingresos por Tipos', 'empty-title' => 'No Hay Datos Disponibles', 'empty-info' => 'No hay datos disponibles para el intervalo seleccionado', ], 'top-selling-products' => [ 'title' => 'Productos Más Vendidos', 'empty-title' => 'No Se Encontraron Productos', 'empty-info' => 'No hay productos disponibles para el intervalo seleccionado', ], 'top-persons' => [ 'title' => 'Personas Más Destacadas', 'empty-title' => 'No Se Encontraron Personas', 'empty-info' => 'No hay personas disponibles para el intervalo seleccionado', ], 'open-leads-by-states' => [ 'title' => 'Leads Abiertos por Etapas', 'empty-title' => 'No Hay Datos Disponibles', 'empty-info' => 'No hay datos disponibles para el intervalo seleccionado', ], ], ], 'layouts' => [ 'app-version' => 'Versión: :version', 'dashboard' => 'Tablero', 'leads' => 'Leads', 'quotes' => 'Cotizaciones', 'quote' => 'Cotización', 'mail' => [ 'title' => 'Correo', 'compose' => 'Redactar', 'inbox' => 'Bandeja de Entrada', 'draft' => 'Borradores', 'outbox' => 'Enviados', 'sent' => 'Enviados', 'trash' => 'Papelera', 'setting' => 'Configuración', ], 'activities' => 'Actividades', 'contacts' => 'Contactos', 'persons' => 'Personas', 'person' => 'Persona', 'organizations' => 'Organizaciones', 'organization' => 'Organización', 'products' => 'Productos', 'product' => 'Producto', 'settings' => 'Configuraciones', 'user' => 'Usuario', 'user-info' => 'Administra todos tus usuarios y sus permisos en el CRM, lo que están autorizados a hacer.', 'groups' => 'Grupos', 'groups-info' => 'Agregar, editar o eliminar grupos del CRM', 'roles' => 'Roles', 'role' => 'Rol', 'roles-info' => 'Agregar, editar o eliminar roles del CRM', 'users' => 'Usuarios', 'users-info' => 'Agregar, editar o eliminar usuarios del CRM', 'lead' => 'Lead', 'lead-info' => 'Administra todas las configuraciones relacionadas con los leads en el CRM', 'pipelines' => 'Pipelines', 'pipelines-info' => 'Agregar, editar o eliminar pipelines del CRM', 'sources' => 'Fuentes', 'sources-info' => 'Agregar, editar o eliminar fuentes del CRM', 'types' => 'Tipos', 'types-info' => 'Agregar, editar o eliminar tipos del CRM', 'automation' => 'Automatización', 'automation-info' => 'Administra todas las configuraciones relacionadas con la automatización en el CRM', 'attributes' => 'Atributos', 'attribute' => 'Atributo', 'attributes-info' => 'Agregar, editar o eliminar atributos del CRM', 'email-templates' => 'Plantillas de Correo', 'email' => 'Correo', 'email-templates-info' => 'Agregar, editar o eliminar plantillas de correo del CRM', 'events' => 'Eventos', 'events-info' => 'Agregar, editar o eliminar eventos del CRM', 'campaigns' => 'Campañas', 'campaigns-info' => 'Agregar, editar o eliminar campañas del CRM', 'workflows' => 'Flujos de Trabajo', 'workflows-info' => 'Agregar, editar o eliminar flujos de trabajo del CRM', 'webhooks' => 'Webhooks', 'webhooks-info' => 'Agregar, editar o eliminar webhooks desde CRM', 'other-settings' => 'Otras Configuraciones', 'other-settings-info' => 'Administra todas tus configuraciones adicionales en el CRM', 'tags' => 'Etiquetas', 'tags-info' => 'Agregar, editar o eliminar etiquetas del CRM', 'my-account' => 'Mi Cuenta', 'sign-out' => 'Cerrar Sesión', 'back' => 'Volver', 'name' => 'Nombre', 'configuration' => 'Configuración', 'howdy' => '¡Hola!', 'warehouses' => 'Almacenes', 'warehouse' => 'Almacén', 'warehouses-info' => 'Agregar, editar o eliminar almacenes del CRM', 'inventory' => 'Inventario', 'inventory-info' => 'Gestionar todos los ajustes relacionados con el inventario en el CRM', 'data_transfer' => 'Transferencia de Datos', 'data_transfer_info' => 'Gestionar la configuración relacionada con la transferencia de datos de personas, productos y clientes potenciales en el CRM', ], 'user' => [ 'account' => [ 'name' => 'Nombre', 'email' => 'Correo Electrónico', 'password' => 'Contraseña', 'my_account' => 'Mi cuenta', 'update_details' => 'Actualizar Detalles', 'current_password' => 'Contraseña Actual', 'confirm_password' => 'Confirmar Contraseña', 'password-match' => 'La contraseña actual no coincide.', 'account-save' => 'Cambios en la cuenta guardados exitosamente.', 'permission-denied' => 'Permiso Denegado', 'remove-image' => 'Eliminar Imagen', 'upload_image_pix' => 'Sube una Imagen de Perfil (100px x 100px)', 'upload_image_format' => 'en formato PNG o JPG', 'image_upload_message' => 'Solo se permiten imágenes (.jpeg, .jpg, .png, ..).', ], ], 'emails' => [ 'common' => [ 'dear' => 'Estimado/a :name', 'cheers' => 'Saludos,
Equipo :app_name', 'user' => [ 'dear' => 'Estimado/a :username', 'create-subject' => 'Has sido agregado como miembro.', 'create-body' => '¡Felicidades! Ahora eres miembro de nuestro equipo.', 'forget-password' => [ 'subject' => 'Restablecimiento de Contraseña del Cliente', 'dear' => 'Estimado/a :username', 'reset-password' => 'Restablecer Contraseña', 'info' => 'Estás recibiendo este correo porque recibimos una solicitud de restablecimiento de contraseña para tu cuenta', 'final-summary' => 'Si no solicitaste el restablecimiento de contraseña, no se requiere ninguna acción adicional', 'thanks' => '¡Gracias!', ], ], ], ], 'validations' => [ 'message' => [ 'decimal' => 'El :attribute debe ser un número decimal.', ], ], 'errors' => [ 'dashboard' => 'Tablero', 'go-back' => 'Volver', 'support' => 'Si el problema persiste, contáctanos en :email para obtener ayuda.', '404' => [ 'description' => '¡Ups! La página que estás buscando está de vacaciones. Parece que no pudimos encontrar lo que estabas buscando.', 'title' => '404 Página No Encontrada', ], '401' => [ 'description' => '¡Ups! Parece que no tienes permiso para acceder a esta página. Parece que te faltan las credenciales necesarias.', 'title' => '401 No Autorizado', ], '403' => [ 'description' => '¡Ups! Esta página está fuera de límites. Parece que no tienes los permisos necesarios para ver este contenido.', 'title' => '403 Prohibido', ], '500' => [ 'description' => '¡Ups! Algo salió mal. Parece que tenemos problemas para cargar la página que estás buscando.', 'title' => '500 Error Interno del Servidor', ], '503' => [ 'description' => '¡Ups! Parece que estamos temporalmente fuera de servicio por mantenimiento. Vuelve a intentarlo en un rato.', 'title' => '503 Servicio No Disponible', ], ], 'export' => [ 'csv' => 'CSV', 'download' => 'Descargar', 'export' => 'Exportar', 'no-records' => 'No se encontraron registros.', 'xls' => 'XLS', 'xlsx' => 'XLSX', ], ]; ================================================ FILE: packages/Webkul/Admin/src/Resources/lang/fa/app.php ================================================ [ 'leads' => 'سرنخ‌ها', 'lead' => 'سرنخ', 'quotes' => 'پیشنهادات', 'mail' => 'ایمیل', 'inbox' => 'صندوق ورودی', 'draft' => 'پیش‌نویس', 'outbox' => 'صندوق خروجی', 'sent' => 'ارسال شده', 'trash' => 'زباله‌دان', 'activities' => 'فعالیت‌ها', 'webhook' => 'وب‌هوک', 'contacts' => 'مخاطبین', 'persons' => 'افراد', 'organizations' => 'سازمان‌ها', 'products' => 'محصولات', 'settings' => 'تنظیمات', 'groups' => 'گروه‌ها', 'roles' => 'نقش‌ها', 'users' => 'کاربران', 'user' => 'کاربر', 'automation' => 'اتوماسیون', 'attributes' => 'ویژگی‌ها', 'pipelines' => 'لوله‌ها', 'sources' => 'منابع', 'types' => 'انواع', 'email-templates' => 'قالب‌های ایمیل', 'workflows' => 'جریان‌کار', 'other-settings' => 'سایر تنظیمات', 'tags' => 'برچسب‌ها', 'configuration' => 'پیکربندی', 'create' => 'ایجاد', 'edit' => 'ویرایش', 'view' => 'نمایش', 'print' => 'چاپ', 'delete' => 'حذف', 'export' => 'صادر کردن', 'mass-delete' => 'حذف انبوه', 'data-transfer' => 'انتقال داده', 'imports' => 'واردات', 'import' => 'وارد کردن', 'event' => 'رویداد', 'campaigns' => 'کمپین‌ها', 'warehouses' => 'انبارها', 'inventory' => 'موجودی', ], 'users' => [ 'activate-warning' => 'حساب شما هنوز فعال نشده است. لطفاً با مدیر سیستم تماس بگیرید.', 'login-error' => 'اطلاعات وارد شده با سوابق ما مطابقت ندارد.', 'not-permission' => 'شما اجازه دسترسی به پنل مدیریت را ندارید.', 'login' => [ 'email' => 'آدرس ایمیل', 'forget-password-link' => 'فراموشی رمز عبور؟', 'password' => 'رمز عبور', 'submit-btn' => 'ورود', 'title' => 'ورود', ], 'forget-password' => [ 'create' => [ 'email' => 'ایمیل ثبت‌شده', 'email-not-exist' => 'ایمیل وجود ندارد', 'page-title' => 'فراموشی رمز عبور', 'reset-link-sent' => 'لینک بازنشانی رمز عبور ارسال شد', 'sign-in-link' => 'بازگشت به ورود؟', 'submit-btn' => 'بازنشانی', 'title' => 'بازیابی رمز عبور', ], ], 'reset-password' => [ 'back-link-title' => 'بازگشت به ورود؟', 'confirm-password' => 'تأیید رمز عبور', 'email' => 'ایمیل ثبت‌شده', 'password' => 'رمز عبور', 'submit-btn' => 'بازنشانی رمز عبور', 'title' => 'بازنشانی رمز عبور', ], ], 'account' => [ 'edit' => [ 'back-btn' => 'بازگشت', 'change-password' => 'تغییر رمز عبور', 'confirm-password' => 'تایید رمز عبور', 'current-password' => 'رمز عبور فعلی', 'email' => 'ایمیل', 'general' => 'عمومی', 'invalid-password' => 'رمز عبور فعلی شما نادرست است.', 'name' => 'نام', 'password' => 'رمز عبور', 'profile-image' => 'تصویر پروفایل', 'save-btn' => 'ذخیره حساب کاربری', 'title' => 'حساب من', 'update-success' => 'حساب کاربری با موفقیت به‌روزرسانی شد', 'upload-image-info' => 'آپلود یک تصویر پروفایل (110px X 110px) در فرمت PNG یا JPG', ], ], 'components' => [ 'activities' => [ 'actions' => [ 'mail' => [ 'btn' => 'ایمیل', 'title' => 'نگارش ایمیل', 'to' => 'به', 'enter-emails' => 'برای افزودن ایمیل‌ها اینتر را فشار دهید', 'cc' => 'کپی', 'bcc' => 'کپی مخفی', 'subject' => 'موضوع', 'send-btn' => 'ارسال', 'message' => 'پیام', ], 'file' => [ 'btn' => 'فایل', 'title' => 'افزودن فایل', 'title-control' => 'عنوان', 'name' => 'نام', 'description' => 'توضیحات', 'file' => 'فایل', 'save-btn' => 'ذخیره فایل', ], 'note' => [ 'btn' => 'یادداشت', 'title' => 'افزودن یادداشت', 'comment' => 'نظر', 'save-btn' => 'ذخیره یادداشت', ], 'activity' => [ 'btn' => 'فعالیت', 'title' => 'افزودن فعالیت', 'title-control' => 'عنوان', 'description' => 'توضیحات', 'schedule-from' => 'زمان‌بندی از', 'schedule-to' => 'زمان‌بندی تا', 'location' => 'محل', 'call' => 'تماس', 'meeting' => 'جلسه', 'lunch' => 'ناهار', 'save-btn' => 'ذخیره فعالیت', 'participants' => [ 'title' => 'شرکت‌کنندگان', 'placeholder' => 'برای جستجوی شرکت‌کنندگان تایپ کنید', 'users' => 'کاربران', 'persons' => 'افراد', 'no-results' => 'هیچ نتیجه‌ای یافت نشد...', ], ], ], 'index' => [ 'all' => 'همه', 'bcc' => 'کپی مخفی', 'by-user' => 'توسط :user', 'calls' => 'تماس‌ها', 'cc' => 'کپی', 'change-log' => 'تغییرات', 'delete' => 'حذف', 'edit' => 'ویرایش', 'emails' => 'ایمیل‌ها', 'empty' => 'خالی', 'files' => 'فایل‌ها', 'from' => 'از', 'location' => 'محل', 'lunches' => 'ناهارها', 'mark-as-done' => 'علامت زدن به عنوان انجام شده', 'meetings' => 'جلسات', 'notes' => 'یادداشت‌ها', 'participants' => 'شرکت‌کنندگان', 'planned' => 'برنامه‌ریزی شده', 'quotes' => 'نقل قول‌ها', 'scheduled-on' => 'برنامه‌ریزی شده در', 'system' => 'سیستم', 'to' => 'تا', 'unlink' => 'لغو پیوند', 'view' => 'مشاهده', 'empty-placeholders' => [ 'all' => [ 'title' => 'هیچ فعالیتی یافت نشد', 'description' => 'هیچ فعالیتی برای این مورد یافت نشد. می‌توانید با کلیک روی دکمه "فعالیت" در پنل سمت چپ، فعالیت اضافه کنید.', ], 'planned' => [ 'title' => 'هیچ فعالیت برنامه‌ریزی‌شده‌ای یافت نشد', 'description' => 'هیچ فعالیت برنامه‌ریزی‌شده‌ای برای این مورد یافت نشد. می‌توانید با کلیک روی دکمه "فعالیت" در پنل سمت چپ، فعالیت اضافه کنید.', ], 'notes' => [ 'title' => 'یادداشتی یافت نشد', 'description' => 'هیچ یادداشتی برای این مورد یافت نشد. می‌توانید با کلیک روی دکمه "یادداشت" در پنل سمت چپ، یادداشت اضافه کنید.', ], 'calls' => [ 'title' => 'تماسی یافت نشد', 'description' => 'هیچ تماسی برای این مورد یافت نشد. می‌توانید با کلیک روی دکمه "فعالیت" و انتخاب نوع تماس، تماس اضافه کنید.', ], 'meetings' => [ 'title' => 'ملاقاتی یافت نشد', 'description' => 'هیچ ملاقاتی برای این مورد یافت نشد. می‌توانید با کلیک روی دکمه "فعالیت" و انتخاب نوع جلسه، جلسه اضافه کنید.', ], 'lunches' => [ 'title' => 'ناهاری یافت نشد', 'description' => 'هیچ ناهاری برای این مورد یافت نشد. می‌توانید با کلیک روی دکمه "فعالیت" و انتخاب نوع ناهار، ناهار اضافه کنید.', ], 'files' => [ 'title' => 'فایلی یافت نشد', 'description' => 'هیچ فایلی برای این مورد یافت نشد. می‌توانید با کلیک روی دکمه "فایل" در پنل سمت چپ، فایل اضافه کنید.', ], 'emails' => [ 'title' => 'ایمیلی یافت نشد', 'description' => 'هیچ ایمیلی برای این مورد یافت نشد. می‌توانید با کلیک روی دکمه "ایمیل" در پنل سمت چپ، ایمیل اضافه کنید.', ], 'system' => [ 'title' => 'تغییری ثبت نشده است', 'description' => 'هیچ گزارش تغییری برای این مورد یافت نشد.', ], ], ], ], 'media' => [ 'images' => [ 'add-image-btn' => 'افزودن تصویر', 'ai-add-image-btn' => 'هوش مصنوعی جادویی', 'allowed-types' => 'png, jpeg, jpg', 'not-allowed-error' => 'فقط فایل‌های تصویری (.jpeg, .jpg, .png, ..) مجاز هستند.', 'placeholders' => [ 'front' => 'جلو', 'next' => 'بعدی', 'size' => 'اندازه', 'use-cases' => 'موارد استفاده', 'zoom' => 'بزرگنمایی', ], ], 'videos' => [ 'add-video-btn' => 'افزودن ویدیو', 'allowed-types' => 'mp4, webm, mkv', 'not-allowed-error' => 'فقط فایل‌های ویدیویی (.mp4, .mov, .ogg ..) مجاز هستند.', ], ], 'datagrid' => [ 'index' => [ 'no-records-selected' => 'هیچ رکوردی انتخاب نشده است.', 'must-select-a-mass-action-option' => 'باید یک گزینه از اقدامات انبوه را انتخاب کنید.', 'must-select-a-mass-action' => 'باید یک اقدام انبوه را انتخاب کنید.', ], 'toolbar' => [ 'length-of' => ':length از', 'of' => 'از', 'per-page' => 'در هر صفحه', 'results' => ':total نتایج', 'delete' => 'حذف', 'selected' => ':total موارد انتخاب شده', 'mass-actions' => [ 'submit' => 'ارسال', 'select-option' => 'انتخاب گزینه', 'select-action' => 'انتخاب اقدام', ], 'filter' => [ 'apply-filters-btn' => 'اعمال فیلترها', 'back-btn' => 'بازگشت', 'create-new-filter' => 'ایجاد فیلتر جدید', 'custom-filters' => 'فیلترهای سفارشی', 'delete-error' => 'خطایی در هنگام حذف فیلتر رخ داد، لطفاً دوباره تلاش کنید.', 'delete-success' => 'فیلتر با موفقیت حذف شد.', 'empty-description' => 'هیچ فیلتری برای ذخیره انتخاب نشده است. لطفاً فیلترها را برای ذخیره انتخاب کنید.', 'empty-title' => 'افزودن فیلترها برای ذخیره', 'name' => 'نام', 'quick-filters' => 'فیلترهای سریع', 'save-btn' => 'ذخیره', 'save-filter' => 'ذخیره فیلتر', 'saved-success' => 'فیلتر با موفقیت ذخیره شد.', 'selected-filters' => 'فیلترهای انتخاب شده', 'title' => 'فیلتر', 'update' => 'به‌روزرسانی', 'update-filter' => 'به‌روزرسانی فیلتر', 'updated-success' => 'فیلتر با موفقیت به‌روزرسانی شد.', ], 'search' => [ 'title' => 'جستجو', ], ], 'filters' => [ 'select' => 'انتخاب', 'title' => 'فیلترها', 'dropdown' => [ 'searchable' => [ 'at-least-two-chars' => 'حداقل ۲ حرف تایپ کنید...', 'no-results' => 'هیچ نتیجه‌ای یافت نشد...', ], ], 'custom-filters' => [ 'clear-all' => 'پاک کردن همه', 'title' => 'فیلترهای سفارشی', ], 'boolean-options' => [ 'false' => 'نادرست', 'true' => 'درست', ], 'date-options' => [ 'last-month' => 'ماه گذشته', 'last-six-months' => '۶ ماه گذشته', 'last-three-months' => '۳ ماه گذشته', 'this-month' => 'این ماه', 'this-week' => 'این هفته', 'this-year' => 'امسال', 'today' => 'امروز', 'yesterday' => 'دیروز', ], ], 'table' => [ 'actions' => 'اقدامات', 'no-records-available' => 'رکوردی موجود نیست.', ], ], 'modal' => [ 'confirm' => [ 'agree-btn' => 'موافقم', 'disagree-btn' => 'مخالفم', 'message' => 'آیا مطمئن هستید که می‌خواهید این عمل را انجام دهید؟', 'title' => 'آیا مطمئن هستید؟', ], ], 'tags' => [ 'index' => [ 'title' => 'برچسب‌ها', 'added-tags' => 'برچسب‌های اضافه شده', 'save-btn' => 'ذخیره برچسب', 'placeholder' => 'برای جستجوی برچسب‌ها تایپ کنید', 'add-tag' => 'اضافه کردن ":term"...', 'aquarelle-red' => 'قرمز آبرنگی', 'crushed-cashew' => 'بادام هندی خرد شده', 'beeswax' => 'موم عسل', 'lemon-chiffon' => 'لیمویی', 'snow-flurry' => 'برف سبک', 'honeydew' => 'شهد عسل', ], ], 'layouts' => [ 'powered-by' => [ 'description' => 'توسعه یافته توسط :krayin، یک پروژه متن باز از :webkul.', ], 'header' => [ 'mega-search' => [ 'title' => 'جستجوی بزرگ', 'tabs' => [ 'leads' => 'سرنخ‌ها', 'quotes' => 'پیشنهادات', 'persons' => 'افراد', 'products' => 'محصولات', ], 'explore-all-products' => 'کاوش در تمامی محصولات', 'explore-all-leads' => 'کاوش در تمامی سرنخ‌ها', 'explore-all-contacts' => 'کاوش در تمامی مخاطبین', 'explore-all-quotes' => 'کاوش در تمامی پیشنهادات', 'explore-all-matching-products' => 'کاوش در تمامی محصولات مطابق ":query" (:count)', 'explore-all-matching-leads' => 'کاوش در تمامی سرنخ‌های مطابق ":query" (:count)', 'explore-all-matching-contacts' => 'کاوش در تمامی مخاطبین مطابق ":query" (:count)', 'explore-all-matching-quotes' => 'کاوش در تمامی پیشنهادات مطابق ":query" (:count)', ], ], ], 'attributes' => [ 'edit' => [ 'delete' => 'حذف', ], 'lookup' => [ 'click-to-add' => 'برای اضافه کردن کلیک کنید', 'search' => 'جستجو...', 'no-result-found' => 'نتیجه‌ای یافت نشد', ], ], 'lookup' => [ 'click-to-add' => 'برای اضافه کردن کلیک کنید', 'no-results' => 'نتیجه‌ای یافت نشد', 'add-as-new' => 'اضافه کردن به عنوان جدید', 'search' => 'جستجو...', ], 'flash-group' => [ 'success' => 'موفقیت', 'error' => 'خطا', 'warning' => 'هشدار', 'info' => 'اطلاعات', ], 'tiny-mce' => [ 'http-error' => 'خطای HTTP', 'invalid-json' => 'پاسخ JSON نامعتبر از سرور.', 'upload-failed' => 'آپلود فایل ناموفق بود. لطفاً دوباره تلاش کنید.', ], ], 'quotes' => [ 'index' => [ 'title' => 'نقل‌قول‌ها', 'create-btn' => 'ایجاد نقل‌قول', 'create-success' => 'نقل‌قول با موفقیت ایجاد شد.', 'update-success' => 'نقل‌قول با موفقیت به‌روزرسانی شد.', 'delete-success' => 'نقل‌قول با موفقیت حذف شد.', 'delete-failed' => 'حذف نقل‌قول ممکن نیست.', 'datagrid' => [ 'subject' => 'موضوع', 'sales-person' => 'فروشنده', 'expired-at' => 'تاریخ انقضا', 'created-at' => 'تاریخ ایجاد', 'person' => 'شخص', 'subtotal' => 'جمع جزء', 'discount' => 'تخفیف', 'tax' => 'مالیات', 'adjustment' => 'تنظیم', 'grand-total' => 'جمع کل', 'edit' => 'ویرایش', 'delete' => 'حذف', 'print' => 'چاپ', ], 'pdf' => [ 'adjustment' => 'تنظیم', 'amount' => 'مقدار', 'billing-address' => 'آدرس صورتحساب', 'date' => 'تاریخ', 'discount' => 'تخفیف', 'expired-at' => 'تاریخ انقضا', 'grand-total' => 'جمع کل', 'person' => 'شخص', 'price' => 'قیمت', 'product-name' => 'نام محصول', 'quantity' => 'تعداد', 'quote-id' => 'شناسه نقل‌قول', 'sales-person' => 'فروشنده', 'shipping-address' => 'آدرس ارسال', 'sku' => 'کد SKU', 'sub-total' => 'جمع جزء', 'subject' => 'موضوع', 'tax' => 'مالیات', 'title' => 'نقل‌قول', ], ], 'create' => [ 'title' => 'ایجاد نقل‌قول', 'save-btn' => 'ذخیره نقل‌قول', 'quote-info' => 'اطلاعات نقل‌قول', 'quote-info-info' => 'اطلاعات پایه نقل‌قول را وارد کنید.', 'address-info' => 'اطلاعات آدرس', 'address-info-info' => 'اطلاعات مربوط به آدرس مرتبط با نقل‌قول.', 'quote-items' => 'موارد نقل‌قول', 'search-products' => 'جستجوی محصولات', 'link-to-lead' => 'پیوند به سرنخ', 'quote-item-info' => 'درخواست محصول را برای این نقل‌قول اضافه کنید.', 'quote-name' => 'نام نقل‌قول', 'quantity' => 'تعداد', 'price' => 'قیمت', 'discount' => 'تخفیف', 'tax' => 'مالیات', 'total' => 'مجموع', 'amount' => 'مقدار', 'add-item' => '+ افزودن مورد', 'sub-total' => 'جمع جزء (:symbol)', 'total-discount' => 'تخفیف (:symbol)', 'total-tax' => 'مالیات (:symbol)', 'total-adjustment' => 'تنظیم (:symbol)', 'grand-total' => 'جمع کل (:symbol)', 'discount-amount' => 'مقدار تخفیف', 'tax-amount' => 'مقدار مالیات', 'adjustment-amount' => 'مقدار تنظیم', 'product-name' => 'نام محصول', 'action' => 'عملیات', ], 'edit' => [ 'title' => 'ویرایش نقل‌قول', 'save-btn' => 'ذخیره نقل‌قول', 'quote-info' => 'اطلاعات نقل‌قول', 'quote-info-info' => 'اطلاعات پایه نقل‌قول را وارد کنید.', 'address-info' => 'اطلاعات آدرس', 'address-info-info' => 'اطلاعات مربوط به آدرس مرتبط با نقل‌قول.', 'quote-items' => 'موارد نقل‌قول', 'link-to-lead' => 'پیوند به سرنخ', 'quote-item-info' => 'درخواست محصول را برای این نقل‌قول اضافه کنید.', 'quote-name' => 'نام نقل‌قول', 'quantity' => 'تعداد', 'price' => 'قیمت', 'search-products' => 'جستجوی محصولات', 'discount' => 'تخفیف', 'tax' => 'مالیات', 'total' => 'مجموع', 'amount' => 'مقدار', 'add-item' => '+ افزودن مورد', 'sub-total' => 'جمع جزء (:symbol)', 'total-discount' => 'تخفیف (:symbol)', 'total-tax' => 'مالیات (:symbol)', 'total-adjustment' => 'تنظیم (:symbol)', 'grand-total' => 'جمع کل (:symbol)', 'discount-amount' => 'مقدار تخفیف', 'tax-amount' => 'مقدار مالیات', 'adjustment-amount' => 'مقدار تنظیم', 'product-name' => 'نام محصول', 'action' => 'عملیات', ], ], 'contacts' => [ 'persons' => [ 'index' => [ 'title' => 'اشخاص', 'create-btn' => 'ایجاد شخص', 'create-success' => 'شخص با موفقیت ایجاد شد.', 'update-success' => 'شخص با موفقیت به‌روزرسانی شد.', 'all-delete-success' => 'همه افراد انتخاب‌شده با موفقیت حذف شدند.', 'partial-delete-warning' => 'برخی از افراد با موفقیت حذف شدند. بقیه به دلیل مرتبط بودن با سرنخ‌ها حذف نشدند.', 'none-delete-warning' => 'هیچ‌یک از افراد انتخاب‌شده به دلیل مرتبط بودن با سرنخ‌ها حذف نشدند.', 'no-selection' => 'هیچ فردی برای حذف انتخاب نشده است.', 'delete-failed' => 'حذف افراد انتخاب‌شده با شکست مواجه شد.', 'datagrid' => [ 'contact-numbers' => 'شماره‌های تماس', 'delete' => 'حذف', 'edit' => 'ویرایش', 'emails' => 'ایمیل‌ها', 'id' => 'شناسه', 'view' => 'مشاهده', 'name' => 'نام', 'organization-name' => 'نام سازمان', ], ], 'view' => [ 'title' => ':name', 'about-person' => 'درباره شخص', 'about-organization' => 'درباره سازمان', 'activities' => [ 'index' => [ 'all' => 'همه', 'calls' => 'تماس‌ها', 'meetings' => 'جلسات', 'lunches' => 'ناهارها', 'files' => 'فایل‌ها', 'quotes' => 'نقل‌قول‌ها', 'notes' => 'یادداشت‌ها', 'emails' => 'ایمیل‌ها', 'by-user' => 'توسط :user', 'scheduled-on' => 'برنامه‌ریزی شده در', 'location' => 'مکان', 'participants' => 'شرکت‌کنندگان', 'mark-as-done' => 'علامت‌گذاری به‌عنوان انجام شده', 'delete' => 'حذف', 'edit' => 'ویرایش', ], 'actions' => [ 'mail' => [ 'btn' => 'ایمیل', 'title' => 'ایمیل جدید', 'to' => 'به', 'cc' => 'کپی (CC)', 'bcc' => 'کپی مخفی (BCC)', 'subject' => 'موضوع', 'send-btn' => 'ارسال', 'message' => 'پیام', ], 'file' => [ 'btn' => 'فایل', 'title' => 'افزودن فایل', 'title-control' => 'عنوان', 'name' => 'نام فایل', 'description' => 'توضیحات', 'file' => 'فایل', 'save-btn' => 'ذخیره فایل', ], 'note' => [ 'btn' => 'یادداشت', 'title' => 'افزودن یادداشت', 'comment' => 'نظر', 'save-btn' => 'ذخیره یادداشت', ], 'activity' => [ 'btn' => 'فعالیت', 'title' => 'افزودن فعالیت', 'title-control' => 'عنوان', 'description' => 'توضیحات', 'schedule-from' => 'برنامه‌ریزی از', 'schedule-to' => 'برنامه‌ریزی تا', 'location' => 'مکان', 'call' => 'تماس', 'meeting' => 'جلسه', 'lunch' => 'ناهار', 'save-btn' => 'ذخیره فعالیت', ], ], ], 'tags' => [ 'create-success' => 'برچسب با موفقیت ایجاد شد.', 'destroy-success' => 'برچسب با موفقیت حذف شد.', ], ], 'create' => [ 'title' => 'ایجاد شخص', 'save-btn' => 'ذخیره شخص', ], 'edit' => [ 'title' => 'ویرایش شخص', 'save-btn' => 'ذخیره شخص', ], ], 'organizations' => [ 'index' => [ 'title' => 'سازمان‌ها', 'create-btn' => 'ایجاد سازمان', 'create-success' => 'سازمان با موفقیت ایجاد شد.', 'update-success' => 'سازمان با موفقیت به‌روزرسانی شد.', 'delete-success' => 'سازمان با موفقیت حذف شد.', 'delete-failed' => 'سازمان قابل حذف نیست.', 'datagrid' => [ 'delete' => 'حذف', 'edit' => 'ویرایش', 'id' => 'شناسه', 'name' => 'نام', 'persons-count' => 'تعداد اشخاص', ], ], 'create' => [ 'title' => 'ایجاد سازمان', 'save-btn' => 'ذخیره سازمان', ], 'edit' => [ 'title' => 'ویرایش سازمان', 'save-btn' => 'ذخیره سازمان', ], ], ], 'products' => [ 'index' => [ 'title' => 'محصولات', 'create-btn' => 'ایجاد محصول', 'create-success' => 'محصول با موفقیت ایجاد شد.', 'update-success' => 'محصول با موفقیت به‌روزرسانی شد.', 'delete-success' => 'محصول با موفقیت حذف شد.', 'delete-failed' => 'محصول قابل حذف نیست.', 'datagrid' => [ 'allocated' => 'اختصاص داده شده', 'delete' => 'حذف', 'edit' => 'ویرایش', 'id' => 'شناسه', 'in-stock' => 'در انبار', 'name' => 'نام', 'on-hand' => 'موجودی', 'tag-name' => 'نام برچسب', 'price' => 'قیمت', 'sku' => 'SKU', 'view' => 'مشاهده', ], ], 'create' => [ 'save-btn' => 'ذخیره محصولات', 'title' => 'ایجاد محصولات', 'general' => 'عمومی', 'price' => 'قیمت', ], 'edit' => [ 'title' => 'ویرایش محصولات', 'save-btn' => 'ذخیره محصولات', 'general' => 'عمومی', 'price' => 'قیمت', ], 'view' => [ 'sku' => 'SKU', 'all' => 'همه', 'notes' => 'یادداشت‌ها', 'files' => 'فایل‌ها', 'inventories' => 'موجودی', 'change-logs' => 'تغییرات', 'attributes' => [ 'about-product' => 'درباره محصول', ], 'inventory' => [ 'source' => 'منبع', 'in-stock' => 'در انبار', 'allocated' => 'اختصاص داده شده', 'on-hand' => 'موجودی', 'actions' => 'عملیات', 'assign' => 'اختصاص دادن', 'add-source' => 'افزودن منبع', 'location' => 'مکان', 'add-more' => 'افزودن بیشتر', 'save' => 'ذخیره', ], ], ], 'settings' => [ 'title' => 'تنظیمات', 'groups' => [ 'index' => [ 'create-btn' => 'ایجاد گروه', 'title' => 'گروه‌ها', 'create-success' => 'گروه با موفقیت ایجاد شد.', 'update-success' => 'گروه با موفقیت به‌روزرسانی شد.', 'destroy-success' => 'گروه با موفقیت حذف شد.', 'delete-failed' => 'امکان حذف گروه وجود ندارد.', 'delete-failed-associated-users' => 'امکان حذف گروه وجود ندارد زیرا توسط کاربران استفاده می‌شود.', 'datagrid' => [ 'delete' => 'حذف', 'description' => 'توضیحات', 'edit' => 'ویرایش', 'id' => 'شناسه', 'name' => 'نام', ], 'edit' => [ 'title' => 'ویرایش گروه', ], 'create' => [ 'name' => 'نام', 'title' => 'ایجاد گروه', 'description' => 'توضیحات', 'save-btn' => 'ذخیره گروه', ], ], ], 'roles' => [ 'index' => [ 'being-used' => 'نقش قابل حذف نیست، زیرا در کاربر مدیر استفاده می‌شود.', 'create-btn' => 'ایجاد نقش‌ها', 'create-success' => 'نقش با موفقیت ایجاد شد.', 'current-role-delete-error' => 'نقش اختصاص داده شده به کاربر فعلی قابل حذف نیست.', 'delete-failed' => 'نقش قابل حذف نیست.', 'delete-success' => 'نقش با موفقیت حذف شد.', 'last-delete-error' => 'حداقل یک نقش لازم است.', 'settings' => 'تنظیمات', 'title' => 'نقش‌ها', 'update-success' => 'نقش با موفقیت به‌روزرسانی شد.', 'user-define-error' => 'نقش سیستمی قابل حذف نیست.', 'datagrid' => [ 'all' => 'همه', 'custom' => 'سفارشی', 'delete' => 'حذف', 'description' => 'توضیحات', 'edit' => 'ویرایش', 'id' => 'شناسه', 'name' => 'نام', 'permission-type' => 'نوع مجوز', ], ], 'create' => [ 'access-control' => 'کنترل دسترسی', 'all' => 'همه', 'back-btn' => 'بازگشت', 'custom' => 'سفارشی', 'description' => 'توضیحات', 'general' => 'عمومی', 'name' => 'نام', 'permissions' => 'مجوزها', 'save-btn' => 'ذخیره نقش', 'title' => 'ایجاد نقش', ], 'edit' => [ 'access-control' => 'کنترل دسترسی', 'all' => 'همه', 'back-btn' => 'بازگشت', 'custom' => 'سفارشی', 'description' => 'توضیحات', 'general' => 'عمومی', 'name' => 'نام', 'permissions' => 'مجوزها', 'save-btn' => 'ذخیره نقش', 'title' => 'ویرایش نقش', ], ], 'types' => [ 'index' => [ 'create-btn' => 'ایجاد نوع', 'create-success' => 'نوع با موفقیت ایجاد شد.', 'delete-failed' => 'نوع قابل حذف نیست.', 'delete-success' => 'نوع با موفقیت حذف شد.', 'title' => 'نوع‌ها', 'update-success' => 'نوع با موفقیت به‌روزرسانی شد.', 'datagrid' => [ 'delete' => 'حذف', 'description' => 'توضیحات', 'edit' => 'ویرایش', 'id' => 'شناسه', 'name' => 'نام', ], 'create' => [ 'name' => 'نام', 'save-btn' => 'ذخیره نوع', 'title' => 'ایجاد نوع', ], 'edit' => [ 'title' => 'ویرایش نوع', ], ], ], 'sources' => [ 'index' => [ 'title' => 'منابع', 'create-btn' => 'ایجاد منبع', 'create-success' => 'منبع با موفقیت ایجاد شد.', 'delete-failed' => 'امکان حذف منبع وجود ندارد.', 'delete-success' => 'منبع با موفقیت حذف شد.', 'update-success' => 'منبع با موفقیت به‌روزرسانی شد.', 'delete-failed-associated-leads' => 'نمی‌توان منبع را حذف کرد زیرا با سرنخ‌های موجود مرتبط است. لطفاً ابتدا آن‌ها را جدا کرده یا به‌روزرسانی کنید.', 'datagrid' => [ 'delete' => 'حذف', 'edit' => 'ویرایش', 'id' => 'شناسه', 'name' => 'نام', ], 'create' => [ 'name' => 'نام', 'save-btn' => 'ذخیره منبع', 'title' => 'ایجاد منبع', ], 'edit' => [ 'title' => 'ویرایش منبع', ], ], ], 'workflows' => [ 'index' => [ 'title' => 'جریان‌های کاری', 'create-btn' => 'ایجاد جریان کاری', 'create-success' => 'جریان کاری با موفقیت ایجاد شد.', 'update-success' => 'جریان کاری با موفقیت به‌روزرسانی شد.', 'delete-success' => 'جریان کاری با موفقیت حذف شد.', 'delete-failed' => 'جریان کاری قابل حذف نیست.', 'datagrid' => [ 'delete' => 'حذف', 'description' => 'توضیحات', 'edit' => 'ویرایش', 'id' => 'شناسه', 'name' => 'نام', ], ], 'helpers' => [ 'update-related-leads' => 'به‌روزرسانی سرنخ‌های مرتبط', 'send-email-to-sales-owner' => 'ارسال ایمیل به صاحب فروش', 'send-email-to-participants' => 'ارسال ایمیل به شرکت‌کنندگان', 'add-webhook' => 'افزودن وب‌هوک', 'update-lead' => 'به‌روزرسانی سرنخ', 'update-person' => 'به‌روزرسانی شخص', 'send-email-to-person' => 'ارسال ایمیل به شخص', 'add-tag' => 'افزودن برچسب', 'add-note-as-activity' => 'افزودن یادداشت به‌عنوان فعالیت', 'update-quote' => 'به‌روزرسانی نقل‌قول', ], 'create' => [ 'title' => 'ایجاد جریان کاری', 'event' => 'رویداد', 'back-btn' => 'بازگشت', 'save-btn' => 'ذخیره جریان کاری', 'name' => 'نام', 'basic-details' => 'جزئیات پایه', 'description' => 'توضیحات', 'actions' => 'اقدامات', 'basic-details-info' => 'اطلاعات پایه جریان کاری را وارد کنید.', 'event-info' => 'یک رویداد triggers، چک‌ها، شرایط و اقدامات از پیش تعریف شده را انجام می‌دهد.', 'conditions' => 'شرایط', 'conditions-info' => 'شرایط قوانینی هستند که سناریوها را بررسی می‌کنند و در مواقع خاص فعال می‌شوند.', 'actions-info' => 'یک اقدام نه تنها بار کاری را کاهش می‌دهد بلکه فرآیند اتوماسیون CRM را بسیار آسان‌تر می‌کند.', 'value' => 'مقدار', 'condition-type' => 'نوع شرط', 'all-condition-are-true' => 'تمام شرایط صحیح هستند', 'any-condition-are-true' => 'هر شرطی صحیح است', 'add-condition' => 'افزودن شرط', 'add-action' => 'افزودن اقدام', 'yes' => 'بله', 'no' => 'خیر', 'email' => 'ایمیل', 'is-equal-to' => 'برابر است با', 'is-not-equal-to' => 'برابر نیست با', 'equals-or-greater-than' => 'برابر یا بزرگ‌تر از', 'equals-or-less-than' => 'برابر یا کوچک‌تر از', 'greater-than' => 'بزرگ‌تر از', 'less-than' => 'کوچک‌تر از', 'type' => 'نوع', 'contain' => 'شامل', 'contains' => 'شامل می‌شود', 'does-not-contain' => 'شامل نمی‌شود', ], 'edit' => [ 'title' => 'ویرایش جریان کاری', 'event' => 'رویداد', 'back-btn' => 'بازگشت', 'save-btn' => 'ذخیره جریان کاری', 'name' => 'نام', 'basic-details' => 'جزئیات پایه', 'description' => 'توضیحات', 'actions' => 'اقدامات', 'type' => 'نوع', 'basic-details-info' => 'اطلاعات پایه جریان کاری را وارد کنید.', 'event-info' => 'یک رویداد triggers، چک‌ها، شرایط و اقدامات از پیش تعریف شده را انجام می‌دهد.', 'conditions' => 'شرایط', 'conditions-info' => 'شرایط قوانینی هستند که سناریوها را بررسی می‌کنند و در مواقع خاص فعال می‌شوند.', 'actions-info' => 'یک اقدام نه تنها بار کاری را کاهش می‌دهد بلکه فرآیند اتوماسیون CRM را بسیار آسان‌تر می‌کند.', 'value' => 'مقدار', 'condition-type' => 'نوع شرط', 'all-condition-are-true' => 'تمام شرایط صحیح هستند', 'any-condition-are-true' => 'هر شرطی صحیح است', 'add-condition' => 'افزودن شرط', 'add-action' => 'افزودن اقدام', 'yes' => 'بله', 'no' => 'خیر', 'email' => 'ایمیل', 'is-equal-to' => 'برابر است با', 'is-not-equal-to' => 'برابر نیست با', 'equals-or-greater-than' => 'برابر یا بزرگ‌تر از', 'equals-or-less-than' => 'برابر یا کوچک‌تر از', 'greater-than' => 'بزرگ‌تر از', 'less-than' => 'کوچک‌تر از', 'contain' => 'شامل', 'contains' => 'شامل می‌شود', 'does-not-contain' => 'شامل نمی‌شود', ], ], 'webforms' => [ 'index' => [ 'title' => 'فرم‌های وب', 'create-btn' => 'ایجاد فرم وب', 'create-success' => 'فرم وب با موفقیت ایجاد شد.', 'update-success' => 'فرم وب با موفقیت به‌روزرسانی شد.', 'delete-success' => 'فرم وب با موفقیت حذف شد.', 'delete-failed' => 'فرم وب قابل حذف نیست.', 'datagrid' => [ 'id' => 'شناسه', 'title' => 'عنوان', 'edit' => 'ویرایش', 'delete' => 'حذف', ], ], 'create' => [ 'title' => 'ایجاد فرم وب', 'add-attribute-btn' => 'افزودن دکمه ویژگی', 'attribute-label-color' => 'رنگ برچسب ویژگی', 'attributes' => 'ویژگی‌ها', 'attributes-info' => 'ویژگی‌های سفارشی را به فرم اضافه کنید.', 'background-color' => 'رنگ پس‌زمینه', 'create-lead' => 'ایجاد سرب', 'customize-webform' => 'سفارشی‌سازی فرم وب', 'customize-webform-info' => 'فرم وب خود را با رنگ‌های انتخابی سفارشی‌سازی کنید.', 'description' => 'توضیحات', 'display-custom-message' => 'نمایش پیام سفارشی', 'form-background-color' => 'رنگ پس‌زمینه فرم', 'form-submit-btn-color' => 'رنگ دکمه ارسال فرم', 'form-submit-button-color' => 'رنگ دکمه ارسال فرم', 'form-title-color' => 'رنگ عنوان فرم', 'general' => 'عمومی', 'leads' => 'سرنخ‌ها', 'person' => 'شخص', 'save-btn' => 'ذخیره فرم وب', 'submit-button-label' => 'برچسب دکمه ارسال', 'submit-success-action' => 'عملکرد موفقیت آمیز ارسال', 'redirect-to-url' => 'انتقال به آدرس', 'choose-value' => 'انتخاب مقدار', 'select-file' => 'انتخاب فایل', 'select-image' => 'انتخاب تصویر', 'enter-value' => 'مقدار را وارد کنید', ], 'edit' => [ 'add-attribute-btn' => 'افزودن دکمه ویژگی', 'attribute-label-color' => 'رنگ برچسب ویژگی', 'attributes' => 'ویژگی‌ها', 'attributes-info' => 'ویژگی‌های سفارشی را به فرم اضافه کنید.', 'background-color' => 'رنگ پس‌زمینه', 'choose-value' => 'انتخاب مقدار', 'code-snippet' => 'کد نمونه', 'copied' => 'کپی شد', 'copy' => 'کپی', 'create-lead' => 'ایجاد سرب', 'customize-webform' => 'سفارشی‌سازی فرم وب', 'customize-webform-info' => 'فرم وب خود را با رنگ‌های انتخابی سفارشی‌سازی کنید.', 'description' => 'توضیحات', 'display-custom-message' => 'نمایش پیام سفارشی', 'embed' => 'گنجاندن', 'enter-value' => 'مقدار را وارد کنید', 'form-background-color' => 'رنگ پس‌زمینه فرم', 'form-submit-btn-color' => 'رنگ دکمه ارسال فرم', 'form-submit-button-color' => 'رنگ دکمه ارسال فرم', 'form-title-color' => 'رنگ عنوان فرم', 'general' => 'عمومی', 'leads' => 'سرنخ‌ها', 'person' => 'شخص', 'preview' => 'پیش‌نمایش', 'public-url' => 'آدرس عمومی', 'redirect-to-url' => 'انتقال به آدرس', 'save-btn' => 'ذخیره فرم وب', 'select-file' => 'انتخاب فایل', 'select-image' => 'انتخاب تصویر', 'submit-button-label' => 'برچسب دکمه ارسال', 'submit-success-action' => 'عملکرد موفقیت آمیز ارسال', 'title' => 'ویرایش فرم وب', ], ], 'email-template' => [ 'index' => [ 'create-btn' => 'ایجاد قالب ایمیل', 'title' => 'قالب‌های ایمیل', 'create-success' => 'قالب ایمیل با موفقیت ایجاد شد.', 'update-success' => 'قالب ایمیل با موفقیت به‌روزرسانی شد.', 'delete-success' => 'قالب ایمیل با موفقیت حذف شد.', 'delete-failed' => 'قالب ایمیل قابل حذف نیست.', 'datagrid' => [ 'delete' => 'حذف', 'edit' => 'ویرایش', 'id' => 'شناسه', 'name' => 'نام', 'subject' => 'موضوع', ], ], 'create' => [ 'title' => 'ایجاد قالب ایمیل', 'save-btn' => 'ذخیره قالب ایمیل', 'email-template' => 'قالب ایمیل', 'subject' => 'موضوع', 'content' => 'محتوا', 'subject-placeholders' => 'متغیرهای موضوع', 'general' => 'عمومی', 'name' => 'نام', ], 'edit' => [ 'title' => 'ویرایش قالب ایمیل', 'save-btn' => 'ذخیره قالب ایمیل', 'email-template' => 'قالب ایمیل', 'subject' => 'موضوع', 'content' => 'محتوا', 'subject-placeholders' => 'متغیرهای موضوع', 'general' => 'عمومی', 'name' => 'نام', ], ], 'marketing' => [ 'events' => [ 'index' => [ 'create-btn' => 'ایجاد رویداد', 'title' => 'رویدادها', 'create-success' => 'رویداد با موفقیت ایجاد شد.', 'update-success' => 'رویداد با موفقیت به‌روزرسانی شد.', 'delete-success' => 'رویداد با موفقیت حذف شد.', 'delete-failed' => 'رویداد قابل حذف نیست.', 'mass-delete-success' => 'رویدادها با موفقیت حذف شدند', 'delete-failed-associated-campaigns' => 'رویداد قابل حذف نیست زیرا با کمپین‌های موجود مرتبط است. لطفاً ابتدا آن‌ها را جدا کرده یا به‌روزرسانی کنید.', 'datagrid' => [ 'delete' => 'حذف', 'edit' => 'ویرایش', 'id' => 'شناسه', 'name' => 'نام', 'description' => 'توضیحات', 'date' => 'تاریخ', ], 'create' => [ 'title' => 'ایجاد رویداد', 'name' => 'نام', 'date' => 'تاریخ', 'description' => 'توضیحات', 'save-btn' => 'ذخیره رویداد', ], 'edit' => [ 'title' => 'ویرایش رویداد', ], ], ], 'campaigns' => [ 'index' => [ 'create-btn' => 'ایجاد کمپین', 'title' => 'کمپین‌ها', 'create-success' => 'کمپین با موفقیت ایجاد شد.', 'update-success' => 'کمپین با موفقیت به‌روزرسانی شد.', 'delete-success' => 'کمپین با موفقیت حذف شد.', 'delete-failed' => 'کمپین قابل حذف نیست.', 'mass-delete-success' => 'کمپین‌ها با موفقیت حذف شدند', 'datagrid' => [ 'id' => 'شناسه', 'name' => 'نام', 'subject' => 'موضوع', 'status' => 'وضعیت', 'active' => 'فعال', 'inactive' => 'غیرفعال', 'edit' => 'ویرایش', 'delete' => 'حذف', ], 'create' => [ 'title' => 'ایجاد کمپین', 'name' => 'نام', 'type' => 'نوع', 'subject' => 'موضوع', 'event' => 'رویداد', 'email-template' => 'قالب ایمیل', 'status' => 'وضعیت', ], 'edit' => [ 'title' => 'ویرایش کمپین', ], ], ], ], 'tags' => [ 'index' => [ 'create-btn' => 'ایجاد برچسب', 'title' => 'برچسب‌ها', 'create-success' => 'برچسب با موفقیت ایجاد شد.', 'update-success' => 'برچسب با موفقیت به‌روزرسانی شد.', 'delete-success' => 'برچسب با موفقیت حذف شد.', 'delete-failed' => 'برچسب قابل حذف نیست.', 'datagrid' => [ 'delete' => 'حذف', 'edit' => 'ویرایش', 'id' => 'شناسه', 'name' => 'نام', 'users' => 'کاربران', 'created-at' => 'تاریخ ایجاد', ], 'create' => [ 'name' => 'نام', 'save-btn' => 'ذخیره برچسب', 'title' => 'ایجاد برچسب', 'color' => 'رنگ', ], 'edit' => [ 'title' => 'ویرایش برچسب', ], ], ], 'users' => [ 'index' => [ 'create-btn' => 'ایجاد کاربر', 'create-success' => 'کاربر با موفقیت ایجاد شد.', 'delete-failed' => 'کاربر قابل حذف نیست.', 'delete-success' => 'کاربر با موفقیت حذف شد.', 'last-delete-error' => 'حداقل یک کاربر لازم است.', 'mass-delete-failed' => 'کاربران قابل حذف نیستند.', 'mass-delete-success' => 'کاربران با موفقیت حذف شدند.', 'mass-update-failed' => 'کاربران قابل به‌روزرسانی نیستند.', 'mass-update-success' => 'کاربران با موفقیت به‌روزرسانی شدند.', 'title' => 'کاربران', 'update-success' => 'کاربر با موفقیت به‌روزرسانی شد.', 'user-define-error' => 'قابل حذف نیست.', 'active' => 'فعال', 'inactive' => 'غیرفعال', 'datagrid' => [ 'active' => 'فعال', 'created-at' => 'تاریخ ایجاد', 'delete' => 'حذف', 'edit' => 'ویرایش', 'email' => 'ایمیل', 'id' => 'شناسه', 'inactive' => 'غیرفعال', 'name' => 'نام', 'status' => 'وضعیت', 'update-status' => 'به‌روزرسانی وضعیت', 'users' => 'کاربران', ], 'create' => [ 'confirm-password' => 'تأیید رمز عبور', 'email' => 'ایمیل', 'general' => 'عمومی', 'global' => 'سراسری', 'group' => 'گروه', 'individual' => 'فردی', 'name' => 'نام', 'password' => 'رمز عبور', 'permission' => 'مجوز', 'role' => 'نقش', 'save-btn' => 'ذخیره کاربر', 'status' => 'وضعیت', 'title' => 'ایجاد کاربر', 'view-permission' => 'مجوز مشاهده', 'select-at-lest-one-group' => 'Select at least one group', ], 'edit' => [ 'title' => 'ویرایش کاربر', ], ], ], 'pipelines' => [ 'index' => [ 'title' => 'پایپ‌لاین‌ها', 'create-btn' => 'ایجاد پایپ‌لاین', 'create-success' => 'پایپ‌لاین با موفقیت ایجاد شد.', 'update-success' => 'پایپ‌لاین با موفقیت به‌روزرسانی شد.', 'default-required' => 'حداقل یک پایپ‌لاین پیش‌فرض مورد نیاز است.', 'delete-success' => 'پایپ‌لاین با موفقیت حذف شد.', 'delete-failed' => 'پایپ‌لاین قابل حذف نیست.', 'default-delete-error' => 'پایپ‌لاین پیش‌فرض قابل حذف نیست.', 'datagrid' => [ 'delete' => 'حذف', 'edit' => 'ویرایش', 'id' => 'شناسه', 'is-default' => 'پیش‌فرض', 'name' => 'نام', 'no' => 'خیر', 'rotten-days' => 'روزهای فاسد', 'yes' => 'بله', ], ], 'create' => [ 'title' => 'ایجاد پایپ‌لاین', 'save-btn' => 'ذخیره پایپ‌لاین', 'name' => 'نام', 'rotten-days' => 'روزهای فاسد', 'mark-as-default' => 'علامت‌گذاری به‌عنوان پیش‌فرض', 'general' => 'عمومی', 'probability' => 'احتمال(%)', 'new-stage' => 'جدید', 'won-stage' => 'برنده', 'lost-stage' => 'بازنده', 'stage-btn' => 'افزودن مرحله', 'stages' => 'مراحل', 'duplicate-name' => 'فیلد "نام" نمی‌تواند تکراری باشد', 'delete-stage' => 'حذف مرحله', 'add-new-stages' => 'افزودن مراحل جدید', 'add-stage-info' => 'مرحله جدیدی برای پایپ‌لاین خود اضافه کنید', 'newly-added' => 'تازه اضافه شده', 'stage-delete-success' => 'مرحله با موفقیت حذف شد', ], 'edit' => [ 'title' => 'ویرایش پایپ‌لاین', 'save-btn' => 'ذخیره پایپ‌لاین', 'name' => 'نام', 'rotten-days' => 'روزهای فاسد', 'mark-as-default' => 'علامت‌گذاری به‌عنوان پیش‌فرض', 'general' => 'عمومی', 'probability' => 'احتمال(%)', 'new-stage' => 'جدید', 'won-stage' => 'برنده', 'lost-stage' => 'بازنده', 'stage-btn' => 'افزودن مرحله', 'stages' => 'مراحل', 'duplicate-name' => 'فیلد "نام" نمی‌تواند تکراری باشد', 'delete-stage' => 'حذف مرحله', 'add-new-stages' => 'افزودن مراحل جدید', 'add-stage-info' => 'مرحله جدیدی برای پایپ‌لاین خود اضافه کنید', 'stage-delete-success' => 'مرحله با موفقیت حذف شد', ], ], 'webhooks' => [ 'index' => [ 'title' => 'وب‌هوک‌ها', 'create-btn' => 'ایجاد وب‌هوک', 'create-success' => 'وب‌هوک با موفقیت ایجاد شد.', 'update-success' => 'وب‌هوک با موفقیت به‌روزرسانی شد.', 'delete-success' => 'وب‌هوک با موفقیت حذف شد.', 'delete-failed' => 'وب‌هوک قابل حذف نیست.', 'datagrid' => [ 'id' => 'شناسه', 'delete' => 'حذف', 'edit' => 'ویرایش', 'name' => 'نام', 'entity-type' => 'نوع موجودیت', 'end-point' => 'پایان نقطه', ], ], 'create' => [ 'title' => 'ایجاد وب‌هوک', 'save-btn' => 'ذخیره وب‌هوک', 'info' => 'جزئیات وب‌هوک‌ها را وارد کنید', 'url-and-parameters' => 'آدرس و پارامترها', 'method' => 'متد', 'post' => 'پست', 'put' => 'پوت', 'url-endpoint' => 'آدرس پایان', 'parameters' => 'پارامترها', 'add-new-parameter' => 'افزودن پارامتر جدید', 'url-preview' => 'پیش‌نمایش آدرس:', 'headers' => 'هدینگ‌ها', 'add-new-header' => 'افزودن هدینگ جدید', 'body' => 'بدن', 'default' => 'پیش‌فرض', 'x-www-form-urlencoded' => 'x-www-form-urlencoded', 'key-and-value' => 'کلید و مقدار', 'add-new-payload' => 'اضافه کردن بار جدید', 'raw' => 'خام', 'general' => 'عمومی', 'name' => 'نام', 'entity-type' => 'نوع موجودیت', 'insert-placeholder' => 'درج نشانه‌گذاری', 'description' => 'توضیحات', 'json' => 'جی‌سان', 'text' => 'متن', ], 'edit' => [ 'title' => 'ویرایش وب‌هوک', 'edit-btn' => 'ذخیره وب‌هوک', 'save-btn' => 'ذخیره وب‌هوک', 'info' => 'جزئیات وب‌هوک‌ها را وارد کنید', 'url-and-parameters' => 'آدرس و پارامترها', 'method' => 'متد', 'post' => 'پست', 'put' => 'پوت', 'url-endpoint' => 'آدرس پایان', 'parameters' => 'پارامترها', 'add-new-parameter' => 'افزودن پارامتر جدید', 'url-preview' => 'پیش‌نمایش آدرس:', 'headers' => 'هدینگ‌ها', 'add-new-header' => 'افزودن هدینگ جدید', 'body' => 'بدن', 'default' => 'پیش‌فرض', 'x-www-form-urlencoded' => 'x-www-form-urlencoded', 'key-and-value' => 'کلید و مقدار', 'add-new-payload' => 'اضافه کردن بار جدید', 'raw' => 'خام', 'general' => 'عمومی', 'name' => 'نام', 'entity-type' => 'نوع موجودیت', 'insert-placeholder' => 'درج نشانه‌گذاری', 'description' => 'توضیحات', 'json' => 'جی‌سان', 'text' => 'متن', ], ], 'warehouses' => [ 'index' => [ 'title' => 'انبارها', 'create-btn' => 'ایجاد انبار', 'create-success' => 'انبار با موفقیت ایجاد شد.', 'name-exists' => 'نام انبار قبلاً موجود است.', 'update-success' => 'انبار با موفقیت به‌روزرسانی شد.', 'delete-success' => 'انبار با موفقیت حذف شد.', 'delete-failed' => 'انبار قابل حذف نیست.', 'datagrid' => [ 'id' => 'شناسه', 'name' => 'نام', 'contact-name' => 'نام تماس', 'delete' => 'حذف', 'edit' => 'ویرایش', 'view' => 'مشاهده', 'created-at' => 'تاریخ ایجاد', 'products' => 'محصولات', 'contact-emails' => 'ایمیل‌های تماس', 'contact-numbers' => 'شماره‌های تماس', ], ], 'create' => [ 'title' => 'ایجاد انبار', 'save-btn' => 'ذخیره انبار', 'contact-info' => 'اطلاعات تماس', ], 'edit' => [ 'title' => 'ویرایش انبار', 'save-btn' => 'ذخیره انبار', 'contact-info' => 'اطلاعات تماس', ], 'view' => [ 'all' => 'همه', 'notes' => 'یادداشت‌ها', 'files' => 'فایل‌ها', 'location' => 'مکان', 'change-logs' => 'لاگ‌های تغییرات', 'locations' => [ 'action' => 'عملیات', 'add-location' => 'افزودن مکان', 'create-success' => 'مکان با موفقیت ایجاد شد.', 'delete' => 'حذف', 'delete-failed' => 'مکان قابل حذف نیست.', 'delete-success' => 'مکان با موفقیت حذف شد.', 'name' => 'نام', 'save-btn' => 'ذخیره', ], 'general-information' => [ 'title' => 'اطلاعات عمومی', ], 'contact-information' => [ 'title' => 'اطلاعات تماس', ], ], ], 'attributes' => [ 'index' => [ 'title' => 'ویژگی‌ها', 'create-btn' => 'ایجاد ویژگی', 'create-success' => 'ویژگی با موفقیت ایجاد شد.', 'update-success' => 'ویژگی با موفقیت به‌روزرسانی شد.', 'delete-success' => 'ویژگی با موفقیت حذف شد.', 'delete-failed' => 'ویژگی قابل حذف نیست.', 'user-define-error' => 'قادر به حذف ویژگی سیستم نیست.', 'mass-delete-failed' => 'ویژگی‌های سیستمی قابل حذف نیستند.', 'datagrid' => [ 'yes' => 'بله', 'no' => 'خیر', 'id' => 'شناسه', 'code' => 'کد', 'name' => 'نام', 'entity-type' => 'نوع موجودیت', 'type' => 'نوع', 'is-default' => 'پیش‌فرض است', 'edit' => 'ویرایش', 'delete' => 'حذف', 'entity-types' => [ 'leads' => 'سرنخ‌ها', 'organizations' => 'سازمان‌ها', 'persons' => 'افراد', 'products' => 'محصولات', 'quotes' => 'نقل‌قول‌ها', 'warehouses' => 'انبارها', ], 'types' => [ 'text' => 'متن', 'textarea' => 'ناحیه متنی', 'price' => 'قیمت', 'boolean' => 'بولی', 'select' => 'انتخاب', 'multiselect' => 'چند انتخابی', 'checkbox' => 'چک‌باکس', 'email' => 'ایمیل', 'address' => 'آدرس', 'phone' => 'تلفن', 'lookup' => 'جستجو', 'datetime' => 'تاریخ و زمان', 'date' => 'تاریخ', 'image' => 'تصویر', 'file' => 'فایل', ], ], ], 'create' => [ 'title' => 'ایجاد ویژگی', 'save-btn' => 'ذخیره ویژگی', 'code' => 'کد', 'name' => 'نام', 'entity-type' => 'نوع موجودیت', 'type' => 'نوع', 'validations' => 'اعتبارسنجی‌ها', 'is-required' => 'الزامی است', 'input-validation' => 'اعتبارسنجی ورودی', 'is-unique' => 'منحصربه‌فرد است', 'labels' => 'برچسب‌ها', 'general' => 'عمومی', 'numeric' => 'عددی', 'decimal' => 'اعشاری', 'url' => 'آدرس', 'options' => 'گزینه‌ها', 'option-type' => 'نوع گزینه', 'lookup-type' => 'نوع جستجو', 'add-option' => 'افزودن گزینه', 'save-option' => 'ذخیره گزینه', 'option-name' => 'نام گزینه', 'add-attribute-options' => 'افزودن گزینه‌های ویژگی', 'text' => 'متن', 'textarea' => 'میدان متن', 'price' => 'قیمت', 'boolean' => 'بولی', 'select' => 'انتخاب', 'multiselect' => 'چند انتخابی', 'email' => 'ایمیل', 'address' => 'آدرس', 'phone' => 'تلفن', 'datetime' => 'تاریخ و زمان', 'date' => 'تاریخ', 'image' => 'تصویر', 'file' => 'فایل', 'lookup' => 'جستجو', 'entity_type' => 'نوع موجودیت', 'checkbox' => 'چک باکس', 'is_required' => 'الزامی است', 'is_unique' => 'منحصربه‌فرد است', 'actions' => 'عملیات', ], 'edit' => [ 'actions' => 'عملیات', 'add-attribute-options' => 'افزودن گزینه‌های ویژگی', 'add-option' => 'افزودن گزینه', 'address' => 'آدرس', 'boolean' => 'بولی', 'checkbox' => 'چک باکس', 'code' => 'کد', 'date' => 'تاریخ', 'datetime' => 'تاریخ و زمان', 'decimal' => 'اعشاری', 'email' => 'ایمیل', 'entity-type' => 'نوع موجودیت', 'entity_type' => 'نوع موجودیت', 'file' => 'فایل', 'general' => 'عمومی', 'image' => 'تصویر', 'input-validation' => 'اعتبارسنجی ورودی', 'is-required' => 'الزامی است', 'is-unique' => 'منحصربه‌فرد است', 'is_required' => 'الزامی است', 'is_unique' => 'منحصربه‌فرد است', 'labels' => 'برچسب‌ها', 'lookup' => 'جستجو', 'lookup-type' => 'نوع جستجو', 'multiselect' => 'چند انتخابی', 'name' => 'نام', 'numeric' => 'عددی', 'option-deleted' => 'Attribute Option is deleted successfully', 'option-name' => 'نام گزینه', 'option-type' => 'نوع گزینه', 'options' => 'گزینه‌ها', 'phone' => 'تلفن', 'price' => 'قیمت', 'save-btn' => 'ذخیره ویژگی', 'save-option' => 'ذخیره گزینه', 'select' => 'انتخاب', 'text' => 'متن', 'textarea' => 'میدان متن', 'title' => 'ویرایش ویژگی', 'type' => 'نوع', 'url' => 'آدرس', 'validations' => 'اعتبارسنجی‌ها', ], ], 'data-transfer' => [ 'imports' => [ 'create' => [ 'action' => 'عملیات', 'allowed-errors' => 'خطاهای مجاز', 'back-btn' => 'بازگشت', 'create-update' => 'ایجاد/به‌روزرسانی', 'delete' => 'حذف', 'download-sample' => 'دانلود نمونه', 'field-separator' => 'جداکننده فیلد', 'file' => 'فایل', 'general' => 'عمومی', 'images-directory' => 'مسیر پوشه تصاویر', 'process-in-queue' => 'پردازش در صف', 'results' => 'نتایج', 'save-btn' => 'ذخیره واردات', 'settings' => 'تنظیمات', 'skip-errors' => 'رد کردن خطاها', 'stop-on-errors' => 'توقف در صورت خطا', 'title' => 'ایجاد واردات', 'type' => 'نوع', 'validation-strategy' => 'استراتژی اعتبارسنجی', ], 'edit' => [ 'action' => 'عملیات', 'allowed-errors' => 'خطاهای مجاز', 'back-btn' => 'بازگشت', 'create-update' => 'ایجاد/به‌روزرسانی', 'delete' => 'حذف', 'download-sample' => 'دانلود نمونه', 'field-separator' => 'جداکننده فیلد', 'file' => 'فایل', 'general' => 'عمومی', 'images-directory' => 'مسیر پوشه تصاویر', 'process-in-queue' => 'پردازش در صف', 'results' => 'نتایج', 'save-btn' => 'ذخیره واردات', 'settings' => 'تنظیمات', 'skip-errors' => 'رد کردن خطاها', 'stop-on-errors' => 'توقف در صورت خطا', 'title' => 'ویرایش واردات', 'type' => 'نوع', 'validation-strategy' => 'استراتژی اعتبارسنجی', ], 'index' => [ 'button-title' => 'ایجاد واردات', 'title' => 'واردات‌ها', 'datagrid' => [ 'actions' => 'عملیات', 'completed-at' => 'تکمیل شده در', 'created' => 'ایجاد شده', 'delete' => 'حذف', 'deleted' => 'حذف شده', 'edit' => 'ویرایش', 'error-file' => 'فایل خطا', 'id' => 'شناسه', 'started-at' => 'شروع شده در', 'state' => 'وضعیت', 'summary' => 'خلاصه', 'type' => 'نوع', 'updated' => 'به‌روزرسانی شده', 'uploaded-file' => 'فایل آپلود شده', ], ], 'import' => [ 'back-btn' => 'بازگشت', 'completed-batches' => 'کل دسته‌های تکمیل شده:', 'download-error-report' => 'دانلود گزارش کامل', 'edit-btn' => 'ویرایش', 'imported-info' => 'تبریک! واردات شما با موفقیت انجام شد.', 'importing-info' => 'واردات در حال انجام است', 'indexing-info' => 'ایندکس‌گذاری منابع (قیمت، موجودی و Elastic Search) در حال پیشرفت است', 'linking-info' => 'پیونددهی منابع در حال انجام است', 'progress' => 'پیشرفت:', 'title' => 'واردات', 'total-batches' => 'کل دسته‌ها:', 'total-created' => 'کل رکوردهای ایجاد شده:', 'total-deleted' => 'کل رکوردهای حذف شده:', 'total-errors' => 'کل خطاها:', 'total-invalid-rows' => 'کل ردیف‌های نامعتبر:', 'total-rows-processed' => 'کل ردیف‌های پردازش شده:', 'total-updated' => 'کل رکوردهای به‌روزرسانی شده:', 'validate' => 'اعتبارسنجی', 'validate-info' => 'برای بررسی واردات خود، روی "اعتبارسنجی داده‌ها" کلیک کنید.', 'validating-info' => 'خواندن و اعتبارسنجی داده‌ها آغاز شده است', 'validation-failed-info' => 'واردات شما نامعتبر است. لطفاً خطاهای زیر را رفع کرده و دوباره تلاش کنید.', 'validation-success-info' => 'واردات شما معتبر است. برای شروع فرآیند واردات، روی "واردات" کلیک کنید.', ], 'create-success' => 'واردات با موفقیت ایجاد شد.', 'delete-failed' => 'حذف واردات به طور غیرمنتظره‌ای ناکام ماند.', 'delete-success' => 'واردات با موفقیت حذف شد.', 'not-valid' => 'واردات نامعتبر است', 'nothing-to-import' => 'هیچ منبعی برای واردات وجود ندارد.', 'setup-queue-error' => 'لطفاً درایور صف خود را به "database" یا "redis" تغییر دهید تا فرآیند واردات شروع شود.', 'update-success' => 'واردات با موفقیت به‌روزرسانی شد.', ], ], ], 'activities' => [ 'index' => [ 'title' => 'فعالیت‌ها', 'datagrid' => [ 'comment' => 'نظر', 'created_at' => 'تاریخ ایجاد', 'created_by' => 'ایجاد شده توسط', 'edit' => 'ویرایش', 'id' => 'شناسه', 'done' => 'انجام شده', 'not-done' => 'انجام نشده', 'lead' => 'سرنخ', 'mass-delete' => 'حذف انبوه', 'mass-update' => 'به‌روزرسانی انبوه', 'schedule-from' => 'برنامه‌ریزی از', 'schedule-to' => 'برنامه‌ریزی تا', 'schedule_from' => 'برنامه‌ریزی از', 'schedule_to' => 'برنامه‌ریزی تا', 'title' => 'عنوان', 'is_done' => 'انجام شده', 'type' => 'نوع', 'update' => 'به‌روزرسانی', 'call' => 'تماس', 'meeting' => 'جلسه', 'lunch' => 'ناهار', ], ], 'edit' => [ 'title' => 'ویرایش فعالیت', 'back-btn' => 'برگشت', 'save-btn' => 'ذخیره فعالیت', 'type' => 'نوع فعالیت', 'call' => 'تماس', 'meeting' => 'جلسه', 'lunch' => 'ناهار', 'schedule_to' => 'برنامه‌ریزی تا', 'schedule_from' => 'برنامه‌ریزی از', 'location' => 'مکان', 'comment' => 'نظر', 'lead' => 'سرنخ', 'participants' => 'شرکت‌کنندگان', 'general' => 'عمومی', 'persons' => 'افراد', 'no-result-found' => 'سوابقی یافت نشد.', 'users' => 'کاربران', ], 'updated' => 'به‌روزرسانی شد :attribute', 'created' => 'ایجاد شد', 'duration-overlapping' => 'شرکت‌کنندگان در این زمان جلسه دیگری دارند. آیا می‌خواهید ادامه دهید؟', 'create-success' => 'فعالیت با موفقیت ایجاد شد.', 'update-success' => 'فعالیت با موفقیت به‌روزرسانی شد.', 'overlapping-error' => 'شرکت‌کنندگان در این زمان جلسه دیگری دارند.', 'destroy-success' => 'فعالیت با موفقیت حذف شد.', 'delete-failed' => 'امکان حذف فعالیت وجود ندارد.', 'mass-update-success' => 'فعالیت‌ها با موفقیت به‌روزرسانی شدند.', 'mass-destroy-success' => 'فعالیت‌ها با موفقیت حذف شدند.', 'mass-delete-failed' => 'امکان حذف فعالیت‌ها وجود ندارد.', ], 'mail' => [ 'index' => [ 'compose' => 'نوشتن', 'draft' => 'پیش‌نویس', 'inbox' => 'صندوق ورودی', 'outbox' => 'صندوق خروجی', 'sent' => 'ارسال شده', 'trash' => 'سطل زباله', 'compose-mail-btn' => 'نوشتن ایمیل', 'btn' => 'ایمیل', 'mail' => [ 'title' => 'نوشتن ایمیل', 'to' => 'به', 'enter-emails' => 'برای اضافه کردن ایمیل‌ها، کلید Enter را فشار دهید', 'cc' => 'CC', 'bcc' => 'BCC', 'subject' => 'موضوع', 'send-btn' => 'ارسال', 'message' => 'پیام', 'draft' => 'پیش‌نویس', ], 'datagrid' => [ 'id' => 'شناسه', 'from' => 'از', 'to' => 'به', 'subject' => 'موضوع', 'tags' => 'برچسب‌ها', 'content' => 'محتوا', 'attachments' => 'پیوست‌ها', 'date' => 'تاریخ', 'move-to-inbox' => 'انتقال به صندوق ورودی', 'move-to-trash' => 'به سطل زباله منتقل شد', 'edit' => 'ویرایش', 'view' => 'نمایش', 'delete' => 'حذف', ], ], 'create-success' => 'ایمیل با موفقیت ارسال شد.', 'update-success' => 'ایمیل با موفقیت به‌روزرسانی شد.', 'mass-update-success' => 'ایمیل‌ها با موفقیت به‌روزرسانی شدند.', 'delete-success' => 'ایمیل با موفقیت حذف شد.', 'delete-failed' => 'ایمیل قابل حذف نیست.', 'invalid-route' => 'مسیر نامعتبر برای ایمیل.', 'unauthorized' => 'این عمل مجاز نیست.', 'view' => [ 'title' => 'ایمیل‌ها', 'subject' => ':subject', 'link-mail' => 'لینک ایمیل', 'to' => 'به', 'cc' => 'CC', 'bcc' => 'BCC', 'reply' => 'پاسخ', 'reply-all' => 'پاسخ به همه', 'forward' => 'فوروارد', 'delete' => 'حذف', 'enter-mails' => 'شناسه ایمیل را وارد کنید', 'rotten-days' => 'سرنخ به مدت :days روز خراب شده است', 'search-an-existing-lead' => 'جستجوی سرنخ موجود', 'search-an-existing-contact' => 'جستجوی مخاطب موجود', 'message' => 'پیام', 'add-attachments' => 'اضافه کردن پیوست‌ها', 'discard' => 'لغو', 'send' => 'ارسال', 'no-result-found' => 'نتیجه‌ای یافت نشد', 'add-new-contact' => 'افزودن مخاطب جدید', 'description' => 'توضیحات', 'search' => 'جستجو...', 'add-new-lead' => 'افزودن سرنخ جدید', 'create-new-contact' => 'ایجاد مخاطب جدید', 'save-contact' => 'ذخیره مخاطب', 'create-lead' => 'ایجاد سرنخ', 'linked-contact' => 'مخاطب مرتبط', 'link-to-contact' => 'لینک به مخاطب', 'link-to-lead' => 'لینک به سرنخ', 'linked-lead' => 'سرنخ مرتبط', 'lead-details' => 'جزئیات سرنخ', 'contact-person' => 'شخص تماس', 'product' => 'محصول', 'tags' => [ 'create-success' => 'برچسب با موفقیت ایجاد شد.', 'destroy-success' => 'برچسب با موفقیت حذف شد.', ], ], ], 'common' => [ 'custom-attributes' => [ 'add-more' => 'افزودن بیشتر', 'address' => 'آدرس', 'city' => 'شهر', 'contact' => 'شماره‌های تماس', 'country' => 'کشور', 'email' => 'ایمیل', 'home' => 'خانه', 'postcode' => 'کد پستی', 'save' => 'ذخیره', 'select' => 'انتخاب', 'select-country' => 'انتخاب کشور', 'select-state' => 'انتخاب ایالت', 'state' => 'ایالت', 'update-contact-title' => 'به‌روزرسانی شماره‌های تماس', 'update-emails-title' => 'به‌روزرسانی ایمیل‌های تماس', 'work' => 'محل کار', ], ], 'leads' => [ 'create-success' => 'سرنخ با موفقیت ایجاد شد.', 'update-success' => 'سرنخ با موفقیت به‌روزرسانی شد.', 'update-failed' => 'سرنخ‌ها قابل حذف نیستند.', 'destroy-success' => 'سرنخ با موفقیت حذف شد.', 'destroy-failed' => 'سرنخ قابل حذف نیست.', 'file' => [ 'data-not-found' => 'داده‌ای یافت نشد.', 'empty-content' => 'محتوای PDF خالی است یا نمی‌توان آن را استخراج کرد.', 'failed-extract' => 'استخراج متن از فایل ناموفق بود.', 'insufficient-info' => 'به دلیل اطلاعات ناکافی، در حال حاضر نمی‌توانیم درخواست شما را پردازش کنیم.', 'invalid-base64' => 'فرمت base64 نامعتبر است.', 'invalid-format' => 'فرمت JSON نامعتبر است.', 'invalid-response' => 'فرمت پاسخ هوش مصنوعی نامعتبر است.', 'missing-api-key' => 'کلید API یا پیکربندی مدل مفقود است.', 'not-found' => 'فایل یافت نشد.', 'recursive-call' => 'تماس بازگشتی شناسایی شد.', 'text-generation-failed' => 'استخراج متن ناموفق بود. فایل ممکن است خالی یا غیرقابل خواندن باشد.', ], 'index' => [ 'title' => 'سرنخ‌ها', 'create-btn' => 'ایجاد سرنخ', 'datagrid' => [ 'id' => 'شناسه', 'sales-person' => 'کارشناس فروش', 'subject' => 'موضوع', 'source' => 'منبع', 'lead-value' => 'ارزش سرنخ', 'lead-type' => 'نوع سرنخ', 'tag-name' => 'نام برچسب', 'contact-person' => 'شخص تماس', 'stage' => 'مرحله', 'rotten-lead' => 'سرنخ خراب شده', 'date-to' => 'تاریخ تا', 'created-at' => 'تاریخ ایجاد', 'no' => 'خیر', 'yes' => 'بله', 'delete' => 'حذف', 'mass-delete' => 'حذف گروهی', 'mass-update' => 'به‌روزرسانی گروهی', ], 'kanban' => [ 'rotten-days' => 'سرنخ برای :days روز خراب شده است', 'empty-list' => 'لیست سرنخ‌های شما خالی است', 'empty-list-description' => 'یک سرنخ ایجاد کنید تا اهداف خود را سازماندهی کنید.', 'create-lead-btn' => 'ایجاد سرنخ', 'columns' => [ 'contact-person' => 'شخص تماس', 'id' => 'شناسه', 'lead-type' => 'نوع سرنخ', 'lead-value' => 'ارزش سرنخ', 'sales-person' => 'کارشناس فروش', 'source' => 'منبع', 'title' => 'عنوان', 'tags' => 'برچسب‌ها', 'expected-close-date' => 'تاریخ بسته شدن مورد انتظار', 'created-at' => 'تاریخ ایجاد', ], 'toolbar' => [ 'search' => [ 'title' => 'جستجو بر اساس عنوان', ], 'filters' => [ 'apply-filters' => 'اعمال فیلترها', 'clear-all' => 'پاک کردن همه', 'filter' => 'فیلتر', 'filters' => 'فیلترها', 'from' => 'از', 'select' => 'انتخاب', 'to' => 'تا', ], ], ], 'view-switcher' => [ 'all-pipelines' => 'تمام خطوط تولید', 'create-new-pipeline' => 'ایجاد خط تولید جدید', ], 'upload' => [ 'create-lead' => 'ایجاد سرنخ با استفاده از هوش مصنوعی', 'file' => 'آپلود فایل', 'file-info' => 'فقط فایل‌های با فرمت pdf, bmp, jpg, jpeg, png پذیرفته می‌شوند.', 'file-required' => 'لطفاً حداقل یک فایل معتبر برای ادامه انتخاب کنید.', 'save-btn' => 'ذخیره', 'upload-file' => 'بارگذاری فایل', ], ], 'create' => [ 'title' => 'ایجاد سرنخ', 'save-btn' => 'ذخیره', 'details' => 'جزئیات', 'details-info' => 'اطلاعات پایه سرنخ را وارد کنید', 'contact-person' => 'شخص تماس', 'contact-info' => 'اطلاعات درباره شخص تماس', 'products' => 'محصولات', 'products-info' => 'اطلاعات درباره محصولات', ], 'edit' => [ 'title' => 'ویرایش سرنخ', 'save-btn' => 'ذخیره', 'details' => 'جزئیات', 'details-info' => 'اطلاعات پایه سرنخ را وارد کنید', 'contact-person' => 'شخص تماس', 'contact-info' => 'اطلاعات درباره شخص تماس', 'products' => 'محصولات', 'products-info' => 'اطلاعات درباره محصولات', ], 'common' => [ 'contact' => [ 'name' => 'نام', 'email' => 'ایمیل', 'contact-number' => 'شماره تماس', 'organization' => 'سازمان', ], 'products' => [ 'product-name' => 'نام محصول', 'quantity' => 'تعداد', 'price' => 'قیمت', 'amount' => 'مقدار', 'action' => 'عملیات', 'add-more' => 'افزودن بیشتر', 'total' => 'جمع', ], ], 'view' => [ 'title' => 'سرنخ: :title', 'rotten-days' => ':days روز', 'tabs' => [ 'description' => 'توضیحات', 'products' => 'محصولات', 'quotes' => 'نقل‌قول‌ها', ], 'attributes' => [ 'title' => 'درباره سرنخ', ], 'quotes' => [ 'subject' => 'موضوع', 'expired-at' => 'تاریخ انقضا', 'sub-total' => 'جمع جزئی', 'discount' => 'تخفیف', 'tax' => 'مالیات', 'adjustment' => 'تنظیمات', 'grand-total' => 'جمع کل', 'delete' => 'حذف', 'edit' => 'ویرایش', 'download' => 'دانلود', 'destroy-success' => 'پیشنهاد با موفقیت حذف شد.', 'empty-title' => 'هیچ پیشنهادی یافت نشد', 'empty-info' => 'هیچ پیشنهادی برای این سرنخ یافت نشد', 'add-btn' => 'افزودن پیشنهاد', ], 'products' => [ 'product-name' => 'نام محصول', 'quantity' => 'تعداد', 'price' => 'قیمت', 'amount' => 'مقدار', 'action' => 'عملیات', 'add-more' => 'افزودن بیشتر', 'total' => 'جمع', 'empty-title' => 'هیچ محصولی یافت نشد', 'empty-info' => 'هیچ محصولی برای این سرنخ یافت نشد', 'add-product' => 'افزودن محصول', ], 'persons' => [ 'title' => 'درباره افراد', 'job-title' => ':job_title در :organization', ], 'stages' => [ 'won' => 'برد', 'lost' => 'باخت', 'need-more-info' => 'نیاز به اطلاعات بیشتر', 'closed-at' => 'بسته شده در', 'won-value' => 'ارزش برد', 'lost-reason' => 'دلیل باخت', 'save-btn' => 'ذخیره', ], 'tags' => [ 'create-success' => 'برچسب با موفقیت ایجاد شد.', 'destroy-success' => 'برچسب با موفقیت حذف شد.', ], ], ], 'configuration' => [ 'index' => [ 'back' => 'بازگشت', 'delete' => 'حذف', 'save-btn' => 'ذخیره پیکربندی', 'save-success' => 'پیکربندی با موفقیت ذخیره شد.', 'search' => 'جستجو', 'select-country' => 'انتخاب کشور', 'select-state' => 'انتخاب ایالت', 'title' => 'پیکربندی', 'general' => [ 'title' => 'عمومی', 'info' => 'پیکربندی عمومی', 'general' => [ 'title' => 'عمومی', 'info' => 'تنظیمات عمومی خود را اینجا به‌روزرسانی کنید.', 'locale-settings' => [ 'title' => 'تنظیمات محلی', 'title-info' => 'زبان مورد استفاده در رابط کاربری را تعریف می‌کند، مانند عربی (ar)، انگلیسی (en)، اسپانیایی (es)، فارسی (fa) و ترکی (tr).', ], 'admin-logo' => [ 'logo-image' => 'تصویر لوگو', 'title' => 'لوگوی مدیر', 'title-info' => 'تصویر لوگو برای پنل مدیریت خود را پیکربندی کنید.', ], ], 'settings' => [ 'title' => 'تنظیمات', 'info' => 'تنظیمات خود را اینجا به‌روزرسانی کنید.', 'footer' => [ 'info' => 'ما می‌توانیم بخش "توسعه یافته توسط" را اینجا پیکربندی کنیم.', 'powered-by' => 'توسعه یافته توسط ویرایشگر متن', 'title' => 'پیکربندی بخش "توسعه یافته توسط"', ], 'menu' => [ 'activities' => 'فعالیت‌ها', 'configuration' => 'پیکربندی', 'contacts' => 'مخاطبین', 'dashboard' => 'داشبورد', 'draft' => 'پیش‌نویس', 'inbox' => 'صندوق ورودی', 'info' => 'ما می‌توانیم نام آیتم‌های منو را اینجا پیکربندی کنیم.', 'leads' => 'سرنخ‌ها', 'mail' => 'ایمیل', 'organizations' => 'سازمان‌ها', 'outbox' => 'صندوق خروجی', 'persons' => 'افراد', 'products' => 'محصولات', 'quotes' => 'نقل‌قول‌ها', 'sent' => 'ارسال شده', 'settings' => 'تنظیمات', 'title' => 'پیکربندی آیتم‌های منو', 'trash' => 'زباله‌دان', ], 'menu-color' => [ 'brand-color' => 'Brand Color', 'info' => 'ما می‌توانیم رنگ آیتم‌های منو را اینجا تغییر دهیم.', 'title' => 'پیکربندی رنگ آیتم‌های منو', ], ], ], 'email' => [ 'title' => 'تنظیمات ایمیل', 'info' => 'پیکربندی ایمیل برای برنامه.', 'imap' => [ 'title' => 'تنظیمات IMAP', 'info' => 'پیکربندی ایمیل IMAP برای دریافت ایمیل‌ها.', 'account' => [ 'title' => 'حساب IMAP', 'title-info' => 'تنظیمات حساب IMAP خود را اینجا پیکربندی کنید.', 'host' => 'میزبان', 'port' => 'پورت', 'encryption' => 'نوع رمزگذاری', 'validate-cert' => 'اعتبارسنجی گواهی SSL', 'username' => 'نام کاربری IMAP', 'password' => 'رمز عبور IMAP', ], ], ], 'magic-ai' => [ 'title' => 'هوش مصنوعی جادویی', 'info' => 'پیکربندی هوش مصنوعی جادویی برای برنامه.', 'settings' => [ 'api-key' => 'کلید API', 'api-key-info' => 'به یاد داشته باشید که برای هر مدل از کلید API OpenRouter استفاده کنید. این یک گام ساده برای افزایش امنیت و عملکرد است.', 'enable' => 'فعال کردن', 'info' => 'تجربه هوش مصنوعی جادویی خود را با کلید API OpenRouter خود بهبود بخشید. اکنون آن را یکپارچه کنید تا یک ماجراجویی هوش مصنوعی شخصی‌سازی شده و بدون درز برای شما فراهم شود! به راحتی تنظیمات را سفارشی کنید و کنترل سفر هوش مصنوعی خود را به دست بگیرید.', 'other' => 'مدل دیگر', 'other-model' => 'برای مدل‌های دیگر، از شناسه مدل از OpenRouter استفاده کنید.', 'doc-generation' => 'تولید DOC', 'doc-generation-info' => 'ویژگی تولید DOC را فعال کنید تا به‌صورت خودکار داده‌ها را از فایل‌های DOC استخراج و به فرمت متنی تبدیل کند. با فعال‌سازی این ویژگی، بهره‌وری و کارایی خود را افزایش دهید و فرآیند کاری را ساده‌تر کنید.', 'title' => 'تنظیمات عمومی', 'models' => [ 'deepseek-r1' => 'Deepseek R1 Distill-llama-8b', 'gemini-2-0-flash-001' => 'Gemini 2.0 flash-001', 'gpt-4o' => 'GPT-4.0', 'gpt-4o-mini' => 'GPT-4.0 mini', 'grok-2-1212' => 'Grok 2.12', 'llama-3-2-3b-instruct' => 'Llama 3.2 3b Instruct', 'title' => 'مدل‌ها', ], ], ], ], ], 'dashboard' => [ 'index' => [ 'title' => 'داشبورد', 'start-date' => 'Start Date', 'end-date' => 'End Date', 'revenue' => [ 'lost-revenue' => 'درآمد از دست رفته', 'won-revenue' => 'درآمد برنده', ], 'over-all' => [ 'average-lead-value' => 'میانگین ارزش لید', 'total-leads' => 'کل لیدها', 'average-leads-per-day' => 'میانگین لیدها در روز', 'total-quotations' => 'کل نقل قول‌ها', 'total-persons' => 'کل افراد', 'total-organizations' => 'کل سازمان‌ها', ], 'total-leads' => [ 'title' => 'لیدها', 'total' => 'کل لیدها', 'won' => 'لیدهای برنده', 'lost' => 'لیدهای از دست رفته', ], 'revenue-by-sources' => [ 'title' => 'درآمد بر اساس منابع', 'empty-title' => 'هیچ داده‌ای موجود نیست', 'empty-info' => 'داده‌ای برای بازه زمانی انتخاب شده وجود ندارد', ], 'revenue-by-types' => [ 'title' => 'درآمد بر اساس انواع', 'empty-title' => 'هیچ داده‌ای موجود نیست', 'empty-info' => 'داده‌ای برای بازه زمانی انتخاب شده وجود ندارد', ], 'top-selling-products' => [ 'title' => 'محصولات برتر', 'empty-title' => 'هیچ محصولی پیدا نشد', 'empty-info' => 'محصولی برای بازه زمانی انتخاب شده موجود نیست', ], 'top-persons' => [ 'title' => 'افراد برتر', 'empty-title' => 'هیچ فردی پیدا نشد', 'empty-info' => 'فردی برای بازه زمانی انتخاب شده موجود نیست', ], 'open-leads-by-states' => [ 'title' => 'سرنخ‌های باز بر اساس مراحل', 'empty-title' => 'هیچ داده‌ای موجود نیست', 'empty-info' => 'داده‌ای برای بازه زمانی انتخاب شده وجود ندارد', ], ], ], 'layouts' => [ 'app-version' => 'نسخه: :version', 'dashboard' => 'داشبورد', 'leads' => 'لیدها', 'quotes' => 'نقل قول‌ها', 'quote' => 'نقل قول', 'mail' => [ 'title' => 'ایمیل', 'compose' => 'تدوین', 'inbox' => 'صندوق ورودی', 'draft' => 'پیش‌نویس', 'outbox' => 'صندوق خروجی', 'sent' => 'ارسال شده', 'trash' => 'زباله‌دان', 'setting' => 'تنظیمات', ], 'activities' => 'فعالیت‌ها', 'contacts' => 'مخاطبین', 'persons' => 'افراد', 'person' => 'فرد', 'organizations' => 'سازمان‌ها', 'organization' => 'سازمان', 'products' => 'محصولات', 'product' => 'محصول', 'settings' => 'تنظیمات', 'user' => 'کاربر', 'user-info' => 'مدیریت همه کاربران و مجوزهای آنها در CRM، آنچه که اجازه دارند انجام دهند.', 'groups' => 'گروه‌ها', 'groups-info' => 'اضافه، ویرایش یا حذف گروه‌ها از CRM', 'roles' => 'نقش‌ها', 'role' => 'نقش', 'roles-info' => 'اضافه، ویرایش یا حذف نقش‌ها از CRM', 'users' => 'کاربران', 'users-info' => 'اضافه، ویرایش یا حذف کاربران از CRM', 'lead' => 'لید', 'lead-info' => 'مدیریت همه تنظیمات مربوط به لیدها در CRM', 'pipelines' => 'پایپ‌لاین‌ها', 'pipelines-info' => 'اضافه، ویرایش یا حذف پایپ‌لاین‌ها از CRM', 'sources' => 'منابع', 'sources-info' => 'اضافه، ویرایش یا حذف منابع از CRM', 'types' => 'انواع', 'types-info' => 'اضافه، ویرایش یا حذف انواع از CRM', 'automation' => 'اتوماسیون', 'automation-info' => 'مدیریت همه تنظیمات مربوط به اتوماسیون در CRM', 'attributes' => 'ویژگی‌ها', 'attribute' => 'ویژگی', 'attributes-info' => 'اضافه، ویرایش یا حذف ویژگی‌ها از CRM', 'email-templates' => 'قالب‌های ایمیل', 'email' => 'ایمیل', 'email-templates-info' => 'اضافه، ویرایش یا حذف قالب‌های ایمیل از CRM', 'events' => 'رویدادها', 'events-info' => 'افزودن، ویرایش یا حذف رویدادها از CRM', 'campaigns' => 'کمپین‌ها', 'campaigns-info' => 'افزودن، ویرایش یا حذف کمپین‌ها از CRM', 'workflows' => 'فرایندها', 'workflows-info' => 'اضافه، ویرایش یا حذف فرایندها از CRM', 'webhooks' => 'وب‌هوک‌ها', 'webhooks-info' => 'افزودن، ویرایش یا حذف وب‌هوک‌ها از CRM', 'other-settings' => 'تنظیمات دیگر', 'other-settings-info' => 'مدیریت همه تنظیمات اضافی در CRM', 'tags' => 'برچسب‌ها', 'tags-info' => 'اضافه، ویرایش یا حذف برچسب‌ها از CRM', 'my-account' => 'حساب من', 'sign-out' => 'خروج', 'back' => 'برگشت', 'name' => 'نام', 'configuration' => 'پیکربندی', 'howdy' => 'سلام!', 'warehouses' => 'انبارها', 'warehouse' => 'انبار', 'warehouses-info' => 'اضافه، ویرایش یا حذف انبارها از CRM', 'inventory' => 'موجودی', 'inventory-info' => 'مدیریت تمام تنظیمات مربوط به موجودی در CRM', 'data_transfer' => 'انتقال داده', 'data_transfer_info' => 'مدیریت تنظیمات مربوط به انتقال داده‌های اشخاص، محصولات و سرنخ‌ها در CRM', ], 'user' => [ 'account' => [ 'name' => 'نام', 'email' => 'ایمیل', 'password' => 'رمز عبور', 'my_account' => 'حساب من', 'update_details' => 'بروزرسانی جزئیات', 'current_password' => 'رمز عبور فعلی', 'confirm_password' => 'تأیید رمز عبور', 'password-match' => 'رمز عبور فعلی مطابقت ندارد.', 'account-save' => 'تغییرات حساب با موفقیت ذخیره شد.', 'permission-denied' => 'دسترسی مجاز نیست', 'remove-image' => 'حذف تصویر', 'upload_image_pix' => 'بارگذاری تصویر پروفایل (100px x 100px)', 'upload_image_format' => 'در فرمت PNG یا JPG', 'image_upload_message' => 'فقط تصاویر (.jpeg, .jpg, .png, ..) مجاز هستند.', ], ], 'emails' => [ 'common' => [ 'dear' => 'عزیز :name', 'cheers' => 'با احترام،
تیم :app_name', 'user' => [ 'dear' => 'سلام :username', 'create-subject' => 'شما به عنوان عضو اضافه شدید.', 'create-body' => 'تبریک می‌گوییم! شما اکنون عضو تیم ما هستید.', 'forget-password' => [ 'subject' => 'بازنشانی رمز عبور مشتری', 'dear' => 'سلام :username', 'reset-password' => 'بازنشانی رمز عبور', 'info' => 'شما این ایمیل را دریافت کرده‌اید زیرا ما درخواست بازنشانی رمز عبور برای حساب شما دریافت کرده‌ایم', 'final-summary' => 'اگر درخواست بازنشانی رمز عبور نکرده‌اید، نیازی به انجام اقدام دیگری نیست', 'thanks' => 'متشکریم!', ], ], ], ], 'validations' => [ 'message' => [ 'decimal' => ':attribute باید یک عدد اعشاری باشد.', ], ], 'errors' => [ 'dashboard' => 'داشبورد', 'go-back' => 'بازگشت', 'support' => 'اگر مشکل ادامه داشت، برای کمک با ما از طریق :email تماس بگیرید.', '404' => [ 'description' => 'اوه! به نظر می‌رسد صفحه‌ای که دنبال آن بودید، در دسترس نیست. نتوانستیم چیزی که دنبالش بودید را پیدا کنیم.', 'title' => '404 صفحه پیدا نشد', ], '401' => [ 'description' => 'اوه! به نظر می‌رسد که شما مجاز به دسترسی به این صفحه نیستید. شما مجوزهای لازم را ندارید.', 'title' => '401 مجاز نیست', ], '403' => [ 'description' => 'اوه! این صفحه ممنوع است. به نظر می‌رسد شما مجوزهای لازم برای مشاهده این محتوا را ندارید.', 'title' => '403 ممنوع', ], '500' => [ 'description' => 'اوه! مشکلی پیش آمده است. به نظر می‌رسد مشکلی در بارگذاری صفحه مورد نظر شما وجود دارد.', 'title' => '500 خطای سرور داخلی', ], '503' => [ 'description' => 'اوه! به نظر می‌رسد که به طور موقت برای نگهداری آفلاین هستیم. لطفاً بعداً دوباره بررسی کنید.', 'title' => '503 سرویس در دسترس نیست', ], ], 'export' => [ 'csv' => 'CSV', 'download' => 'دانلود', 'export' => 'صادر کردن', 'no-records' => 'هیچ سوابقی برای صادر کردن وجود ندارد.', 'xls' => 'XLS', 'xlsx' => 'XLSX', ], ]; ================================================ FILE: packages/Webkul/Admin/src/Resources/lang/pt_BR/app.php ================================================ [ 'leads' => 'Oportunidades', 'lead' => 'Oportunidade', 'quotes' => 'Cotações', 'mail' => 'E-mail', 'inbox' => 'Caixa de Entrada', 'draft' => 'Rascunho', 'outbox' => 'Caixa de Saída', 'sent' => 'Enviados', 'trash' => 'Lixeira', 'activities' => 'Atividades', 'webhook' => 'Webhook', 'contacts' => 'Contatos', 'persons' => 'Pessoas', 'organizations' => 'Empresas', 'products' => 'Produtos', 'settings' => 'Configurações', 'groups' => 'Grupos', 'roles' => 'Funções', 'users' => 'Usuários', 'user' => 'Usuário', 'automation' => 'Automação', 'attributes' => 'Atributos', 'pipelines' => 'Funil', 'sources' => 'Origem', 'types' => 'Tipos', 'email-templates' => 'Modelos de E-mail', 'workflows' => 'Fluxos de Trabalho', 'other-settings' => 'Outras Configurações', 'tags' => 'Tags', 'configuration' => 'Configuração', 'create' => 'Adicionar', 'edit' => 'Editar', 'view' => 'Visualizar', 'print' => 'Imprimir', 'delete' => 'Excluir', 'export' => 'Exportar', 'mass-delete' => 'Exclusão em Massa', 'data-transfer' => 'Data Transfer', 'imports' => 'Imports', 'import' => 'Import', 'event' => 'Evento', 'campaigns' => 'Campanhas', 'warehouses' => 'Armazéns', 'inventory' => 'Estoque', ], 'users' => [ 'activate-warning' => 'Sua conta ainda não foi ativada. Por favor, entre em contato com o administrador.', 'login-error' => 'As credenciais não correspondem aos nossos registros.', 'not-permission' => 'You do not have permission to access the admin panel.', 'login' => [ 'email' => 'Endereço de E-mail', 'forget-password-link' => 'Esqueceu a Senha?', 'password' => 'Senha', 'submit-btn' => 'Acessar', 'title' => 'Acessar', ], 'forget-password' => [ 'create' => [ 'email' => 'E-mail Registrado', 'email-not-exist' => 'E-mail Não Existe', 'page-title' => 'Esqueceu a Senha', 'reset-link-sent' => 'Link para redefinir a senha foi enviado', 'sign-in-link' => 'Voltar para tela de acesso?', 'submit-btn' => 'Redefinir', 'title' => 'Recuperar Senha', ], ], 'reset-password' => [ 'back-link-title' => 'Voltar para tela de acesso?', 'confirm-password' => 'Confirmar Senha', 'email' => 'E-mail Registrado', 'password' => 'Senha', 'submit-btn' => 'Redefinir Senha', 'title' => 'Redefinir Senha', ], ], 'account' => [ 'edit' => [ 'back-btn' => 'Voltar', 'change-password' => 'Alterar Senha', 'confirm-password' => 'Confirmar Senha', 'current-password' => 'Senha Atual', 'email' => 'E-mail', 'general' => 'Geral', 'invalid-password' => 'A senha atual que você digitou está incorreta.', 'name' => 'Nome', 'password' => 'Senha', 'profile-image' => 'Imagem de Perfil', 'save-btn' => 'Salvar Conta', 'title' => 'Minha Conta', 'update-success' => 'Conta atualizada com sucesso', 'upload-image-info' => 'Carregue uma imagem de perfil (110px X 110px) no formato PNG ou JPG', ], ], 'components' => [ 'activities' => [ 'actions' => [ 'mail' => [ 'btn' => 'E-mail', 'title' => 'Escrever e-mail', 'to' => 'Para', 'enter-emails' => 'Pressione Enter para adicionar e-mails', 'cc' => 'Cópia', 'bcc' => 'Cópia oculta', 'subject' => 'Assunto', 'send-btn' => 'Enviar', 'message' => 'Mensagem', ], 'file' => [ 'btn' => 'Arquivo', 'title' => 'Adicionar Arquivo', 'title-control' => 'Título', 'name' => 'Nome', 'description' => 'Descrição', 'file' => 'Arquivo', 'save-btn' => 'Salvar Arquivo', ], 'note' => [ 'btn' => 'Nota', 'title' => 'Adicionar Nota', 'comment' => 'Comentário', 'save-btn' => 'Salvar Nota', ], 'activity' => [ 'btn' => 'Atividade', 'title' => 'Adicionar Atividade', 'title-control' => 'Título', 'description' => 'Descrição', 'schedule-from' => 'Agendar De', 'schedule-to' => 'Agendar Até', 'location' => 'Localização', 'call' => 'Chamada', 'meeting' => 'Reunião', 'lunch' => 'Almoço', 'save-btn' => 'Salvar Atividade', 'participants' => [ 'title' => 'Participantes', 'placeholder' => 'Digite para pesquisar participantes', 'users' => 'Usuários', 'persons' => 'Pessoas', 'no-results' => 'Nenhum resultado encontrado...', ], ], ], 'index' => [ 'all' => 'Todos', 'bcc' => 'Cópia oculta', 'by-user' => 'Por usuário', 'calls' => 'Chamadas', 'cc' => 'Cópia', 'change-log' => 'Logs de Alterações', 'delete' => 'Excluir', 'edit' => 'Editar', 'emails' => 'E-mails', 'empty' => 'Vazio', 'files' => 'Arquivos', 'from' => 'De', 'location' => 'Localização', 'lunches' => 'Almoços', 'mark-as-done' => 'Marcar como Concluído', 'meetings' => 'Reuniões', 'notes' => 'Notas', 'participants' => 'Participantes', 'planned' => 'Planejado', 'quotes' => 'Cotações', 'scheduled-on' => 'Agendado em', 'system' => 'Sistema', 'to' => 'Para', 'unlink' => 'Desvincular', 'view' => 'Visualizar', 'empty-placeholders' => [ 'all' => [ 'title' => 'Nenhuma atividade encontrada', 'description' => 'Nenhuma atividade foi encontrada para este item. Você pode adicionar atividades clicando no botão Atividade no painel à esquerda.', ], 'planned' => [ 'title' => 'Nenhuma atividade planejada encontrada', 'description' => 'Nenhuma atividade planejada foi encontrada. Adicione uma clicando no botão Atividade no painel à esquerda.', ], 'notes' => [ 'title' => 'Nenhuma nota encontrada', 'description' => 'Nenhuma nota foi encontrada. Adicione notas clicando no botão Nota no painel à esquerda.', ], 'calls' => [ 'title' => 'Nenhuma chamada encontrada', 'description' => 'Nenhuma chamada foi encontrada. Adicione chamadas clicando no botão Atividade e selecionando o tipo Chamada.', ], 'meetings' => [ 'title' => 'Nenhuma reunião encontrada', 'description' => 'Nenhuma reunião foi encontrada. Adicione reuniões clicando no botão Atividade e selecionando o tipo Reunião.', ], 'lunches' => [ 'title' => 'Nenhum almoço encontrado', 'description' => 'Nenhum almoço foi encontrado. Adicione almoços clicando no botão Atividade e selecionando o tipo Almoço.', ], 'files' => [ 'title' => 'Nenhum arquivo encontrado', 'description' => 'Nenhum arquivo foi encontrado. Adicione arquivos clicando no botão Arquivo no painel à esquerda.', ], 'emails' => [ 'title' => 'Nenhum e-mail encontrado', 'description' => 'Nenhum e-mail foi encontrado. Adicione e-mails clicando no botão Correio no painel à esquerda.', ], 'system' => [ 'title' => 'Nenhum registro de alteração encontrado', 'description' => 'Nenhum registro de alteração foi encontrado.', ], ], ], ], 'media' => [ 'images' => [ 'add-image-btn' => 'Adicionar Imagem', 'ai-add-image-btn' => 'Mágica AI', 'allowed-types' => 'png, jpeg, jpg', 'not-allowed-error' => 'Apenas arquivos de imagem (.jpeg, .jpg, .png, ..) são permitidos.', 'placeholders' => [ 'front' => 'Frente', 'next' => 'Próximo', 'size' => 'Tamanho', 'use-cases' => 'Casos de Uso', 'zoom' => 'Zoom', ], ], 'videos' => [ 'add-video-btn' => 'Adicionar Vídeo', 'allowed-types' => 'mp4, webm, mkv', 'not-allowed-error' => 'Apenas arquivos de vídeo (.mp4, .mov, .ogg ..) são permitidos.', ], ], 'datagrid' => [ 'index' => [ 'no-records-selected' => 'Nenhum registro foi selecionado.', 'must-select-a-mass-action-option' => 'Você deve selecionar uma opção de ação em massa.', 'must-select-a-mass-action' => 'Você deve selecionar uma ação em massa.', ], 'toolbar' => [ 'length-of' => 'tamanho de', 'of' => 'de', 'per-page' => 'Por Página', 'results' => 'Resultados', 'delete' => 'Excluir', 'selected' => 'Itens Selecionados', 'mass-actions' => [ 'submit' => 'Enviar', 'select-option' => 'Selecionar Opção', 'select-action' => 'Selecionar Ação', ], 'filter' => [ 'apply-filters-btn' => 'Aplicar Filtros', 'back-btn' => 'Voltar', 'create-new-filter' => 'Adicionar novo Filtro', 'custom-filters' => 'Filtros Personalizados', 'delete-error' => 'Algo deu errado ao excluir o filtro, por favor tente novamente.', 'delete-success' => 'Filtro excluído com sucesso.', 'empty-description' => 'Não há filtros selecionados disponíveis para salvar. Por favor, selecione filtros para salvar.', 'empty-title' => 'Adicionar Filtros para Salvar', 'name' => 'Nome', 'quick-filters' => 'Filtros Rápidos', 'save-btn' => 'Salvar', 'save-filter' => 'Salvar Filtro', 'saved-success' => 'Filtro salvo com sucesso.', 'selected-filters' => 'Filtros Selecionados', 'title' => 'Filtro', 'update' => 'Atualizar', 'update-filter' => 'Atualizar Filtro', 'updated-success' => 'Filtro atualizado com sucesso.', ], 'search' => [ 'title' => 'Pesquisar', ], ], 'filters' => [ 'select' => 'Selecionar', 'title' => 'Filtros', 'dropdown' => [ 'searchable' => [ 'at-least-two-chars' => 'Digite pelo menos 2 caracteres...', 'no-results' => 'Nenhum resultado encontrado...', ], ], 'custom-filters' => [ 'clear-all' => 'Limpar Todos', 'title' => 'Filtros Personalizados', ], 'boolean-options' => [ 'false' => 'Falso', 'true' => 'Verdadeiro', ], 'date-options' => [ 'last-month' => 'Último Mês', 'last-six-months' => 'Últimos 6 Meses', 'last-three-months' => 'Últimos 3 Meses', 'this-month' => 'Este Mês', 'this-week' => 'Esta Semana', 'this-year' => 'Este Ano', 'today' => 'Hoje', 'yesterday' => 'Ontem', ], ], 'table' => [ 'actions' => 'Ações', 'no-records-available' => 'Nenhum Registro Disponível.', ], ], 'modal' => [ 'confirm' => [ 'agree-btn' => 'Concordar', 'disagree-btn' => 'Discordar', 'message' => 'Você tem certeza de que deseja realizar esta ação?', 'title' => 'Você tem certeza?', ], ], 'tags' => [ 'index' => [ 'title' => 'Tags', 'added-tags' => 'Tags Adicionadas', 'save-btn' => 'Salvar Tag', 'placeholder' => 'Digite para procurar tags', 'add-tag' => 'Adicionar Tag', 'aquarelle-red' => 'Vermelho', 'crushed-cashew' => 'Castanho', 'beeswax' => 'Amarelado', 'lemon-chiffon' => 'Limão', 'snow-flurry' => 'Neve', 'honeydew' => 'Melão', ], ], 'layouts' => [ 'powered-by' => [ 'description' => 'Desenvolvido por :krayin, um projeto de código aberto da :webkul.', ], 'header' => [ 'mega-search' => [ 'title' => 'Busca rápida', 'tabs' => [ 'leads' => 'Oportunidades', 'quotes' => 'Cotações', 'persons' => 'Pessoas', 'products' => 'Produtos', ], 'explore-all-products' => 'Explorar todos os Produtos', 'explore-all-leads' => 'Explorar todos as Oportunidades', 'explore-all-contacts' => 'Explorar todos os Contatos', 'explore-all-quotes' => 'Explorar todas as Cotações', 'explore-all-matching-products' => 'Explorar todos os produtos correspondentes a ":query" (:count)', 'explore-all-matching-leads' => 'Explorar todos os negócios correspondentes a ":query" (:count)', 'explore-all-matching-contacts' => 'Explorar todos os contatos correspondentes a ":query" (:count)', 'explore-all-matching-quotes' => 'Explorar todas as cotações correspondentes a ":query" (:count)', ], ], ], 'attributes' => [ 'edit' => [ 'delete' => 'Excluir', ], 'lookup' => [ 'click-to-add' => 'Clique para adicionar', 'search' => 'Pesquisar...', 'no-result-found' => 'Nenhum resultado encontrado', ], ], 'lookup' => [ 'click-to-add' => 'Clique para Adicionar', 'no-results' => 'Nenhum Resultado Encontrado', 'add-as-new' => 'Adicionar como Novo', 'search' => 'Pesquisar...', ], 'flash-group' => [ 'success' => 'Sucesso', 'error' => 'Erro', 'warning' => 'Aviso', 'info' => 'Informação', ], 'tiny-mce' => [ 'http-error' => 'Erro HTTP', 'invalid-json' => 'Resposta JSON inválida do servidor.', 'upload-failed' => 'Falha no upload do arquivo. Por favor, tente novamente.', ], ], 'quotes' => [ 'index' => [ 'title' => 'Cotações', 'create-btn' => 'Adicionar Cotação', 'create-success' => 'Cotação adicionada com sucesso.', 'update-success' => 'Cotação atualizada com sucesso.', 'delete-success' => 'Cotação excluída com sucesso.', 'delete-failed' => 'Não é possível excluir a cotação.', 'datagrid' => [ 'subject' => 'Assunto', 'sales-person' => 'Vendedor', 'expired-at' => 'Expirado em', 'created-at' => 'Criado em', 'person' => 'Pessoa', 'subtotal' => 'Subtotal', 'discount' => 'Desconto', 'tax' => 'Imposto', 'adjustment' => 'Ajuste', 'grand-total' => 'Total Geral', 'edit' => 'Editar', 'delete' => 'Excluir', 'print' => 'Imprimir', ], 'pdf' => [ 'adjustment' => 'Ajuste', 'amount' => 'Valor', 'billing-address' => 'Endereço de Cobrança', 'date' => 'Data', 'discount' => 'Desconto', 'expired-at' => 'Expirado em', 'grand-total' => 'Total Geral', 'person' => 'Pessoa', 'price' => 'Preço', 'product-name' => 'Nome do Produto', 'quantity' => 'Quantidade', 'quote-id' => 'ID da Cotação', 'sales-person' => 'Vendedor', 'shipping-address' => 'Endereço de Envio', 'sku' => 'Código', 'sub-total' => 'Subtotal', 'subject' => 'Assunto', 'tax' => 'Imposto', 'title' => 'Cotação', ], ], 'create' => [ 'title' => 'Adicionar Cotação', 'save-btn' => 'Salvar Cotação', 'quote-info' => 'Informações da Cotação', 'quote-info-info' => 'Informe as informações básicas da cotação.', 'address-info' => 'Informações de Endereço', 'address-info-info' => 'Informações sobre o endereço relacionado à cotação.', 'quote-items' => 'Itens da Cotação', 'search-products' => 'Pesquisar Produtos', 'link-to-lead' => 'Vincular a um negócio', 'quote-item-info' => 'Adicionar solicitação de produto para esta cotação.', 'quote-name' => 'Nome da Cotação', 'quantity' => 'Quantidade', 'price' => 'Preço', 'discount' => 'Desconto', 'tax' => 'Imposto', 'total' => 'Total', 'amount' => 'Valor', 'add-item' => '+ Adicionar Item', 'sub-total' => 'Subtotal (:symbol)', 'total-discount' => 'Desconto (:symbol)', 'total-tax' => 'Imposto (:symbol)', 'total-adjustment' => 'Ajuste (:symbol)', 'grand-total' => 'Total Geral (:symbol)', 'discount-amount' => 'Valor do Desconto', 'tax-amount' => 'Valor do Imposto', 'adjustment-amount' => 'Valor do Ajuste', 'product-name' => 'Nome do Produto', 'action' => 'Ação', ], 'edit' => [ 'title' => 'Editar Cotação', 'save-btn' => 'Salvar Cotação', 'quote-info' => 'Informações da Cotação', 'quote-info-info' => 'Informe as informações básicas da cotação.', 'address-info' => 'Informações de Endereço', 'address-info-info' => 'Informações sobre o endereço relacionado à cotação.', 'quote-items' => 'Itens da Cotação', 'link-to-lead' => 'Vincular a um negócio', 'quote-item-info' => 'Adicionar solicitação de produto para esta cotação.', 'quote-name' => 'Nome da Cotação', 'quantity' => 'Quantidade', 'price' => 'Preço', 'search-products' => 'Pesquisar Produtos', 'discount' => 'Desconto', 'tax' => 'Imposto', 'total' => 'Total', 'amount' => 'Valor', 'add-item' => '+ Adicionar Item', 'sub-total' => 'Subtotal (:symbol)', 'total-discount' => 'Desconto (:symbol)', 'total-tax' => 'Imposto (:symbol)', 'total-adjustment' => 'Ajuste (:symbol)', 'grand-total' => 'Total Geral (:symbol)', 'discount-amount' => 'Valor do Desconto', 'tax-amount' => 'Valor do Imposto', 'adjustment-amount' => 'Valor do Ajuste', 'product-name' => 'Nome do Produto', 'action' => 'Ação', ], ], 'contacts' => [ 'persons' => [ 'index' => [ 'title' => 'Pessoas', 'create-btn' => 'Adicionar Pessoa', 'create-success' => 'Pessoa adicionada com sucesso.', 'update-success' => 'Pessoa atualizada com sucesso.', 'all-delete-success' => 'Todas as pessoas selecionadas foram excluídas com sucesso.', 'partial-delete-warning' => 'Algumas pessoas foram excluídas com sucesso. Outras não puderam ser excluídas porque estão vinculadas a leads.', 'none-delete-warning' => 'Nenhuma das pessoas selecionadas pôde ser excluída porque estão vinculadas a leads.', 'no-selection' => 'Nenhuma pessoa foi selecionada para exclusão.', 'delete-failed' => 'Falha ao excluir as pessoas selecionadas.', 'datagrid' => [ 'contact-numbers' => 'Números de Contato', 'delete' => 'Excluir', 'edit' => 'Editar', 'emails' => 'E-mails', 'id' => 'ID', 'view' => 'Visualizar', 'name' => 'Nome', 'organization-name' => 'Nome da Empresa', ], ], 'view' => [ 'title' => ':name', 'about-person' => 'Sobre a Pessoa', 'about-organization' => 'Sobre a Empresa', 'activities' => [ 'index' => [ 'all' => 'Todos', 'calls' => 'Chamadas', 'meetings' => 'Reuniões', 'lunches' => 'Almoços', 'files' => 'Arquivos', 'quotes' => 'Cotações', 'notes' => 'Notas', 'emails' => 'E-mails', 'by-user' => 'Por usuário', 'scheduled-on' => 'Agendado em', 'location' => 'Localização', 'participants' => 'Participantes', 'mark-as-done' => 'Marcar como Concluído', 'delete' => 'Excluir', 'edit' => 'Editar', ], 'actions' => [ 'mail' => [ 'btn' => 'E-mail', 'title' => 'Escrever e-mail', 'to' => 'Para', 'cc' => 'Cópia', 'bcc' => 'Cópia oculta', 'subject' => 'Assunto', 'send-btn' => 'Enviar', 'message' => 'Mensagem', ], 'file' => [ 'btn' => 'Arquivo', 'title' => 'Adicionar Arquivo', 'title-control' => 'Título', 'name' => 'Nome do Arquivo', 'description' => 'Descrição', 'file' => 'Arquivo', 'save-btn' => 'Salvar Arquivo', ], 'note' => [ 'btn' => 'Nota', 'title' => 'Adicionar Nota', 'comment' => 'Comentário', 'save-btn' => 'Salvar Nota', ], 'activity' => [ 'btn' => 'Atividade', 'title' => 'Adicionar Atividade', 'title-control' => 'Título', 'description' => 'Descrição', 'schedule-from' => 'Agendar De', 'schedule-to' => 'Agendar Até', 'location' => 'Localização', 'call' => 'Chamada', 'meeting' => 'Reunião', 'lunch' => 'Almoço', 'save-btn' => 'Salvar Atividade', ], ], ], 'tags' => [ 'create-success' => 'Tag criada com sucesso.', 'destroy-success' => 'Tag excluída com sucesso.', ], ], 'create' => [ 'title' => 'Adicionar Pessoa', 'save-btn' => 'Salvar Pessoa', ], 'edit' => [ 'title' => 'Editar Pessoa', 'save-btn' => 'Salvar Pessoa', ], ], 'organizations' => [ 'index' => [ 'title' => 'Empresas', 'create-btn' => 'Adicionar Empresa', 'create-success' => 'Empresa adicionada com sucesso.', 'update-success' => 'Empresa atualizada com sucesso.', 'delete-success' => 'Empresa excluída com sucesso.', 'delete-failed' => 'Não foi possível excluir a empresa.', 'datagrid' => [ 'delete' => 'Excluir', 'edit' => 'Editar', 'id' => 'ID', 'name' => 'Nome', 'persons-count' => 'Quantidade de Pessoas', ], ], 'create' => [ 'title' => 'Adicionar Empresa', 'save-btn' => 'Salvar Empresa', ], 'edit' => [ 'title' => 'Editar Empresa', 'save-btn' => 'Salvar Empresa', ], ], ], 'products' => [ 'index' => [ 'title' => 'Produtos', 'create-btn' => 'Adicionar Produto', 'create-success' => 'Produto adicionado com sucesso.', 'update-success' => 'Produto atualizado com sucesso.', 'delete-success' => 'Produto excluído com sucesso.', 'delete-failed' => 'Não foi possível excluir o produto.', 'datagrid' => [ 'allocated' => 'Alocado', 'delete' => 'Excluir', 'edit' => 'Editar', 'id' => 'ID', 'in-stock' => 'Em Estoque', 'name' => 'Nome', 'on-hand' => 'Disponível', 'tag-name' => 'Nome da Tag', 'price' => 'Preço', 'sku' => 'Código', 'view' => 'Visualizar', ], ], 'create' => [ 'save-btn' => 'Salvar Produtos', 'title' => 'Adicionar Produtos', 'general' => 'Geral', 'price' => 'Preço', ], 'edit' => [ 'title' => 'Editar Produtos', 'save-btn' => 'Salvar Produtos', 'general' => 'Geral', 'price' => 'Preço', ], 'view' => [ 'sku' => 'Código', 'all' => 'Todos', 'notes' => 'Notas', 'files' => 'Arquivos', 'inventories' => 'Inventário', 'change-logs' => 'Histórico de Alterações', 'attributes' => [ 'about-product' => 'Sobre o Produto', ], 'inventory' => [ 'source' => 'Origem', 'in-stock' => 'Em Estoque', 'allocated' => 'Alocado', 'on-hand' => 'Disponível', 'actions' => 'Ações', 'assign' => 'Atribuir', 'add-source' => 'Adicionar Origem', 'location' => 'Localização', 'add-more' => 'Adicionar Mais', 'save' => 'Salvar', ], ], ], 'settings' => [ 'title' => 'Configurações', 'groups' => [ 'index' => [ 'create-btn' => 'Criar Grupo', 'title' => 'Grupos', 'create-success' => 'Grupo criado com sucesso.', 'update-success' => 'Grupo atualizado com sucesso.', 'destroy-success' => 'Grupo excluído com sucesso.', 'delete-failed' => 'Não foi possível excluir o grupo.', 'delete-failed-associated-users' => 'Não foi possível excluir o grupo, pois está sendo utilizado por usuários.', 'datagrid' => [ 'delete' => 'Excluir', 'description' => 'Descrição', 'edit' => 'Editar', 'id' => 'ID', 'name' => 'Nome', ], 'edit' => [ 'title' => 'Editar Grupo', ], 'create' => [ 'name' => 'Nome', 'title' => 'Adicionar Grupo', 'description' => 'Descrição', 'save-btn' => 'Salvar Grupo', ], ], ], 'roles' => [ 'index' => [ 'being-used' => 'Não é possível excluir o cargo, pois está sendo usado por um usuário administrador.', 'create-btn' => 'Adicionar Cargos', 'create-success' => 'Cargo adicionado com sucesso.', 'current-role-delete-error' => 'Não é possível excluir o cargo atribuído ao usuário atual.', 'delete-failed' => 'Não foi possível excluir o cargo.', 'delete-success' => 'Cargo excluído com sucesso.', 'last-delete-error' => 'É necessário pelo menos um cargo.', 'settings' => 'Configurações', 'title' => 'Cargos', 'update-success' => 'Cargo atualizado com sucesso.', 'user-define-error' => 'Não é possível excluir cargo do sistema.', 'datagrid' => [ 'all' => 'Todos', 'custom' => 'Personalizado', 'delete' => 'Excluir', 'description' => 'Descrição', 'edit' => 'Editar', 'id' => 'ID', 'name' => 'Nome', 'permission-type' => 'Tipo de Permissão', ], ], 'create' => [ 'access-control' => 'Controle de Acesso', 'all' => 'Todos', 'back-btn' => 'Voltar', 'custom' => 'Personalizado', 'description' => 'Descrição', 'general' => 'Geral', 'name' => 'Nome', 'permissions' => 'Permissões', 'save-btn' => 'Salvar Cargo', 'title' => 'Adicionar Cargo', ], 'edit' => [ 'access-control' => 'Controle de Acesso', 'all' => 'Todos', 'back-btn' => 'Voltar', 'custom' => 'Personalizado', 'description' => 'Descrição', 'general' => 'Geral', 'name' => 'Nome', 'permissions' => 'Permissões', 'save-btn' => 'Salvar Cargo', 'title' => 'Editar Cargo', ], ], 'types' => [ 'index' => [ 'create-btn' => 'Adicionar Tipo', 'create-success' => 'Tipo adicionado com sucesso.', 'delete-failed' => 'Não é possível excluir o tipo.', 'delete-success' => 'Tipo excluído com sucesso.', 'title' => 'Tipos', 'update-success' => 'Tipo atualizado com sucesso.', 'datagrid' => [ 'delete' => 'Excluir', 'description' => 'Descrição', 'edit' => 'Editar', 'id' => 'ID', 'name' => 'Nome', ], 'create' => [ 'name' => 'Nome', 'save-btn' => 'Salvar Tipo', 'title' => 'Adicionar Tipo', ], 'edit' => [ 'title' => 'Editar Tipo', ], ], ], 'sources' => [ 'index' => [ 'title' => 'Fontes', 'create-btn' => 'Criar Fonte', 'create-success' => 'Fonte criada com sucesso.', 'delete-failed' => 'Não foi possível excluir a fonte.', 'delete-success' => 'Fonte excluída com sucesso.', 'update-success' => 'Fonte atualizada com sucesso.', 'delete-failed-associated-leads' => 'Não é possível excluir a fonte porque está associada a leads existentes. Por favor, desvincule ou atualize esses leads antes da exclusão.', 'datagrid' => [ 'delete' => 'Excluir', 'edit' => 'Editar', 'id' => 'ID', 'name' => 'Nome', ], 'create' => [ 'name' => 'Nome', 'save-btn' => 'Salvar Origem', 'title' => 'Adicionar Origem', ], 'edit' => [ 'title' => 'Editar Origem', ], ], ], 'workflows' => [ 'index' => [ 'title' => 'Workflows', 'create-btn' => 'Adicionar Workflow', 'create-success' => 'Workflow adicionado com sucesso.', 'update-success' => 'Workflow atualizado com sucesso.', 'delete-success' => 'Workflow excluído com sucesso.', 'delete-failed' => 'Não é possível excluir o Workflow.', 'datagrid' => [ 'delete' => 'Excluir', 'description' => 'Descrição', 'edit' => 'Editar', 'id' => 'ID', 'name' => 'Nome', ], ], 'helpers' => [ 'update-related-leads' => 'Atualizar leads relacionados', 'send-email-to-sales-owner' => 'Enviar e-mail para o proprietário de vendas', 'send-email-to-participants' => 'Enviar e-mail para os participantes', 'add-webhook' => 'Adicionar Webhook', 'update-lead' => 'Atualizar Oportunidade', 'update-person' => 'Atualizar Pessoa', 'send-email-to-person' => 'Enviar e-mail para a pessoa', 'add-tag' => 'Adicionar Tag', 'add-note-as-activity' => 'Adicionar Nota como Atividade', 'update-quote' => 'Update Quote', ], 'create' => [ 'title' => 'Adicionar Workflow', 'event' => 'Evento', 'back-btn' => 'Voltar', 'save-btn' => 'Salvar Workflow', 'name' => 'Nome', 'basic-details' => 'Detalhes Básicos', 'description' => 'Descrição', 'actions' => 'Ações', 'basic-details-info' => 'Informe as informações básicas do workflow.', 'event-info' => 'Um evento dispara, verifica, aplica condições e executa ações predefinidas.', 'conditions' => 'Condições', 'conditions-info' => 'As condições são regras que verificam cenários, acionadas em ocasiões específicas.', 'actions-info' => 'Uma ação não apenas reduz a carga de trabalho, mas também facilita a automação do CRM.', 'value' => 'Valor', 'condition-type' => 'Tipo de Condição', 'all-condition-are-true' => 'Todas as condições são verdadeiras', 'any-condition-are-true' => 'Qualquer condição é verdadeira', 'add-condition' => 'Adicionar Condição', 'add-action' => 'Adicionar Ação', 'yes' => 'Sim', 'no' => 'Não', 'email' => 'E-mail', 'is-equal-to' => 'É igual a', 'is-not-equal-to' => 'Não é igual a', 'equals-or-greater-than' => 'É igual ou maior que', 'equals-or-less-than' => 'É igual ou menor que', 'greater-than' => 'Maior que', 'less-than' => 'Menor que', 'type' => 'Tipo', 'contain' => 'Contém', 'contains' => 'Contém', 'does-not-contain' => 'Não contém', ], 'edit' => [ 'title' => 'Editar Workflow', 'event' => 'Evento', 'back-btn' => 'Voltar', 'save-btn' => 'Salvar Workflow', 'name' => 'Nome', 'basic-details' => 'Detalhes Básicos', 'description' => 'Descrição', 'actions' => 'Ações', 'type' => 'Tipo', 'basic-details-info' => 'Informe as informações básicas do workflow.', 'event-info' => 'Um evento dispara, verifica, aplica condições e executa ações predefinidas.', 'conditions' => 'Condições', 'conditions-info' => 'As condições são regras que verificam cenários, acionadas em ocasiões específicas.', 'actions-info' => 'Uma ação não apenas reduz a carga de trabalho, mas também facilita a automação do CRM.', 'value' => 'Valor', 'condition-type' => 'Tipo de Condição', 'all-condition-are-true' => 'Todas as condições são verdadeiras', 'any-condition-are-true' => 'Qualquer condição é verdadeira', 'add-condition' => 'Adicionar Condição', 'add-action' => 'Adicionar Ação', 'yes' => 'Sim', 'no' => 'Não', 'email' => 'E-mail', 'is-equal-to' => 'É igual a', 'is-not-equal-to' => 'Não é igual a', 'equals-or-greater-than' => 'É igual ou maior que', 'equals-or-less-than' => 'É igual ou menor que', 'greater-than' => 'Maior que', 'less-than' => 'Menor que', 'contain' => 'Contém', 'contains' => 'Contém', 'does-not-contain' => 'Não contém', ], ], 'webforms' => [ 'index' => [ 'title' => 'Webforms', 'create-btn' => 'Adicionar Webform', 'create-success' => 'Webform adicionado com sucesso.', 'update-success' => 'Webform atualizado com sucesso.', 'delete-success' => 'Webform excluído com sucesso.', 'delete-failed' => 'Não é possível excluir o Webform.', 'datagrid' => [ 'id' => 'ID', 'title' => 'Título', 'edit' => 'Editar', 'delete' => 'Excluir', ], ], 'create' => [ 'title' => 'Adicionar Webform', 'add-attribute-btn' => 'Adicionar Botão de Atributo', 'attribute-label-color' => 'Cor do Rótulo do Atributo', 'attributes' => 'Atributos', 'attributes-info' => 'Adicione atributos personalizados ao formulário.', 'background-color' => 'Cor de Fundo', 'create-lead' => 'Adicionar Oportunidade', 'customize-webform' => 'Personalizar Webform', 'customize-webform-info' => 'Personalize seu formulário com as cores dos elementos de sua escolha.', 'description' => 'Descrição', 'display-custom-message' => 'Exibir mensagem personalizada', 'form-background-color' => 'Cor de Fundo do Formulário', 'form-submit-btn-color' => 'Cor do Botão de Envio do Formulário', 'form-submit-button-color' => 'Cor do Botão de Envio do Formulário', 'form-title-color' => 'Cor do Título do Formulário', 'general' => 'Geral', 'leads' => 'Oportunidades', 'person' => 'Pessoa', 'save-btn' => 'Salvar Webform', 'submit-button-label' => 'Rótulo do Botão de Envio', 'submit-success-action' => 'Ação de Sucesso ao Enviar', 'redirect-to-url' => 'Redirecionar Para URL', 'choose-value' => 'Escolher Valor', 'select-file' => 'Selecionar Arquivo', 'select-image' => 'Selecionar Imagem', 'enter-value' => 'Inserir Valor', ], 'edit' => [ 'add-attribute-btn' => 'Adicionar Botão de Atributo', 'attribute-label-color' => 'Cor do Rótulo do Atributo', 'attributes' => 'Atributos', 'attributes-info' => 'Adicione atributos personalizados ao formulário.', 'background-color' => 'Cor de Fundo', 'choose-value' => 'Escolher Valor', 'code-snippet' => 'Trecho de Código', 'copied' => 'Copiado', 'copy' => 'Copiar', 'create-lead' => 'Adicionar Oportunidade', 'customize-webform' => 'Personalizar Webform', 'customize-webform-info' => 'Personalize seu formulário com as cores dos elementos de sua escolha.', 'description' => 'Descrição', 'display-custom-message' => 'Exibir mensagem personalizada', 'embed' => 'Incorporar', 'enter-value' => 'Inserir Valor', 'form-background-color' => 'Cor de Fundo do Formulário', 'form-submit-btn-color' => 'Cor do Botão de Envio do Formulário', 'form-submit-button-color' => 'Cor do Botão de Envio do Formulário', 'form-title-color' => 'Cor do Título do Formulário', 'general' => 'Geral', 'leads' => 'Oportunidades', 'person' => 'Pessoa', 'preview' => 'Visualizar', 'public-url' => 'URL Pública', 'redirect-to-url' => 'Redirecionar Para URL', 'save-btn' => 'Salvar Webform', 'select-file' => 'Selecionar Arquivo', 'select-image' => 'Selecionar Imagem', 'submit-button-label' => 'Rótulo do Botão de Envio', 'submit-success-action' => 'Ação de Sucesso ao Enviar', 'title' => 'Editar Webform', ], ], 'email-template' => [ 'index' => [ 'create-btn' => 'Adicionar Modelo de E-mail', 'title' => 'Modelos de E-mail', 'create-success' => 'Modelo de E-mail adicionado com sucesso.', 'update-success' => 'Modelo de E-mail atualizado com sucesso.', 'delete-success' => 'Modelo de E-mail excluído com sucesso.', 'delete-failed' => 'Não é possível excluir o Modelo de E-mail.', 'datagrid' => [ 'delete' => 'Excluir', 'edit' => 'Editar', 'id' => 'ID', 'name' => 'Nome', 'subject' => 'Assunto', ], ], 'create' => [ 'title' => 'Adicionar Modelo de E-mail', 'save-btn' => 'Salvar Modelo de E-mail', 'email-template' => 'Modelo de E-mail', 'subject' => 'Assunto', 'content' => 'Conteúdo', 'subject-placeholders' => 'Descrição do Assunto', 'general' => 'Geral', 'name' => 'Nome', ], 'edit' => [ 'title' => 'Editar Modelo de E-mail', 'save-btn' => 'Salvar Modelo de E-mail', 'email-template' => 'Modelo de E-mail', 'subject' => 'Assunto', 'content' => 'Conteúdo', 'subject-placeholders' => 'Descrição do Assunto', 'general' => 'Geral', 'name' => 'Nome', ], ], 'marketing' => [ 'events' => [ 'index' => [ 'create-btn' => 'Adicionar Evento', 'title' => 'Eventos', 'create-success' => 'Evento adicionado com sucesso.', 'update-success' => 'Evento atualizado com sucesso.', 'delete-success' => 'Evento excluído com sucesso.', 'delete-failed' => 'Não é possível excluir o evento.', 'mass-delete-success' => 'Eventos excluídos com sucesso', 'delete-failed-associated-campaigns' => 'Não é possível excluir o evento, pois está associado a campanhas existentes. Por favor, desvincule ou atualize essas campanhas antes da exclusão.', 'datagrid' => [ 'delete' => 'Excluir', 'edit' => 'Editar', 'id' => 'ID', 'name' => 'Nome', 'description' => 'Descrição', 'date' => 'Data', ], 'create' => [ 'title' => 'Adicionar Evento', 'name' => 'Nome', 'date' => 'Data', 'description' => 'Descrição', 'save-btn' => 'Salvar Evento', ], 'edit' => [ 'title' => 'Editar Evento', ], ], ], 'campaigns' => [ 'index' => [ 'create-btn' => 'Adcionar Campanha', 'title' => 'Campanhas', 'create-success' => 'Campanha adicionada com sucesso.', 'update-success' => 'Campanha atualizada com sucesso.', 'delete-success' => 'Campanha excluída com sucesso.', 'delete-failed' => 'Não é possível excluir a campanha.', 'mass-delete-success' => 'Campanhas excluídas com sucesso', 'datagrid' => [ 'id' => 'ID', 'name' => 'Nome', 'subject' => 'Assunto', 'status' => 'Status', 'active' => 'Ativo', 'inactive' => 'Inativo', 'edit' => 'Editar', 'delete' => 'Excluir', ], 'create' => [ 'title' => 'Adicionar Campanha', 'name' => 'Nome', 'type' => 'Tipo', 'subject' => 'Assunto', 'event' => 'Evento', 'email-template' => 'Modelo de E-mail', 'status' => 'Status', ], 'edit' => [ 'title' => 'Editar Campanha', ], ], ], ], 'tags' => [ 'index' => [ 'create-btn' => 'Adicionar Tag', 'title' => 'Tags', 'create-success' => 'Tag adicionada com sucesso.', 'update-success' => 'Tag atualizada com sucesso.', 'delete-success' => 'Tag excluída com sucesso.', 'delete-failed' => 'Não é possível excluir a Tag.', 'datagrid' => [ 'delete' => 'Excluir', 'edit' => 'Editar', 'id' => 'ID', 'name' => 'Nome', 'users' => 'Usuários', 'created-at' => 'Criado Em', ], 'create' => [ 'name' => 'Nome', 'save-btn' => 'Salvar Tag', 'title' => 'Adicionar Tag', 'color' => 'Cor', ], 'edit' => [ 'title' => 'Editar Tag', ], ], ], 'users' => [ 'index' => [ 'create-btn' => 'Adicionar Usuário', 'create-success' => 'Usuário adicionado com sucesso.', 'delete-failed' => 'Não foi possível excluir o usuário.', 'delete-success' => 'Usuário excluído com sucesso.', 'last-delete-error' => 'É necessário pelo menos um usuário.', 'mass-delete-failed' => 'Não foi possível excluir os usuários.', 'mass-delete-success' => 'Usuários excluídos com sucesso.', 'mass-update-failed' => 'Não foi possível atualizar os usuários.', 'mass-update-success' => 'Usuários atualizados com sucesso.', 'title' => 'Usuários', 'update-success' => 'Usuário atualizado com sucesso.', 'user-define-error' => 'Não é possível excluir o usuário do sistema.', 'active' => 'Ativo', 'inactive' => 'Inativo', 'datagrid' => [ 'active' => 'Ativo', 'created-at' => 'Criado Em', 'delete' => 'Excluir', 'edit' => 'Editar', 'email' => 'E-mail', 'id' => 'ID', 'inactive' => 'Inativo', 'name' => 'Nome', 'status' => 'Status', 'update-status' => 'Atualizar Status', 'users' => 'Usuários', ], 'create' => [ 'confirm-password' => 'Confirmar Senha', 'email' => 'E-mail', 'general' => 'Geral', 'global' => 'Global', 'group' => 'Grupo', 'individual' => 'Individual', 'name' => 'Nome', 'password' => 'Senha', 'permission' => 'Permissão', 'role' => 'Função', 'save-btn' => 'Salvar Usuário', 'status' => 'Status', 'title' => 'Adicionar Usuário', 'view-permission' => 'Visualizar Permissão', 'select-at-lest-one-group' => 'Select at least one group', ], 'edit' => [ 'title' => 'Editar Usuário', ], ], ], 'pipelines' => [ 'index' => [ 'title' => 'Funis', 'create-btn' => 'Adicionar Funil', 'create-success' => 'Funil adicionado com sucesso.', 'update-success' => 'Funil atualizado com sucesso.', 'default-required' => 'É necessário pelo menos um pipeline padrão.', 'delete-success' => 'Funil excluído com sucesso.', 'delete-failed' => 'Não foi possível excluir o funil.', 'default-delete-error' => 'Não é possível excluir o funil padrão.', 'datagrid' => [ 'delete' => 'Excluir', 'edit' => 'Editar', 'id' => 'ID', 'is-default' => 'É Padrão', 'name' => 'Nome', 'no' => 'Não', 'rotten-days' => 'Dias parado nesta etapa', 'yes' => 'Sim', ], ], 'create' => [ 'title' => 'Adicionar Funil', 'save-btn' => 'Salvar Funil', 'name' => 'Nome', 'rotten-days' => 'Dias parado nesta etapa', 'mark-as-default' => 'Marcar como Padrão', 'general' => 'Geral', 'probability' => 'Probabilidade (%)', 'new-stage' => 'Novo', 'won-stage' => 'Ganho', 'lost-stage' => 'Perdido', 'stage-btn' => 'Adicionar Estágio', 'stages' => 'Estágios', 'duplicate-name' => 'O campo "Nome" não pode ser duplicado', 'delete-stage' => 'Excluir Estágio', 'add-new-stages' => 'Adicionar Novos Estágios', 'add-stage-info' => 'Adicionar novo estágio para o seu Funil', 'newly-added' => 'Adicionado Recentemente', 'stage-delete-success' => 'Estágio excluído com sucesso', ], 'edit' => [ 'title' => 'Editar Funil', 'save-btn' => 'Salvar Funil', 'name' => 'Nome', 'rotten-days' => 'Dias parado nesta etapa', 'mark-as-default' => 'Marcar como Padrão', 'general' => 'Geral', 'probability' => 'Probabilidade (%)', 'new-stage' => 'Novo', 'won-stage' => 'Ganho', 'lost-stage' => 'Perdido', 'stage-btn' => 'Adicionar Estágio', 'stages' => 'Estágios', 'duplicate-name' => 'O campo "Nome" não pode ser duplicado', 'delete-stage' => 'Excluir Estágio', 'add-new-stages' => 'Adicionar Novos Estágios', 'add-stage-info' => 'Adicionar novo estágio para o seu Funil', 'stage-delete-success' => 'Estágio excluído com sucesso', ], ], 'webhooks' => [ 'index' => [ 'title' => 'Webhooks', 'create-btn' => 'Adicionar Webhook', 'create-success' => 'Webhook adicionado com sucesso.', 'update-success' => 'Webhook atualizado com sucesso.', 'delete-success' => 'Webhook deletado com sucesso.', 'delete-failed' => 'Webhook não pode ser deletado.', 'datagrid' => [ 'id' => 'ID', 'delete' => 'Deletar', 'edit' => 'Editar', 'name' => 'Nome', 'entity-type' => 'Tipo de Entidade', 'end-point' => 'Ponto de Acesso', ], ], 'create' => [ 'title' => 'Adcionar Webhook', 'save-btn' => 'Salvar Webhook', 'info' => 'Digite os detalhes dos webhooks', 'url-and-parameters' => 'URL e Parâmetros', 'method' => 'Método', 'post' => 'Post', 'put' => 'Put', 'url-endpoint' => 'Endpoint da URL', 'parameters' => 'Parâmetros', 'add-new-parameter' => 'Adicionar Novo Parâmetro', 'url-preview' => 'Pré-visualização da URL:', 'headers' => 'Cabeçalhos', 'add-new-header' => 'Adicionar Novo Cabeçalho', 'body' => 'Corpo', 'default' => 'Padrão', 'x-www-form-urlencoded' => 'x-www-form-urlencoded', 'key-and-value' => 'Chave e Valor', 'add-new-payload' => 'Adicionar novo payload', 'raw' => 'Raw', 'general' => 'Geral', 'name' => 'Nome', 'entity-type' => 'Tipo de Entidade', 'insert-placeholder' => 'Inserir Placeholder', 'description' => 'Descrição', 'json' => 'Json', 'text' => 'Texto', ], 'edit' => [ 'title' => 'Editar Webhook', 'edit-btn' => 'Salvar Webhook', 'save-btn' => 'Salvar Webhook', 'info' => 'Digite os detalhes dos webhooks', 'url-and-parameters' => 'URL e Parâmetros', 'method' => 'Método', 'post' => 'Post', 'put' => 'Put', 'url-endpoint' => 'Endpoint da URL', 'parameters' => 'Parâmetros', 'add-new-parameter' => 'Adicionar Novo Parâmetro', 'url-preview' => 'Pré-visualização da URL:', 'headers' => 'Cabeçalhos', 'add-new-header' => 'Adicionar Novo Cabeçalho', 'body' => 'Corpo', 'default' => 'Padrão', 'x-www-form-urlencoded' => 'x-www-form-urlencoded', 'key-and-value' => 'Chave e Valor', 'add-new-payload' => 'Adicionar novo payload', 'raw' => 'Raw', 'general' => 'Geral', 'name' => 'Nome', 'entity-type' => 'Tipo de Entidade', 'insert-placeholder' => 'Inserir Placeholder', 'description' => 'Descrição', 'json' => 'Json', 'text' => 'Texto', ], ], 'warehouses' => [ 'index' => [ 'title' => 'Depósitos', 'create-btn' => 'Adicionar Depósito', 'create-success' => 'Depósito adicionado com sucesso.', 'name-exists' => 'Nome do depósito já existe.', 'update-success' => 'Depósito atualizado com sucesso.', 'delete-success' => 'Depósito deletado com sucesso.', 'delete-failed' => 'Depósito não pode ser deletado.', 'datagrid' => [ 'id' => 'ID', 'name' => 'Nome', 'contact-name' => 'Nome de Contato', 'delete' => 'Deletar', 'edit' => 'Editar', 'view' => 'Visualizar', 'created-at' => 'Criado em', 'products' => 'Produtos', 'contact-emails' => 'Emails de Contato', 'contact-numbers' => 'Números de Contato', ], ], 'create' => [ 'title' => 'Adicionar Depósito', 'save-btn' => 'Salvar Depósito', 'contact-info' => 'Informações de Contato', ], 'edit' => [ 'title' => 'Editar Depósito', 'save-btn' => 'Salvar Depósito', 'contact-info' => 'Informações de Contato', ], 'view' => [ 'all' => 'Todos', 'notes' => 'Notas', 'files' => 'Arquivos', 'location' => 'Localização', 'change-logs' => 'Registros de Alterações', 'locations' => [ 'action' => 'Ação', 'add-location' => 'Adicionar Localização', 'create-success' => 'Localização adicionada com sucesso.', 'delete' => 'Deletar', 'delete-failed' => 'Localização não pode ser deletada.', 'delete-success' => 'Localização deletada com sucesso.', 'name' => 'Nome', 'save-btn' => 'Salvar', ], 'general-information' => [ 'title' => 'Informações Gerais', ], 'contact-information' => [ 'title' => 'Informações de Contato', ], ], ], 'attributes' => [ 'index' => [ 'title' => 'Atributos', 'create-btn' => 'Adicionar Atributo', 'create-success' => 'Atributo criados com sucesso.', 'update-success' => 'Atributo atualizados com sucesso.', 'delete-success' => 'Atributo deletados com sucesso.', 'delete-failed' => 'Atributo não podem ser deletados.', 'user-define-error' => 'Não é possível deletar atributos do sistema.', 'mass-delete-failed' => 'Atributos do sistema não podem ser deletados.', 'datagrid' => [ 'yes' => 'Sim', 'no' => 'Não', 'id' => 'ID', 'code' => 'Código', 'name' => 'Nome', 'entity-type' => 'Tipo de Entidade', 'type' => 'Tipo', 'is-default' => 'É Padrão', 'edit' => 'Editar', 'delete' => 'Deletar', 'entity-types' => [ 'leads' => 'Oportunidades', 'organizations' => 'Empresas', 'persons' => 'Pessoas', 'products' => 'Produtos', 'quotes' => 'Cotações', 'warehouses' => 'Depósitos', ], 'types' => [ 'text' => 'Texto', 'textarea' => 'Área de texto', 'price' => 'Preço', 'boolean' => 'Booleano', 'select' => 'Selecionar', 'multiselect' => 'Seleção múltipla', 'checkbox' => 'Caixa de seleção', 'email' => 'Email', 'address' => 'Endereço', 'phone' => 'Telefone', 'lookup' => 'Busca', 'datetime' => 'Data e hora', 'date' => 'Data', 'image' => 'Imagem', 'file' => 'Arquivo', ], ], ], 'create' => [ 'title' => 'Adicionar Atributo', 'save-btn' => 'Salvar Atributo', 'code' => 'Código', 'name' => 'Nome', 'entity-type' => 'Tipo de Entidade', 'type' => 'Tipo', 'validations' => 'Validações', 'is-required' => 'É Obrigatório', 'input-validation' => 'Validação de Entrada', 'is-unique' => 'É Único', 'labels' => 'Rótulos', 'general' => 'Geral', 'numeric' => 'Numérico', 'decimal' => 'Decimal', 'url' => 'URL', 'options' => 'Opções', 'option-type' => 'Tipo de Opção', 'lookup-type' => 'Tipo de Pesquisa', 'add-option' => 'Adicionar Opção', 'save-option' => 'Salvar Opção', 'option-name' => 'Nome da Opção', 'add-attribute-options' => 'Adicionar Opções de Atributo', 'text' => 'Texto', 'textarea' => 'Área de Texto', 'price' => 'Preço', 'boolean' => 'Verdadeiro ou falso', 'select' => 'Seleção', 'multiselect' => 'Multiseleção', 'email' => 'E-mail', 'address' => 'Endereço', 'phone' => 'Telefone', 'datetime' => 'Data e Hora', 'date' => 'Data', 'image' => 'Imagem', 'file' => 'Arquivo', 'lookup' => 'Pesquisa', 'entity_type' => 'Tipo de Entidade', 'checkbox' => 'Caixa de Seleção', 'is_required' => 'É Obrigatório', 'is_unique' => 'É Único', 'actions' => 'Ações', ], 'edit' => [ 'actions' => 'Ações', 'add-attribute-options' => 'Adicionar Opções de Atributo', 'add-option' => 'Adicionar Opção', 'address' => 'Endereço', 'boolean' => 'Verdadeiro ou falso', 'checkbox' => 'Caixa de Seleção', 'code' => 'Código', 'date' => 'Data', 'datetime' => 'Data e Hora', 'decimal' => 'Decimal', 'email' => 'E-mail', 'entity-type' => 'Tipo de Entidade', 'entity_type' => 'Tipo de Entidade', 'file' => 'Arquivo', 'general' => 'Geral', 'image' => 'Imagem', 'input-validation' => 'Validação de Entrada', 'is-required' => 'É Obrigatório', 'is-unique' => 'É Único', 'is_required' => 'É Obrigatório', 'is_unique' => 'É Único', 'labels' => 'Rótulos', 'lookup' => 'Pesquisa', 'lookup-type' => 'Tipo de Pesquisa', 'multiselect' => 'Multiseleção', 'name' => 'Nome', 'numeric' => 'Numérico', 'option-deleted' => 'Attribute Option is deleted successfully', 'option-name' => 'Nome da Opção', 'option-type' => 'Tipo de Opção', 'options' => 'Opções', 'phone' => 'Telefone', 'price' => 'Preço', 'save-btn' => 'Salvar Atributo', 'save-option' => 'Salvar Opção', 'select' => 'Seleção', 'text' => 'Texto', 'textarea' => 'Área de Texto', 'title' => 'Editar Atributo', 'type' => 'Tipo', 'url' => 'URL', 'validations' => 'Validações', ], ], 'data-transfer' => [ 'imports' => [ 'create' => [ 'action' => 'Action', 'allowed-errors' => 'Allowed Errors', 'back-btn' => 'Back', 'create-update' => 'Create/Update', 'delete' => 'Delete', 'download-sample' => 'Download Sample', 'field-separator' => 'Field Separator', 'file' => 'File', 'general' => 'General', 'images-directory' => 'Images Directory Path', 'process-in-queue' => 'Process In Queue', 'results' => 'Results', 'save-btn' => 'Save Import', 'settings' => 'Settings', 'skip-errors' => 'Skip Errors', 'stop-on-errors' => 'Stop on Errors', 'title' => 'Create Import', 'type' => 'Type', 'validation-strategy' => 'Validation Strategy', ], 'edit' => [ 'action' => 'Action', 'allowed-errors' => 'Allowed Errors', 'back-btn' => 'Back', 'create-update' => 'Create/Update', 'delete' => 'Delete', 'download-sample' => 'Download Sample', 'field-separator' => 'Field Separator', 'file' => 'File', 'general' => 'General', 'images-directory' => 'Images Directory Path', 'process-in-queue' => 'Process In Queue', 'results' => 'Results', 'save-btn' => 'Save Import', 'settings' => 'Settings', 'skip-errors' => 'Skip Errors', 'stop-on-errors' => 'Stop on Errors', 'title' => 'Edit Import', 'type' => 'Type', 'validation-strategy' => 'Validation Strategy', ], 'index' => [ 'button-title' => 'Create Import', 'title' => 'Imports', 'datagrid' => [ 'actions' => 'Actions', 'completed-at' => 'Completed At', 'created' => 'Created', 'delete' => 'Delete', 'deleted' => 'Deleted', 'edit' => 'Edit', 'error-file' => 'Error File', 'id' => 'ID', 'started-at' => 'Started At', 'state' => 'State', 'summary' => 'Summary', 'type' => 'Type', 'updated' => 'Updated', 'uploaded-file' => 'Uploaded File', ], ], 'import' => [ 'back-btn' => 'Back', 'completed-batches' => 'Total Batches Completed:', 'download-error-report' => 'Download Full Report', 'edit-btn' => 'Edit', 'imported-info' => 'Congratulations! Your import was successful.', 'importing-info' => 'Import In Process', 'indexing-info' => 'Resources Indexing (Price, Inventory and Elastic Search) In Progress', 'linking-info' => 'Resources Linking In Progress', 'progress' => 'Progress:', 'title' => 'Import', 'total-batches' => 'Total Batches:', 'total-created' => 'Total Records Created:', 'total-deleted' => 'Total Records Deleted:', 'total-errors' => 'Total Errors:', 'total-invalid-rows' => 'Total Invalid Rows:', 'total-rows-processed' => 'Total Rows Processed:', 'total-updated' => 'Total Records Updated:', 'validate' => 'Validate', 'validate-info' => 'Click on Validate Data to check your import.', 'validating-info' => 'The data started reading and Validating', 'validation-failed-info' => 'Your import is invalid. Please fix the following errors and try again.', 'validation-success-info' => 'Your import is valid. Click on Import to start the import process.', ], 'create-success' => 'Import created successfully.', 'delete-failed' => 'Import deletion failed unexpectedly.', 'delete-success' => 'Import deleted successfully.', 'not-valid' => 'Import is invalid', 'nothing-to-import' => 'There are no resources to import.', 'setup-queue-error' => 'Please change your queue driver to "database" or "redis" to start the import process.', 'update-success' => 'Import updated successfully.', ], ], ], 'activities' => [ 'index' => [ 'title' => 'Atividades', 'datagrid' => [ 'comment' => 'Comentário', 'created_at' => 'Criado Em', 'created_by' => 'Criado Por', 'edit' => 'Editar', 'id' => 'ID', 'done' => 'Concluído', 'not-done' => 'Não Concluído', 'lead' => 'Oportunidade', 'mass-delete' => 'Excluir em Massa', 'mass-update' => 'Atualizar em Massa', 'schedule-from' => 'Agendado de', 'schedule-to' => 'Agendado até', 'schedule_from' => 'Agendado de', 'schedule_to' => 'Agendado até', 'title' => 'Título', 'is_done' => 'Está Concluído', 'type' => 'Tipo', 'update' => 'Atualizar', 'call' => 'Chamada', 'meeting' => 'Reunião', 'lunch' => 'Almoço', ], ], 'edit' => [ 'title' => 'Editar Atividade', 'back-btn' => 'Voltar', 'save-btn' => 'Salvar Atividade', 'type' => 'Tipo de Atividade', 'call' => 'Chamada', 'meeting' => 'Reunião', 'lunch' => 'Almoço', 'schedule_to' => 'Agendado até', 'schedule_from' => 'Agendado de', 'location' => 'Localização', 'comment' => 'Comentário', 'lead' => 'Oportunidade', 'participants' => 'Participantes', 'general' => 'Geral', 'persons' => 'Pessoas', 'no-result-found' => 'Nenhum registro encontrado.', 'users' => 'Usuários', ], 'updated' => 'Atualizado', 'created' => 'Criado', 'duration-overlapping' => 'Os participantes têm outra reunião neste horário. Deseja continuar?', 'create-success' => 'Atividade adicionada com sucesso.', 'update-success' => 'Atividade atualizada com sucesso.', 'overlapping-error' => 'Os participantes têm outra reunião neste horário.', 'destroy-success' => 'Atividade deletada com sucesso.', 'delete-failed' => 'A atividade não pode ser deletada.', 'mass-update-success' => 'Atividades atualizadas com sucesso.', 'mass-destroy-success' => 'Activities deleted successfully.', 'mass-delete-failed' => 'Activities can not be deleted.', ], 'mail' => [ 'index' => [ 'compose' => 'Escrever', 'draft' => 'Rascunho', 'inbox' => 'Caixa de Entrada', 'outbox' => 'Caixa de Saída', 'sent' => 'Enviado', 'trash' => 'Lixeira', 'compose-mail-btn' => 'Escrever E-mail', 'btn' => 'E-mail', 'mail' => [ 'title' => 'Escrever E-mail', 'to' => 'Para', 'enter-emails' => 'Pressione Enter para adicionar e-mails', 'cc' => 'Cópia', 'bcc' => 'Cópia oculta', 'subject' => 'Assunto', 'send-btn' => 'Enviar', 'message' => 'Mensagem', 'draft' => 'Rascunho', ], 'datagrid' => [ 'id' => 'ID', 'from' => 'De', 'to' => 'Para', 'subject' => 'Assunto', 'tags' => 'Tags', 'content' => 'Content', 'attachments' => 'Attachments', 'date' => 'Date', 'move-to-inbox' => 'Movido para Caixa de Entrada', 'move-to-trash' => 'Movido para a lixeira', 'edit' => 'Editar', 'view' => 'Visualizar', 'delete' => 'Excluir', ], ], 'create-success' => 'E-mail enviado com sucesso.', 'update-success' => 'E-mail atualizado com sucesso.', 'mass-update-success' => 'E-mails atualizados com sucesso.', 'delete-success' => 'E-mail excluído com sucesso.', 'delete-failed' => 'E-mail não pode ser excluído.', 'invalid-route' => 'Rota inválida para o e-mail.', 'unauthorized' => 'Esta ação não está autorizada.', 'view' => [ 'title' => 'E-mails', 'subject' => ':subject', 'link-mail' => 'Link do E-mail', 'to' => 'Para', 'cc' => 'Cópia', 'bcc' => 'Cópia oculta', 'reply' => 'Responder', 'reply-all' => 'Responder a Todos', 'forward' => 'Encaminhar', 'delete' => 'Excluir', 'enter-mails' => 'Digite o id do e-mail', 'rotten-days' => 'O negócio está sem movimentação há :days dias', 'search-an-existing-lead' => 'Pesquisar um negócio existente', 'search-an-existing-contact' => 'Pesquisar um contato existente', 'message' => 'Mensagem', 'add-attachments' => 'Adicionar Anexos', 'discard' => 'Descartar', 'send' => 'Enviar', 'no-result-found' => 'Nenhum resultado encontrado', 'add-new-contact' => 'Adicionar Novo Contato', 'description' => 'Descrição', 'search' => 'Pesquisar...', 'add-new-lead' => 'Adicionar Nova Oportunidade', 'create-new-contact' => 'Adicionar Novo Contato', 'save-contact' => 'Salvar Contato', 'create-lead' => 'Adicionar Oportunidade', 'linked-contact' => 'Contato Vinculado', 'link-to-contact' => 'Vincular ao Contato', 'link-to-lead' => 'Vincular a uma Oportunidade', 'linked-lead' => 'Oportunidade Vinculado', 'lead-details' => 'Detalhes da Oportunidade', 'contact-person' => 'Pessoa de Contato', 'product' => 'Produto', 'tags' => [ 'create-success' => 'Tag adicionada com sucesso.', 'destroy-success' => 'Tag excluída com sucesso.', ], ], ], 'common' => [ 'custom-attributes' => [ 'add-more' => 'Adicionar Mais', 'address' => 'Endereço', 'city' => 'Cidade', 'contact' => 'Números de Contato', 'country' => 'País', 'email' => 'E-mail', 'home' => 'Casa', 'postcode' => 'CEP', 'save' => 'Salvar', 'select' => 'Selecionar', 'select-country' => 'Selecionar País', 'select-state' => 'Selecionar Estado', 'state' => 'Estado', 'update-contact-title' => 'Atualizar Números de Contato', 'update-emails-title' => 'Atualizar E-mails de Contato', 'work' => 'Trabalho', ], ], 'leads' => [ 'create-success' => 'Negócio adicionado com sucesso.', 'update-success' => 'Negócio atualizado com sucesso.', 'update-failed' => 'Leads can not be deleted.', 'destroy-success' => 'Negócio excluído com sucesso.', 'destroy-failed' => 'Este negócio não pode ser excluído.', 'file' => [ 'data-not-found' => 'Dados não encontrados.', 'empty-content' => 'O conteúdo do PDF está vazio ou não pôde ser extraído.', 'failed-extract' => 'Falha ao extrair texto do arquivo.', 'insufficient-info' => 'Devido a dados insuficientes, não podemos processar sua solicitação no momento.', 'invalid-base64' => 'Formato base64 inválido.', 'invalid-format' => 'Formato JSON inválido.', 'invalid-response' => 'Formato de resposta de IA inválido.', 'missing-api-key' => 'Chave API ou configuração do modelo ausente.', 'not-found' => 'Arquivo não encontrado.', 'recursive-call' => 'Chamada recursiva detectada.', 'text-generation-failed' => 'Falha na extração de texto. O arquivo pode estar vazio ou ilegível.', ], 'index' => [ 'title' => 'Oportunidades', 'create-btn' => 'Adicionar Negócio', 'datagrid' => [ 'id' => 'ID', 'sales-person' => 'Vendedor', 'subject' => 'Assunto', 'source' => 'Origem', 'lead-value' => 'Valor do Negócio', 'lead-type' => 'Tipo de Negócio', 'tag-name' => 'Nome da Tag', 'contact-person' => 'Pessoa de Contato', 'stage' => 'Etapa', 'rotten-lead' => 'Negócio estagnado', 'date-to' => 'Data fechamento', 'created-at' => 'Criado em', 'no' => 'Não', 'yes' => 'Sim', 'delete' => 'Excluir', 'mass-delete' => 'Excluir em Massa', 'mass-update' => 'Atualizar em Massa', ], 'kanban' => [ 'rotten-days' => 'Negócio estagnado por :days dias', 'empty-list' => 'Sua lista de Negócios está vazia', 'empty-list-description' => 'Adicione um negócio para organizar seus objetivos.', 'create-lead-btn' => 'Adicionar Negócio', 'columns' => [ 'contact-person' => 'Pessoa de Contato', 'id' => 'ID', 'lead-type' => 'Tipo de Negócio', 'lead-value' => 'Valor do negócio', 'sales-person' => 'Vendedor', 'source' => 'Origem', 'title' => 'Título', 'tags' => 'Tags', 'expected-close-date' => 'Data Esperada de Fechamento', 'created-at' => 'Criado em', ], 'toolbar' => [ 'search' => [ 'title' => 'Buscar por título', ], 'filters' => [ 'apply-filters' => 'Aplicar Filtros', 'clear-all' => 'Limpar Tudo', 'filter' => 'Filtrar', 'filters' => 'Filtros', 'from' => 'De', 'select' => 'Selecionar', 'to' => 'Para', ], ], ], 'view-switcher' => [ 'all-pipelines' => 'Todos os Funis', 'create-new-pipeline' => 'Adicionar Novo Funil', ], 'upload' => [ 'create-lead' => 'Adicionar Negócio Usando AI', 'file' => 'Upload de arquivo', 'file-info' => 'Apenas arquivos nos formatos pdf, bmp, jpg, jpeg, png são aceitos.', 'file-required' => 'Por favor, selecione pelo menos um arquivo válido para prosseguir.', 'save-btn' => 'Salvar', 'upload-file' => 'Enviar arquivo', ], ], 'create' => [ 'title' => 'Adicionar Negócio', 'save-btn' => 'Salvar', 'details' => 'Detalhes', 'details-info' => 'Coloque as informações básicas do Negócio', 'contact-person' => 'Pessoa de Contato', 'contact-info' => 'Informações sobre a Pessoa de Contato', 'products' => 'Produtos', 'products-info' => 'Informações sobre os Produtos', ], 'edit' => [ 'title' => 'Editar Negócio', 'save-btn' => 'Salvar', 'details' => 'Detalhes', 'details-info' => 'Coloque as informações básicas do Negócio', 'contact-person' => 'Pessoa de Contato', 'contact-info' => 'Informações sobre a Pessoa de Contato', 'products' => 'Produtos', 'products-info' => 'Informações sobre os Produtos', ], 'common' => [ 'contact' => [ 'name' => 'Nome', 'email' => 'E-mail', 'contact-number' => 'Número de Contato', 'organization' => 'Empresa', ], 'products' => [ 'product-name' => 'Nome do Produto', 'quantity' => 'Quantidade', 'price' => 'Preço', 'amount' => 'Valor', 'action' => 'Ação', 'add-more' => 'Adicionar Mais', 'total' => 'Total', ], ], 'view' => [ 'title' => 'Negócio: :title', 'rotten-days' => ':days Dias', 'tabs' => [ 'description' => 'Descrição', 'products' => 'Produtos', 'quotes' => 'Cotações', ], 'attributes' => [ 'title' => 'Sobre o Negócio', ], 'quotes' => [ 'subject' => 'Assunto', 'expired-at' => 'Expirado em', 'sub-total' => 'Subtotal', 'discount' => 'Desconto', 'tax' => 'Imposto', 'adjustment' => 'Ajuste', 'grand-total' => 'Total Geral', 'delete' => 'Excluir', 'edit' => 'Editar', 'download' => 'Baixar', 'destroy-success' => 'Cotação excluída com sucesso.', 'empty-title' => 'Nenhuma Cotação Encontrada', 'empty-info' => 'Nenhuma Cotação Encontrada para este Negócio', 'add-btn' => 'Adicionar Cotação', ], 'products' => [ 'product-name' => 'Nome do Produto', 'quantity' => 'Quantidade', 'price' => 'Preço', 'amount' => 'Valor', 'action' => 'Ação', 'add-more' => 'Adicionar Mais', 'total' => 'Total', 'empty-title' => 'Nenhum Produto Encontrado', 'empty-info' => 'Nenhum Produto Encontrado para este Negócio', 'add-product' => 'Adicionar Produto', ], 'persons' => [ 'title' => 'Sobre as Pessoas', 'job-title' => ':job_title em :organization', ], 'stages' => [ 'won' => 'Ganho', 'lost' => 'Perdido', 'need-more-info' => 'Precisa de Mais Informações', 'closed-at' => 'Fechado em', 'won-value' => 'Valor Ganhado', 'lost-reason' => 'Motivo da Perda', 'save-btn' => 'Salvar', ], 'tags' => [ 'create-success' => 'Tag adicionada com sucesso.', 'destroy-success' => 'Tag excluída com sucesso.', ], ], ], 'configuration' => [ 'index' => [ 'back' => 'Voltar', 'delete' => 'Excluir', 'save-btn' => 'Salvar Configuração', 'save-success' => 'Configuração Salva com Sucesso.', 'search' => 'Pesquisar', 'select-country' => 'Selecionar País', 'select-state' => 'Selecionar Estado', 'title' => 'Configuração', 'general' => [ 'title' => 'Geral', 'info' => 'Configuração Geral', 'general' => [ 'title' => 'Geral', 'info' => 'Atualize suas configurações gerais aqui.', 'locale-settings' => [ 'title' => 'Configurações de Idioma', 'title-info' => 'Define o idioma usado na interface do usuário.', ], 'admin-logo' => [ 'logo-image' => 'Imagem do Logo', 'title' => 'Logo do Admin', 'title-info' => 'Configure a imagem do logo para o seu painel de administração.', ], ], 'settings' => [ 'title' => 'Settings', 'info' => 'Update your settings here.', 'footer' => [ 'info' => 'We can configure the powered by section here.', 'powered-by' => 'Powered by text editor', 'title' => 'Powered by Section Configurations', ], 'menu' => [ 'activities' => 'Activities', 'configuration' => 'Configuration', 'contacts' => 'Contacts', 'dashboard' => 'Dashboard', 'draft' => 'Draft', 'inbox' => 'Inbox', 'info' => 'We can configure the menu items name here.', 'leads' => 'Leads', 'mail' => 'Mail', 'organizations' => 'Organizations', 'outbox' => 'Outbox', 'persons' => 'Persons', 'products' => 'Products', 'quotes' => 'Quotes', 'sent' => 'Sent', 'settings' => 'Settings', 'title' => 'Menu Item Configurations', 'trash' => 'Trash', ], 'menu-color' => [ 'brand-color' => 'Brand Color', 'info' => 'We can change the menu items colors here.', 'title' => 'Menu Item Color Configurations', ], ], ], 'email' => [ 'title' => 'Configurações de E-mail', 'info' => 'Configuração de e-mail para a aplicação.', 'imap' => [ 'title' => 'Configurações IMAP', 'info' => 'Configuração de e-mail IMAP para receber emails.', 'account' => [ 'title' => 'Conta IMAP', 'title-info' => 'Configure as configurações da sua conta IMAP aqui.', 'host' => 'Host', 'port' => 'Porta', 'encryption' => 'Tipo de Criptografia', 'validate-cert' => 'Validar Certificado SSL', 'username' => 'Nome de Usuário IMAP', 'password' => 'Senha IMAP', ], ], ], 'magic-ai' => [ 'title' => 'Magic AI', 'info' => 'Configuração do Magic AI para a aplicação.', 'settings' => [ 'api-key' => 'Chave API', 'api-key-info' => 'Lembre-se de usar uma chave API do OpenRouter para cada modelo. É um passo simples para melhorar a segurança e o desempenho.', 'enable' => 'Habilitar', 'info' => 'Melhore sua experiência com o Magic AI com sua chave API do OpenRouter. Integre-a agora para uma aventura de IA personalizada e perfeita, feita sob medida para você! Personalize as configurações com facilidade e assuma o controle da sua jornada de IA.', 'other' => 'Outro Modelo', 'other-model' => 'Para outros modelos, use o ID do Modelo do OpenRouter.', 'doc-generation' => 'Geração de DOC', 'doc-generation-info' => 'Ative o recurso de geração de DOC para extrair automaticamente dados de arquivos DOC e convertê-los em formato de texto. Aumente sua produtividade e eficiência ativando este recurso para simplificar seu fluxo de trabalho.', 'title' => 'Configurações Gerais', 'models' => [ 'deepseek-r1' => 'Deepseek R1 Distill-llama-8b', 'gemini-2-0-flash-001' => 'Gemini 2.0 flash-001', 'gpt-4o' => 'GPT-4.0', 'gpt-4o-mini' => 'GPT-4.0 mini', 'grok-2-1212' => 'Grok 2.12', 'llama-3-2-3b-instruct' => 'Llama 3.2 3b Instruct', 'title' => 'Modelos', ], ], ], ], ], 'dashboard' => [ 'index' => [ 'title' => 'Início', 'start-date' => 'Start Date', 'end-date' => 'End Date', 'revenue' => [ 'lost-revenue' => 'Negócios Perdidos', 'won-revenue' => 'Negócios Ganhos', ], 'over-all' => [ 'average-lead-value' => 'Ticket médio', 'total-leads' => 'Total de negócios', 'average-leads-per-day' => 'Média de negócios por dia', 'total-quotations' => 'Total de cotações', 'total-persons' => 'Total de pessoas', 'total-organizations' => 'Total de empresas', ], 'total-leads' => [ 'title' => 'Negócios', 'total' => 'Total de negócios', 'won' => 'Negócios ganhos', 'lost' => 'Negócios perdidos', ], 'revenue-by-sources' => [ 'title' => 'Faturamento por origens', 'empty-title' => 'Ainda não há dados', 'empty-info' => 'Nenhum dado disponível para o intervalo selecionado', ], 'revenue-by-types' => [ 'title' => 'Faturamento por tipos', 'empty-title' => 'Ainda não há dados', 'empty-info' => 'Nenhum dado disponível para o intervalo selecionado', ], 'top-selling-products' => [ 'title' => 'Produtos mais vendidos', 'empty-title' => 'Ainda não há dados', 'empty-info' => 'Nenhum produto disponível para o intervalo selecionado', ], 'top-persons' => [ 'title' => 'Principais pessoas', 'empty-title' => 'Ainda não há dados', 'empty-info' => 'Nenhuma pessoa disponível para o intervalo selecionado', ], 'open-leads-by-states' => [ 'title' => 'Negócios por estágios', 'empty-title' => 'Ainda não há dados', 'empty-info' => 'Nenhum dado disponível para o intervalo selecionado', ], ], ], 'layouts' => [ 'app-version' => 'Versão: :version', 'dashboard' => 'Início', 'leads' => 'Oportunidades', 'quotes' => 'Cotações', 'quote' => 'Cotação', 'mail' => [ 'title' => 'E-mail', 'compose' => 'Escrever', 'inbox' => 'Caixa de Entrada', 'draft' => 'Rascunho', 'outbox' => 'Caixa de Saída', 'sent' => 'Enviado', 'trash' => 'Lixeira', 'setting' => 'Configurações', ], 'activities' => 'Atividades', 'contacts' => 'Contatos', 'persons' => 'Pessoas', 'person' => 'Pessoa', 'organizations' => 'Empresas', 'organization' => 'Empresa', 'products' => 'Produtos', 'product' => 'Produto', 'settings' => 'Configurações', 'user' => 'Usuário', 'user-info' => 'Gerencie todos os seus usuários e suas permissões no CRM, o que eles estão autorizados a fazer.', 'groups' => 'Grupos', 'groups-info' => 'Adicionar, editar ou excluir grupos do CRM', 'roles' => 'Funções', 'role' => 'Função', 'roles-info' => 'Adicionar, editar ou excluir funções do CRM', 'users' => 'Usuários', 'users-info' => 'Adicionar, editar ou excluir usuários do CRM', 'lead' => 'Negócio', 'lead-info' => 'Gerencie todas as configurações relacionadas aos Negócios no CRM', 'pipelines' => 'Funis', 'pipelines-info' => 'Adicionar, editar ou excluir funis do CRM', 'sources' => 'Origens', 'sources-info' => 'Adicionar, editar ou excluir origems do CRM', 'types' => 'Tipos', 'types-info' => 'Adicionar, editar ou excluir tipos do CRM', 'automation' => 'Automação', 'automation-info' => 'Gerencie todas as configurações de automação no CRM', 'attributes' => 'Atributos', 'attribute' => 'Atributo', 'attributes-info' => 'Adicionar, editar ou excluir atributos do CRM', 'email-templates' => 'Modelos de E-mail', 'email' => 'E-mail', 'email-templates-info' => 'Adicionar, editar ou excluir modelos de e-mail do CRM', 'events' => 'Events', 'events-info' => 'Add, edit or delete events from CRM', 'campaigns' => 'Campaigns', 'campaigns-info' => 'Add, edit or delete campaigns from CRM', 'workflows' => 'Fluxos de Trabalho', 'workflows-info' => 'Adicionar, editar ou excluir fluxos de trabalho do CRM', 'webhooks' => 'Webhooks', 'webhooks-info' => 'Add, edit or delete webhooks from CRM', 'other-settings' => 'Outras Configurações', 'other-settings-info' => 'Gerencie todas as configurações extras no CRM', 'tags' => 'Tags', 'tags-info' => 'Adicionar, editar ou excluir tags do CRM', 'my-account' => 'Minha conta', 'sign-out' => 'Sair', 'back' => 'Voltar', 'name' => 'Nome', 'configuration' => 'Configuração', 'howdy' => 'Olá!', 'warehouses' => 'Depósitos', 'warehouse' => 'Depósito', 'warehouses-info' => 'Adicionar, editar ou excluir depósitos do CRM', 'inventory' => 'Estoque', 'inventory-info' => 'Gerenciar todas as configurações relacionadas ao estoque no CRM', 'data_transfer' => 'Data Transfer', 'data_transfer_info' => 'Manage persons, products and leads data transfer related settings in the CRM', ], 'user' => [ 'account' => [ 'name' => 'Nome', 'email' => 'E-mail', 'password' => 'Senha', 'my_account' => 'Minha conta', 'update_details' => 'Atualizar Detalhes', 'current_password' => 'Senha atual', 'confirm_password' => 'Confirmar senha', 'password-match' => 'A senha atual não corresponde.', 'account-save' => 'Alterações na conta salvas com sucesso.', 'permission-denied' => 'Permissão Negada', 'remove-image' => 'Remover Imagem', 'upload_image_pix' => 'Carregar uma Imagem de Perfil (100px x 100px)', 'upload_image_format' => 'em formato PNG ou JPG', 'image_upload_message' => 'Somente imagens (.jpeg, .jpg, .png, ..) são permitidas.', ], ], 'emails' => [ 'common' => [ 'dear' => 'Prezado(a) :name', 'cheers' => 'Atenciosamente,
Equipe :app_name', 'user' => [ 'dear' => 'Prezado(a) :username', 'create-subject' => 'Você foi adicionado como membro.', 'create-body' => 'Parabéns! Agora você é um membro da nossa equipe.', 'forget-password' => [ 'subject' => 'Redefinir Senha do Cliente', 'dear' => 'Prezado(a) :username', 'reset-password' => 'Redefinir Senha', 'info' => 'Você está recebendo este e-mail porque recebemos uma solicitação de redefinição de senha para sua conta.', 'final-summary' => 'Se você não solicitou a redefinição de senha, nenhuma outra ação é necessária.', 'thanks' => 'Obrigado!', ], ], ], ], 'validations' => [ 'message' => [ 'decimal' => 'The :attribute must be a decimal.', ], ], 'errors' => [ 'dashboard' => 'Dashboard', 'go-back' => 'Go Back', 'support' => 'If the problem persists, reach out to us at :email for assistance.', '404' => [ 'description' => 'Oops! The page you\'re looking for is on vacation. It seems we couldn\'t find what you were searching for.', 'title' => '404 Page Not Found', ], '401' => [ 'description' => 'Ops! Parece que você não tem permissão para acessar esta página. Parece que estão faltando as credenciais necessárias.', 'title' => '401 Não autorizado.', ], '403' => [ 'description' => 'Oops! This page is off-limits. It appears you don\'t have the required permissions to view this content.', 'title' => '403 Forbidden', ], '500' => [ 'description' => 'Oops! Something went wrong. It seems we\'re having trouble loading the page you\'re looking for.', 'title' => '500 Internal Server Error', ], '503' => [ 'description' => 'Oops! Looks like we\'re temporarily down for maintenance. Please check back in a bit.', 'title' => '503 Service Unavailable', ], ], 'export' => [ 'csv' => 'CSV', 'download' => 'Download', 'export' => 'Exportar', 'no-records' => 'Nenhum registro encontrado.', 'xls' => 'XLS', 'xlsx' => 'XLSX', ], ]; ================================================ FILE: packages/Webkul/Admin/src/Resources/lang/tr/app.php ================================================ [ 'leads' => 'Potansiyeller', 'lead' => 'Potansiyel', 'quotes' => 'Teklifler', 'mail' => 'Posta', 'inbox' => 'Gelen Kutusu', 'draft' => 'Taslak', 'outbox' => 'Gönderilenler', 'sent' => 'Gönderildi', 'trash' => 'Çöp Kutusu', 'activities' => 'Etkinlikler', 'webhook' => 'Web Kancası', 'contacts' => 'Kişiler', 'persons' => 'Kişiler', 'organizations' => 'Organizasyonlar', 'products' => 'Ürünler', 'settings' => 'Ayarlar', 'groups' => 'Gruplar', 'roles' => 'Roller', 'users' => 'Kullanıcılar', 'user' => 'Kullanıcı', 'automation' => 'Otomasyon', 'attributes' => 'Öznitelikler', 'pipelines' => 'Pipelines', 'sources' => 'Kaynaklar', 'types' => 'Türler', 'email-templates' => 'E-posta Şablonları', 'workflows' => 'İş Akışları', 'other-settings' => 'Diğer Ayarlar', 'tags' => 'Etiketler', 'configuration' => 'Yapılandırma', 'create' => 'Oluştur', 'edit' => 'Düzenle', 'view' => 'Görüntüle', 'print' => 'Yazdır', 'delete' => 'Sil', 'export' => 'Dışa Aktar', 'mass-delete' => 'Toplu Sil', 'data-transfer' => 'Veri Transferi', 'imports' => 'İthalatlar', 'import' => 'İthalat', 'event' => 'Etkinlik', 'campaigns' => 'Kampanyalar', 'warehouses' => 'Depolar', 'inventory' => 'Envanter', ], 'users' => [ 'activate-warning' => 'Hesabınız henüz etkinleştirilmedi. Lütfen yönetici ile iletişime geçin.', 'login-error' => 'Kimlik bilgileri kayıtlarımızla eşleşmiyor.', 'not-permission' => 'Yönetici paneline erişim izniniz yok.', 'login' => [ 'email' => 'E-posta Adresi', 'forget-password-link' => 'Şifremi Unuttum?', 'password' => 'Şifre', 'submit-btn' => 'Giriş Yap', 'title' => 'Giriş Yap', ], 'forget-password' => [ 'create' => [ 'email' => 'Kayıtlı E-posta', 'email-not-exist' => 'E-posta Mevcut Değil', 'page-title' => 'Şifremi Unuttum', 'reset-link-sent' => 'Şifre sıfırlama bağlantısı gönderildi', 'sign-in-link' => 'Giriş Yapmaya Dön?', 'submit-btn' => 'Sıfırla', 'title' => 'Şifre Kurtarma', ], ], 'reset-password' => [ 'back-link-title' => 'Giriş Yapmaya Dön?', 'confirm-password' => 'Şifreyi Onayla', 'email' => 'Kayıtlı E-posta', 'password' => 'Şifre', 'submit-btn' => 'Şifreyi Sıfırla', 'title' => 'Şifre Sıfırlama', ], ], 'account' => [ 'edit' => [ 'back-btn' => 'Geri', 'change-password' => 'Şifreyi Değiştir', 'confirm-password' => 'Şifreyi Onayla', 'current-password' => 'Mevcut Şifre', 'email' => 'E-posta', 'general' => 'Genel', 'invalid-password' => 'Girdiğiniz mevcut şifre yanlış.', 'name' => 'Ad', 'password' => 'Şifre', 'profile-image' => 'Profil Resmi', 'save-btn' => 'Hesabı Kaydet', 'title' => 'Hesabım', 'update-success' => 'Hesap başarıyla güncellendi', 'upload-image-info' => 'Profil Resmi Yükleyin (110px X 110px) PNG veya JPG Formatında', ], ], 'components' => [ 'activities' => [ 'actions' => [ 'mail' => [ 'btn' => 'Mail', 'title' => 'Compose Mail', 'to' => 'To', 'enter-emails' => 'Press enter to add emails', 'cc' => 'CC', 'bcc' => 'BCC', 'subject' => 'Subject', 'send-btn' => 'Send', 'message' => 'Message', ], 'file' => [ 'btn' => 'File', 'title' => 'Add File', 'title-control' => 'Title', 'name' => 'Name', 'description' => 'Description', 'file' => 'File', 'save-btn' => 'Save File', ], 'note' => [ 'btn' => 'Note', 'title' => 'Add Note', 'comment' => 'Comment', 'save-btn' => 'Save Note', ], 'activity' => [ 'btn' => 'Activity', 'title' => 'Add Activity', 'title-control' => 'Title', 'description' => 'Description', 'schedule-from' => 'Schedule From', 'schedule-to' => 'Schedule To', 'location' => 'Location', 'call' => 'Call', 'meeting' => 'Meeting', 'lunch' => 'Lunch', 'save-btn' => 'Save Activity', 'participants' => [ 'title' => 'Participants', 'placeholder' => 'Type to search participants', 'users' => 'Users', 'persons' => 'Persons', 'no-results' => 'No result found...', ], ], ], 'index' => [ 'all' => 'All', 'bcc' => 'Bcc', 'by-user' => 'By :user', 'calls' => 'Calls', 'cc' => 'Cc', 'change-log' => 'Changelogs', 'delete' => 'Delete', 'edit' => 'Edit', 'emails' => 'Emails', 'empty' => 'Empty', 'files' => 'Files', 'from' => 'From', 'location' => 'Location', 'lunches' => 'Lunches', 'mark-as-done' => 'Mark as Done', 'meetings' => 'Meetings', 'notes' => 'Notes', 'participants' => 'Participants', 'planned' => 'Planned', 'quotes' => 'Quotes', 'scheduled-on' => 'Scheduled on', 'system' => 'System', 'to' => 'To', 'unlink' => 'Unlink', 'view' => 'View', 'empty-placeholders' => [ 'all' => [ 'title' => 'No Activities Found', 'description' => 'No activities found for this. You can add activities by clicking on the Activity button on the left panel.', ], 'planned' => [ 'title' => 'No Planned Activities Found', 'description' => 'No planned activities found for this. You can add planned activities by clicking on the Activity button on the left panel.', ], 'notes' => [ 'title' => 'No Notes Found', 'description' => 'No notes found for this. You can add notes by clicking on the Note button on the left panel.', ], 'calls' => [ 'title' => 'No Calls Found', 'description' => 'No calls found for this. You can add calls by clicking on the Activity button on the left panel and selecting the Call type.', ], 'meetings' => [ 'title' => 'No Meetings Found', 'description' => 'No meetings found for this. You can add meetings by clicking on the Activity button on the left panel and selecting the Meeting type.', ], 'lunches' => [ 'title' => 'No Lunches Found', 'description' => 'No lunches found for this. You can add lunches by clicking on the Activity button on the left panel and selecting the Lunch type.', ], 'files' => [ 'title' => 'No Files Found', 'description' => 'No files found for this. You can add files by clicking on the File button on the left panel.', ], 'emails' => [ 'title' => 'No Emails Found', 'description' => 'No emails found for this. You can add emails by clicking on the Mail button on the left panel.', ], 'system' => [ 'title' => 'No Changelogs Found', 'description' => 'No changelogs found for this.', ], ], ], ], 'media' => [ 'images' => [ 'add-image-btn' => 'Resim Ekle', 'ai-add-image-btn' => 'Sihirli AI', 'allowed-types' => 'png, jpeg, jpg', 'not-allowed-error' => 'Sadece resim dosyalarına (.jpeg, .jpg, .png, vb.) izin verilmektedir.', 'placeholders' => [ 'front' => 'Ön', 'next' => 'Sonraki', 'size' => 'Boyut', 'use-cases' => 'Kullanım Alanları', 'zoom' => 'Yakınlaştır', ], ], 'videos' => [ 'add-video-btn' => 'Video Ekle', 'allowed-types' => 'mp4, webm, mkv', 'not-allowed-error' => 'Sadece video dosyalarına (.mp4, .mov, .ogg vb.) izin verilmektedir.', ], ], 'datagrid' => [ 'index' => [ 'no-records-selected' => 'Hiçbir kayıt seçilmedi.', 'must-select-a-mass-action-option' => 'Bir toplu işlem seçeneği seçmelisiniz.', 'must-select-a-mass-action' => 'Bir toplu işlem seçmelisiniz.', ], 'toolbar' => [ 'length-of' => ':length kadar', 'of' => 'üzerinden', 'per-page' => 'Sayfa Başına', 'results' => ':total Sonuç', 'delete' => 'Sil', 'selected' => ':total Seçilen Öğeler', 'mass-actions' => [ 'submit' => 'Gönder', 'select-option' => 'Seçim Yap', 'select-action' => 'Eylem Seç', ], 'filter' => [ 'apply-filters-btn' => 'Filtreleri Uygula', 'back-btn' => 'Geri', 'create-new-filter' => 'Yeni Filtre Oluştur', 'custom-filters' => 'Özel Filtreler', 'delete-error' => 'Filtre silinirken bir hata oluştu, lütfen tekrar deneyin.', 'delete-success' => 'Filtre başarıyla silindi.', 'empty-description' => 'Kaydedilecek seçili filtre bulunmamaktadır. Lütfen kaydetmek için filtreler seçin.', 'empty-title' => 'Kaydetmek İçin Filtreler Ekleyin', 'name' => 'Ad', 'quick-filters' => 'Hızlı Filtreler', 'save-btn' => 'Kaydet', 'save-filter' => 'Filtreyi Kaydet', 'saved-success' => 'Filtre başarıyla kaydedildi.', 'selected-filters' => 'Seçilen Filtreler', 'title' => 'Filtre', 'update' => 'Güncelle', 'update-filter' => 'Filtreyi Güncelle', 'updated-success' => 'Filtre başarıyla güncellendi.', ], 'search' => [ 'title' => 'Ara', ], ], 'filters' => [ 'select' => 'Seç', 'title' => 'Filtreler', 'dropdown' => [ 'searchable' => [ 'at-least-two-chars' => 'En az 2 karakter yazın...', 'no-results' => 'Sonuç bulunamadı...', ], ], 'custom-filters' => [ 'clear-all' => 'Hepsini Temizle', 'title' => 'Özel Filtreler', ], 'boolean-options' => [ 'false' => 'Yanlış', 'true' => 'Doğru', ], 'date-options' => [ 'last-month' => 'Geçen Ay', 'last-six-months' => 'Son 6 Ay', 'last-three-months' => 'Son 3 Ay', 'this-month' => 'Bu Ay', 'this-week' => 'Bu Hafta', 'this-year' => 'Bu Yıl', 'today' => 'Bugün', 'yesterday' => 'Dün', ], ], 'table' => [ 'actions' => 'Eylemler', 'no-records-available' => 'Kayıt Bulunmuyor.', ], ], 'modal' => [ 'confirm' => [ 'agree-btn' => 'Kabul Et', 'disagree-btn' => 'Reddet', 'message' => 'Bu işlemi gerçekleştirmek istediğinizden emin misiniz?', 'title' => 'Emin Misiniz?', ], ], 'tags' => [ 'index' => [ 'title' => 'Etiketler', 'added-tags' => 'Eklenen Etiketler', 'save-btn' => 'Etiketi Kaydet', 'placeholder' => 'Etiketleri aramak için yazın', 'add-tag' => '\\" :term \\" Ekle...', 'aquarelle-red' => 'Aquarelle Kırmızı', 'crushed-cashew' => 'Ezilmiş Antep Fıstığı', 'beeswax' => 'Abeş Mum', 'lemon-chiffon' => 'Limon Şifon', 'snow-flurry' => 'Kar Fırtınası', 'honeydew' => 'Honeydew', ], ], 'layouts' => [ 'powered-by' => [ 'description' => ':webkul tarafından geliştirilen açık kaynaklı bir proje olan :krayin tarafından desteklenmektedir.', ], 'header' => [ 'mega-search' => [ 'title' => 'Mega Arama', 'tabs' => [ 'leads' => 'Müşteriler', 'quotes' => 'Teklifler', 'persons' => 'Kişiler', 'products' => 'Ürünler', ], 'explore-all-products' => 'Tüm Ürünleri Keşfet', 'explore-all-leads' => 'Tüm Müşterileri Keşfet', 'explore-all-contacts' => 'Tüm İletişimleri Keşfet', 'explore-all-quotes' => 'Tüm Teklifleri Keşfet', 'explore-all-matching-products' => '":query" (:count) ile eşleşen tüm ürünleri keşfet', 'explore-all-matching-leads' => '":query" (:count) ile eşleşen tüm müşterileri keşfet', 'explore-all-matching-contacts' => '":query" (:count) ile eşleşen tüm iletişimleri keşfet', 'explore-all-matching-quotes' => '":query" (:count) ile eşleşen tüm teklifleri keşfet', ], ], ], 'attributes' => [ 'edit' => [ 'delete' => 'Sil', ], 'lookup' => [ 'click-to-add' => 'Eklemek için tıklayın', 'search' => 'Arama...', 'no-result-found' => 'Sonuç bulunamadı', ], ], 'lookup' => [ 'click-to-add' => 'Eklemek için Tıklayın', 'no-results' => 'Sonuç Bulunamadı', 'add-as-new' => 'Yeni Olarak Ekle', 'search' => 'Arama...', ], 'flash-group' => [ 'success' => 'Başarı', 'error' => 'Hata', 'warning' => 'Uyarı', 'info' => 'Bilgi', ], 'tiny-mce' => [ 'http-error' => 'HTTP Hatası', 'invalid-json' => 'Sunucudan geçersiz JSON yanıtı.', 'upload-failed' => 'Dosya yüklemesi başarısız oldu. Lütfen tekrar deneyin.', ], ], 'quotes' => [ 'index' => [ 'title' => 'Teklifler', 'create-btn' => 'Teklif Oluştur', 'create-success' => 'Teklif başarıyla oluşturuldu.', 'update-success' => 'Teklif başarıyla güncellendi.', 'delete-success' => 'Teklif başarıyla silindi.', 'delete-failed' => 'Teklif silinemedi.', 'datagrid' => [ 'subject' => 'Konu', 'sales-person' => 'Satış Temsilcisi', 'expired-at' => 'Son Kullanma Tarihi', 'created-at' => 'Oluşturulma Tarihi', 'person' => 'Kişi', 'subtotal' => 'Ara Toplam', 'discount' => 'İndirim', 'tax' => 'Vergi', 'adjustment' => 'Düzenleme', 'grand-total' => 'Genel Toplam', 'edit' => 'Düzenle', 'delete' => 'Sil', 'print' => 'Yazdır', ], 'pdf' => [ 'adjustment' => 'Düzenleme', 'amount' => 'Tutar', 'billing-address' => 'Fatura Adresi', 'date' => 'Tarih', 'discount' => 'İndirim', 'expired-at' => 'Son Kullanma Tarihi', 'grand-total' => 'Genel Toplam', 'person' => 'Kişi', 'price' => 'Fiyat', 'product-name' => 'Ürün Adı', 'quantity' => 'Miktar', 'quote-id' => 'Teklif ID', 'sales-person' => 'Satış Temsilcisi', 'shipping-address' => 'Teslimat Adresi', 'sku' => 'SKU', 'sub-total' => 'Ara Toplam', 'subject' => 'Konu', 'tax' => 'Vergi', 'title' => 'Teklif', ], ], 'create' => [ 'title' => 'Teklif Oluştur', 'save-btn' => 'Teklifi Kaydet', 'quote-info' => 'Teklif Bilgileri', 'quote-info-info' => 'Teklifin temel bilgilerini girin.', 'address-info' => 'Adres Bilgileri', 'address-info-info' => 'Teklif ile ilgili adres bilgileri.', 'quote-items' => 'Teklif Kalemleri', 'search-products' => 'Ürünleri Ara', 'link-to-lead' => 'Potansiyele Bağla', 'quote-item-info' => 'Bu teklif için ürün talebi ekleyin.', 'quote-name' => 'Teklif Adı', 'quantity' => 'Miktar', 'price' => 'Fiyat', 'discount' => 'İndirim', 'tax' => 'Vergi', 'total' => 'Toplam', 'amount' => 'Tutar', 'add-item' => '+ Kalem Ekle', 'sub-total' => 'Ara Toplam (:symbol)', 'total-discount' => 'İndirim (:symbol)', 'total-tax' => 'Vergi (:symbol)', 'total-adjustment' => 'Düzenleme (:symbol)', 'grand-total' => 'Genel Toplam (:symbol)', 'discount-amount' => 'İndirim Tutarı', 'tax-amount' => 'Vergi Tutarı', 'adjustment-amount' => 'Düzenleme Tutarı', 'product-name' => 'Ürün Adı', 'action' => 'Eylem', ], 'edit' => [ 'title' => 'Teklifi Düzenle', 'save-btn' => 'Teklifi Kaydet', 'quote-info' => 'Teklif Bilgileri', 'quote-info-info' => 'Teklifin temel bilgilerini girin.', 'address-info' => 'Adres Bilgileri', 'address-info-info' => 'Teklif ile ilgili adres bilgileri.', 'quote-items' => 'Teklif Kalemleri', 'link-to-lead' => 'Potansiyele Bağla', 'quote-item-info' => 'Bu teklif için ürün talebi ekleyin.', 'quote-name' => 'Teklif Adı', 'quantity' => 'Miktar', 'price' => 'Fiyat', 'search-products' => 'Ürünleri Ara', 'discount' => 'İndirim', 'tax' => 'Vergi', 'total' => 'Toplam', 'amount' => 'Tutar', 'add-item' => '+ Kalem Ekle', 'sub-total' => 'Ara Toplam (:symbol)', 'total-discount' => 'İndirim (:symbol)', 'total-tax' => 'Vergi (:symbol)', 'total-adjustment' => 'Düzenleme (:symbol)', 'grand-total' => 'Genel Toplam (:symbol)', 'discount-amount' => 'İndirim Tutarı', 'tax-amount' => 'Vergi Tutarı', 'adjustment-amount' => 'Düzenleme Tutarı', 'product-name' => 'Ürün Adı', 'action' => 'Eylem', ], ], 'contacts' => [ 'persons' => [ 'index' => [ 'title' => 'Kişiler', 'create-btn' => 'Kişi Oluştur', 'create-success' => 'Kişi başarıyla oluşturuldu.', 'update-success' => 'Kişi başarıyla güncellendi.', 'all-delete-success' => 'Seçilen tüm kişiler başarıyla silindi.', 'partial-delete-warning' => 'Bazı kişiler başarıyla silindi. Diğerleri potansiyel müşterilerle bağlantılı olduğu için silinemedi.', 'none-delete-warning' => 'Seçilen kişilerin hiçbiri potansiyel müşterilerle bağlantılı olduğu için silinemedi.', 'no-selection' => 'Silinecek kişi seçilmedi.', 'delete-failed' => 'Seçilen kişiler silinemedi.', 'datagrid' => [ 'contact-numbers' => 'İletişim Numaraları', 'delete' => 'Sil', 'edit' => 'Düzenle', 'emails' => 'E-postalar', 'id' => 'ID', 'view' => 'Görüntüle', 'name' => 'Ad', 'organization-name' => 'Kuruluş Adı', ], ], 'view' => [ 'title' => ':name', 'about-person' => 'Kişi Hakkında', 'about-organization' => 'Kuruluş Hakkında', 'activities' => [ 'index' => [ 'all' => 'Hepsi', 'calls' => 'Aramalar', 'meetings' => 'Toplantılar', 'lunches' => 'Öğle Yemekleri', 'files' => 'Dosyalar', 'quotes' => 'Teklifler', 'notes' => 'Notlar', 'emails' => 'E-postalar', 'by-user' => ':user tarafından', 'scheduled-on' => 'Planlandığı Tarih', 'location' => 'Konum', 'participants' => 'Katılımcılar', 'mark-as-done' => 'Tamamlandı olarak işaretle', 'delete' => 'Sil', 'edit' => 'Düzenle', ], 'actions' => [ 'mail' => [ 'btn' => 'E-posta', 'title' => 'E-posta Oluştur', 'to' => 'Kime', 'cc' => 'CC', 'bcc' => 'BCC', 'subject' => 'Konu', 'send-btn' => 'Gönder', 'message' => 'Mesaj', ], 'file' => [ 'btn' => 'Dosya', 'title' => 'Dosya Ekle', 'title-control' => 'Başlık', 'name' => 'Dosya Adı', 'description' => 'Açıklama', 'file' => 'Dosya', 'save-btn' => 'Dosyayı Kaydet', ], 'note' => [ 'btn' => 'Not', 'title' => 'Not Ekle', 'comment' => 'Yorum', 'save-btn' => 'Notu Kaydet', ], 'activity' => [ 'btn' => 'Etkinlik', 'title' => 'Etkinlik Ekle', 'title-control' => 'Başlık', 'description' => 'Açıklama', 'schedule-from' => 'Başlangıç Tarihi', 'schedule-to' => 'Bitiş Tarihi', 'location' => 'Konum', 'call' => 'Çağrı', 'meeting' => 'Toplantı', 'lunch' => 'Öğle Yemeği', 'save-btn' => 'Etkinliği Kaydet', ], ], ], 'tags' => [ 'create-success' => 'Etiket başarıyla oluşturuldu.', 'destroy-success' => 'Etiket başarıyla silindi.', ], ], 'create' => [ 'title' => 'Kişi Oluştur', 'save-btn' => 'Kişiyi Kaydet', ], 'edit' => [ 'title' => 'Kişiyi Düzenle', 'save-btn' => 'Kişiyi Kaydet', ], ], 'organizations' => [ 'index' => [ 'title' => 'Kuruluşlar', 'create-btn' => 'Kuruluş Oluştur', 'create-success' => 'Kuruluş başarıyla oluşturuldu.', 'update-success' => 'Kuruluş başarıyla güncellendi.', 'delete-success' => 'Kuruluş başarıyla silindi.', 'delete-failed' => 'Kuruluş silinemedi.', 'datagrid' => [ 'delete' => 'Sil', 'edit' => 'Düzenle', 'id' => 'ID', 'name' => 'Ad', 'persons-count' => 'Kişi Sayısı', ], ], 'create' => [ 'title' => 'Kuruluş Oluştur', 'save-btn' => 'Kuruluşu Kaydet', ], 'edit' => [ 'title' => 'Kuruluşu Düzenle', 'save-btn' => 'Kuruluşu Kaydet', ], ], ], 'products' => [ 'index' => [ 'title' => 'Ürünler', 'create-btn' => 'Ürün Oluştur', 'create-success' => 'Ürün başarıyla oluşturuldu.', 'update-success' => 'Ürün başarıyla güncellendi.', 'delete-success' => 'Ürün başarıyla silindi.', 'delete-failed' => 'Ürün silinemedi.', 'datagrid' => [ 'allocated' => 'Tahsis Edilen', 'delete' => 'Sil', 'edit' => 'Düzenle', 'id' => 'ID', 'in-stock' => 'Stokta', 'name' => 'Ad', 'on-hand' => 'Elinde', 'tag-name' => 'Etiket Adı', 'price' => 'Fiyat', 'sku' => 'SKU', 'view' => 'Görüntüle', ], ], 'create' => [ 'save-btn' => 'Ürünleri Kaydet', 'title' => 'Ürün Oluştur', 'general' => 'Genel', 'price' => 'Fiyat', ], 'edit' => [ 'title' => 'Ürünleri Düzenle', 'save-btn' => 'Ürünleri Kaydet', 'general' => 'Genel', 'price' => 'Fiyat', ], 'view' => [ 'sku' => 'SKU', 'all' => 'Hepsi', 'notes' => 'Notlar', 'files' => 'Dosyalar', 'inventories' => 'Envanter', 'change-logs' => 'Değişiklik Günlükleri', 'attributes' => [ 'about-product' => 'Ürün Hakkında', ], 'inventory' => [ 'source' => 'Kaynak', 'in-stock' => 'Stokta', 'allocated' => 'Tahsis Edilen', 'on-hand' => 'Elinde', 'actions' => 'İşlemler', 'assign' => 'Ata', 'add-source' => 'Kaynak Ekle', 'location' => 'Konum', 'add-more' => 'Daha Fazla Ekle', 'save' => 'Kaydet', ], ], ], 'settings' => [ 'title' => 'Ayarlar', 'groups' => [ 'index' => [ 'create-btn' => 'Grup Oluştur', 'title' => 'Gruplar', 'create-success' => 'Grup başarıyla oluşturuldu.', 'update-success' => 'Grup başarıyla güncellendi.', 'destroy-success' => 'Grup başarıyla silindi.', 'delete-failed' => 'Grup silinemedi.', 'delete-failed-associated-users' => 'Grup silinemiyor, çünkü kullanıcılar tarafından kullanılıyor.', 'datagrid' => [ 'delete' => 'Sil', 'description' => 'Açıklama', 'edit' => 'Düzenle', 'id' => 'ID', 'name' => 'Ad', ], 'edit' => [ 'title' => 'Grubu Düzenle', ], 'create' => [ 'name' => 'Ad', 'title' => 'Grup Oluştur', 'description' => 'Açıklama', 'save-btn' => 'Grubu Kaydet', ], ], ], 'roles' => [ 'index' => [ 'being-used' => 'Rol silinemedi, çünkü bu admin kullanıcısında kullanılıyor.', 'create-btn' => 'Rol Oluştur', 'create-success' => 'Rol başarıyla oluşturuldu.', 'current-role-delete-error' => 'Mevcut kullanıcıya atanmış rol silinemedi.', 'delete-failed' => 'Rol silinemedi.', 'delete-success' => 'Rol başarıyla silindi.', 'last-delete-error' => 'En az bir rol gereklidir.', 'settings' => 'Ayarlar', 'title' => 'Roller', 'update-success' => 'Rol başarıyla güncellendi.', 'user-define-error' => 'Sistem rolü silinemedi.', 'datagrid' => [ 'all' => 'Hepsi', 'custom' => 'Özel', 'delete' => 'Sil', 'description' => 'Açıklama', 'edit' => 'Düzenle', 'id' => 'ID', 'name' => 'Ad', 'permission-type' => 'İzin Türü', ], ], 'create' => [ 'access-control' => 'Erişim Kontrolü', 'all' => 'Hepsi', 'back-btn' => 'Geri', 'custom' => 'Özel', 'description' => 'Açıklama', 'general' => 'Genel', 'name' => 'Ad', 'permissions' => 'İzinler', 'save-btn' => 'Rolü Kaydet', 'title' => 'Rol Oluştur', ], 'edit' => [ 'access-control' => 'Erişim Kontrolü', 'all' => 'Hepsi', 'back-btn' => 'Geri', 'custom' => 'Özel', 'description' => 'Açıklama', 'general' => 'Genel', 'name' => 'Ad', 'permissions' => 'İzinler', 'save-btn' => 'Rolü Kaydet', 'title' => 'Rol Düzenle', ], ], 'types' => [ 'index' => [ 'create-btn' => 'Tür Oluştur', 'create-success' => 'Tür başarıyla oluşturuldu.', 'delete-failed' => 'Tür silinemedi.', 'delete-success' => 'Tür başarıyla silindi.', 'title' => 'Türler', 'update-success' => 'Tür başarıyla güncellendi.', 'datagrid' => [ 'delete' => 'Sil', 'description' => 'Açıklama', 'edit' => 'Düzenle', 'id' => 'ID', 'name' => 'Ad', ], 'create' => [ 'name' => 'Ad', 'save-btn' => 'Türü Kaydet', 'title' => 'Tür Oluştur', ], 'edit' => [ 'title' => 'Tür Düzenle', ], ], ], 'sources' => [ 'index' => [ 'title' => 'Kaynaklar', 'create-btn' => 'Kaynak Oluştur', 'create-success' => 'Kaynak başarıyla oluşturuldu.', 'delete-failed' => 'Kaynak silinemedi.', 'delete-success' => 'Kaynak başarıyla silindi.', 'update-success' => 'Kaynak başarıyla güncellendi.', 'delete-failed-associated-leads' => 'Kaynak silinemiyor çünkü mevcut adaylarla ilişkili. Lütfen bu adayları silmeden önce bağlantılarını kaldırın veya güncelleyin.', 'datagrid' => [ 'delete' => 'Sil', 'edit' => 'Düzenle', 'id' => 'ID', 'name' => 'Ad', ], 'create' => [ 'name' => 'Ad', 'save-btn' => 'Kaynağı Kaydet', 'title' => 'Kaynak Oluştur', ], 'edit' => [ 'title' => 'Kaynağı Düzenle', ], ], ], 'workflows' => [ 'index' => [ 'title' => 'İş Akışları', 'create-btn' => 'İş Akışı Oluştur', 'create-success' => 'İş akışı başarıyla oluşturuldu.', 'update-success' => 'İş akışı başarıyla güncellendi.', 'delete-success' => 'İş akışı başarıyla silindi.', 'delete-failed' => 'İş akışı silinemedi.', 'datagrid' => [ 'delete' => 'Sil', 'description' => 'Açıklama', 'edit' => 'Düzenle', 'id' => 'ID', 'name' => 'Ad', ], ], 'helpers' => [ 'update-related-leads' => 'İlgili fırsatları güncelle', 'send-email-to-sales-owner' => 'Satış sahibiyle e-posta gönder', 'send-email-to-participants' => 'Katılımcılara e-posta gönder', 'add-webhook' => 'Webhook Ekle', 'update-lead' => 'Fırsatı Güncelle', 'update-person' => 'Kişiyi Güncelle', 'send-email-to-person' => 'Kişiye e-posta gönder', 'add-tag' => 'Etiket Ekle', 'add-note-as-activity' => 'Notu Aktivite Olarak Ekle', 'update-quote' => 'Teklifi Güncelle', ], 'create' => [ 'title' => 'İş Akışı Oluştur', 'event' => 'Olay', 'back-btn' => 'Geri', 'save-btn' => 'İş Akışını Kaydet', 'name' => 'Ad', 'basic-details' => 'Temel Bilgiler', 'description' => 'Açıklama', 'actions' => 'Eylemler', 'basic-details-info' => 'İş akışının temel bilgilerini girin.', 'event-info' => 'Bir olay tetikler, kontrolleri yapar, koşulları değerlendirir ve önceden tanımlanmış eylemleri gerçekleştirir.', 'conditions' => 'Koşullar', 'conditions-info' => 'Koşullar, belirli durumlarda tetiklenen kurallardır.', 'actions-info' => 'Bir eylem sadece iş yükünü azaltmakla kalmaz, aynı zamanda CRM otomasyonu için oldukça kolaylaştırır.', 'value' => 'Değer', 'condition-type' => 'Koşul Türü', 'all-condition-are-true' => 'Tüm koşullar doğru', 'any-condition-are-true' => 'Herhangi bir koşul doğru', 'add-condition' => 'Koşul Ekle', 'add-action' => 'Eylem Ekle', 'yes' => 'Evet', 'no' => 'Hayır', 'email' => 'E-posta', 'is-equal-to' => 'Eşittir', 'is-not-equal-to' => 'Eşit değildir', 'equals-or-greater-than' => 'Eşittir veya büyük', 'equals-or-less-than' => 'Eşittir veya küçük', 'greater-than' => 'Büyük', 'less-than' => 'Küçük', 'type' => 'Tür', 'contain' => 'İçerir', 'contains' => 'İçerir', 'does-not-contain' => 'İçermez', ], 'edit' => [ 'title' => 'İş Akışını Düzenle', 'event' => 'Olay', 'back-btn' => 'Geri', 'save-btn' => 'İş Akışını Kaydet', 'name' => 'Ad', 'basic-details' => 'Temel Bilgiler', 'description' => 'Açıklama', 'actions' => 'Eylemler', 'type' => 'Tür', 'basic-details-info' => 'İş akışının temel bilgilerini girin.', 'event-info' => 'Bir olay tetikler, kontrolleri yapar, koşulları değerlendirir ve önceden tanımlanmış eylemleri gerçekleştirir.', 'conditions' => 'Koşullar', 'conditions-info' => 'Koşullar, belirli durumlarda tetiklenen kurallardır.', 'actions-info' => 'Bir eylem sadece iş yükünü azaltmakla kalmaz, aynı zamanda CRM otomasyonu için oldukça kolaylaştırır.', 'value' => 'Değer', 'condition-type' => 'Koşul Türü', 'all-condition-are-true' => 'Tüm koşullar doğru', 'any-condition-are-true' => 'Herhangi bir koşul doğru', 'add-condition' => 'Koşul Ekle', 'add-action' => 'Eylem Ekle', 'yes' => 'Evet', 'no' => 'Hayır', 'email' => 'E-posta', 'is-equal-to' => 'Eşittir', 'is-not-equal-to' => 'Eşit değildir', 'equals-or-greater-than' => 'Eşittir veya büyük', 'equals-or-less-than' => 'Eşittir veya küçük', 'greater-than' => 'Büyük', 'less-than' => 'Küçük', 'contain' => 'İçerir', 'contains' => 'İçerir', 'does-not-contain' => 'İçermez', ], ], 'webforms' => [ 'index' => [ 'title' => 'Web Formları', 'create-btn' => 'Web Formu Oluştur', 'create-success' => 'Web formu başarıyla oluşturuldu.', 'update-success' => 'Web formu başarıyla güncellendi.', 'delete-success' => 'Web formu başarıyla silindi.', 'delete-failed' => 'Web formu silinemedi.', 'datagrid' => [ 'id' => 'ID', 'title' => 'Başlık', 'edit' => 'Düzenle', 'delete' => 'Sil', ], ], 'create' => [ 'title' => 'Web Formu Oluştur', 'add-attribute-btn' => 'Öznitelik Düğmesi Ekle', 'attribute-label-color' => 'Öznitelik Etiketi Rengi', 'attributes' => 'Öznitelikler', 'attributes-info' => 'Forma özel öznitelikler ekleyin.', 'background-color' => 'Arka Plan Rengi', 'create-lead' => 'Fırsat Oluştur', 'customize-webform' => 'Web Formunu Özelleştir', 'customize-webform-info' => 'Web formunuzu seçtiğiniz eleman renkleri ile özelleştirin.', 'description' => 'Açıklama', 'display-custom-message' => 'Özel mesaj göster', 'form-background-color' => 'Form Arka Plan Rengi', 'form-submit-btn-color' => 'Form Gönderim Düğmesi Rengi', 'form-submit-button-color' => 'Form Gönderim Düğmesi Rengi', 'form-title-color' => 'Form Başlık Rengi', 'general' => 'Genel', 'leads' => 'Fırsatlar', 'person' => 'Kişi', 'save-btn' => 'Web Formunu Kaydet', 'submit-button-label' => 'Gönderim Düğmesi Etiketi', 'submit-success-action' => 'Gönderim Başarı Eylemi', 'redirect-to-url' => 'URL\'ye Yönlendir', 'choose-value' => 'Değer Seç', 'select-file' => 'Dosya Seç', 'select-image' => 'Görüntü Seç', 'enter-value' => 'Değer Gir', ], 'edit' => [ 'add-attribute-btn' => 'Öznitelik Düğmesi Ekle', 'attribute-label-color' => 'Öznitelik Etiketi Rengi', 'attributes' => 'Öznitelikler', 'attributes-info' => 'Forma özel öznitelikler ekleyin.', 'background-color' => 'Arka Plan Rengi', 'choose-value' => 'Değer Seç', 'code-snippet' => 'Kod Parçası', 'copied' => 'Kopyalandı', 'copy' => 'Kopyala', 'create-lead' => 'Fırsat Oluştur', 'customize-webform' => 'Web Formunu Özelleştir', 'customize-webform-info' => 'Web formunuzu seçtiğiniz eleman renkleri ile özelleştirin.', 'description' => 'Açıklama', 'display-custom-message' => 'Özel mesaj göster', 'embed' => 'Göm', 'enter-value' => 'Değer Gir', 'form-background-color' => 'Form Arka Plan Rengi', 'form-submit-btn-color' => 'Form Gönderim Düğmesi Rengi', 'form-submit-button-color' => 'Form Gönderim Düğmesi Rengi', 'form-title-color' => 'Form Başlık Rengi', 'general' => 'Genel', 'leads' => 'Fırsatlar', 'person' => 'Kişi', 'preview' => 'Önizleme', 'public-url' => 'Genel URL', 'redirect-to-url' => 'URL\'ye Yönlendir', 'save-btn' => 'Web Formunu Kaydet', 'select-file' => 'Dosya Seç', 'select-image' => 'Görüntü Seç', 'submit-button-label' => 'Gönderim Düğmesi Etiketi', 'submit-success-action' => 'Gönderim Başarı Eylemi', 'title' => 'Web Formunu Düzenle', ], ], 'email-template' => [ 'index' => [ 'create-btn' => 'E-posta Şablonu Oluştur', 'title' => 'E-posta Şablonları', 'create-success' => 'E-posta şablonu başarıyla oluşturuldu.', 'update-success' => 'E-posta şablonu başarıyla güncellendi.', 'delete-success' => 'E-posta şablonu başarıyla silindi.', 'delete-failed' => 'E-posta şablonu silinemedi.', 'datagrid' => [ 'delete' => 'Sil', 'edit' => 'Düzenle', 'id' => 'ID', 'name' => 'Ad', 'subject' => 'Konu', ], ], 'create' => [ 'title' => 'E-posta Şablonu Oluştur', 'save-btn' => 'E-posta Şablonunu Kaydet', 'email-template' => 'E-posta Şablonu', 'subject' => 'Konu', 'content' => 'İçerik', 'subject-placeholders' => 'Konu Yer Tutucuları', 'general' => 'Genel', 'name' => 'Ad', ], 'edit' => [ 'title' => 'E-posta Şablonunu Düzenle', 'save-btn' => 'E-posta Şablonunu Kaydet', 'email-template' => 'E-posta Şablonu', 'subject' => 'Konu', 'content' => 'İçerik', 'subject-placeholders' => 'Konu Yer Tutucuları', 'general' => 'Genel', 'name' => 'Ad', ], ], 'marketing' => [ 'events' => [ 'index' => [ 'create-btn' => 'Etkinlik Oluştur', 'title' => 'Etkinlikler', 'create-success' => 'Etkinlik başarıyla oluşturuldu.', 'update-success' => 'Etkinlik başarıyla güncellendi.', 'delete-success' => 'Etkinlik başarıyla silindi.', 'delete-failed' => 'Etkinlik silinemedi.', 'mass-delete-success' => 'Etkinlikler başarıyla silindi', 'delete-failed-associated-campaigns' => 'Kampanya silinemiyor çünkü mevcut adaylarla ilişkili. Lütfen bu adayları silmeden önce bağlantılarını kaldırın veya güncelleyin.', 'datagrid' => [ 'delete' => 'Sil', 'edit' => 'Düzenle', 'id' => 'ID', 'name' => 'Ad', 'description' => 'Açıklama', 'date' => 'Tarih', ], 'create' => [ 'title' => 'Etkinlik Oluştur', 'name' => 'Ad', 'date' => 'Tarih', 'description' => 'Açıklama', 'save-btn' => 'Etkinliği Kaydet', ], 'edit' => [ 'title' => 'Etkinliği Düzenle', ], ], ], 'campaigns' => [ 'index' => [ 'create-btn' => 'Kampanya Oluştur', 'title' => 'Kampanyalar', 'create-success' => 'Kampanya başarıyla oluşturuldu.', 'update-success' => 'Kampanya başarıyla güncellendi.', 'delete-success' => 'Kampanya başarıyla silindi.', 'delete-failed' => 'Kampanya silinemedi.', 'mass-delete-success' => 'Kampanyalar başarıyla silindi', 'datagrid' => [ 'id' => 'ID', 'name' => 'Ad', 'subject' => 'Konu', 'status' => 'Durum', 'active' => 'Aktif', 'inactive' => 'Pasif', 'edit' => 'Düzenle', 'delete' => 'Sil', ], 'create' => [ 'title' => 'Kampanya Oluştur', 'name' => 'Ad', 'type' => 'Tür', 'subject' => 'Konu', 'event' => 'Etkinlik', 'email-template' => 'E-posta Şablonu', 'status' => 'Durum', ], 'edit' => [ 'title' => 'Kampanyayı Düzenle', ], ], ], ], 'tags' => [ 'index' => [ 'create-btn' => 'Etiket Oluştur', 'title' => 'Etiketler', 'create-success' => 'Etiket başarıyla oluşturuldu.', 'update-success' => 'Etiket başarıyla güncellendi.', 'delete-success' => 'Etiket başarıyla silindi.', 'delete-failed' => 'Etiket silinemedi.', 'datagrid' => [ 'delete' => 'Sil', 'edit' => 'Düzenle', 'id' => 'ID', 'name' => 'Ad', 'users' => 'Kullanıcılar', 'created-at' => 'Oluşturulma Tarihi', ], 'create' => [ 'name' => 'Ad', 'save-btn' => 'Etiketi Kaydet', 'title' => 'Etiket Oluştur', 'color' => 'Renk', ], 'edit' => [ 'title' => 'Etiketi Düzenle', ], ], ], 'users' => [ 'index' => [ 'create-btn' => 'Kullanıcı Oluştur', 'create-success' => 'Kullanıcı başarıyla oluşturuldu.', 'delete-failed' => 'Kullanıcı silinemedi.', 'delete-success' => 'Kullanıcı başarıyla silindi.', 'last-delete-error' => 'En az bir kullanıcı gereklidir.', 'mass-delete-failed' => 'Kullanıcılar silinemedi.', 'mass-delete-success' => 'Kullanıcılar başarıyla silindi.', 'mass-update-failed' => 'Kullanıcılar güncellenemedi.', 'mass-update-success' => 'Kullanıcılar başarıyla güncellendi.', 'title' => 'Kullanıcılar', 'update-success' => 'Kullanıcı başarıyla güncellendi.', 'user-define-error' => 'Sistem kullanıcısı silinemedi.', 'active' => 'Aktif', 'inactive' => 'Pasif', 'datagrid' => [ 'active' => 'Aktif', 'created-at' => 'Oluşturulma Tarihi', 'delete' => 'Sil', 'edit' => 'Düzenle', 'email' => 'E-posta', 'id' => 'ID', 'inactive' => 'Pasif', 'name' => 'Ad', 'status' => 'Durum', 'update-status' => 'Durumu Güncelle', 'users' => 'Kullanıcılar', ], 'create' => [ 'confirm-password' => 'Şifreyi Onayla', 'email' => 'E-posta', 'general' => 'Genel', 'global' => 'Küresel', 'group' => 'Grup', 'individual' => 'Bireysel', 'name' => 'Ad', 'password' => 'Şifre', 'permission' => 'İzin', 'role' => 'Rol', 'save-btn' => 'Kullanıcıyı Kaydet', 'status' => 'Durum', 'title' => 'Kullanıcı Oluştur', 'view-permission' => 'Görüntüleme İzni', 'select-at-lest-one-group' => 'Select at least one group', ], 'edit' => [ 'title' => 'Kullanıcıyı Düzenle', ], ], ], 'pipelines' => [ 'index' => [ 'title' => 'Pipelines', 'create-btn' => 'Pipeline Oluştur', 'create-success' => 'Pipeline başarıyla oluşturuldu.', 'update-success' => 'Pipeline başarıyla güncellendi.', 'default-required' => 'En az bir varsayılan boru hattı gereklidir.', 'delete-success' => 'Pipeline başarıyla silindi.', 'delete-failed' => 'Pipeline silinemedi.', 'default-delete-error' => 'Varsayılan pipeline silinemez.', 'datagrid' => [ 'delete' => 'Sil', 'edit' => 'Düzenle', 'id' => 'ID', 'is-default' => 'Varsayılan mı', 'name' => 'Ad', 'no' => 'Hayır', 'rotten-days' => 'Çürük Günler', 'yes' => 'Evet', ], ], 'create' => [ 'title' => 'Pipeline Oluştur', 'save-btn' => 'Pipeline Kaydet', 'name' => 'Ad', 'rotten-days' => 'Çürük Günler', 'mark-as-default' => 'Varsayılan Olarak İşaretle', 'general' => 'Genel', 'probability' => 'Olasılık (%)', 'new-stage' => 'Yeni', 'won-stage' => 'Kazandı', 'lost-stage' => 'Kaybetti', 'stage-btn' => 'Aşama Ekle', 'stages' => 'Aşamalar', 'duplicate-name' => '"Ad" alanı tekrar edemez', 'delete-stage' => 'Aşama Sil', 'add-new-stages' => 'Yeni Aşamalar Ekle', 'add-stage-info' => 'Pipeline için yeni aşama ekleyin', 'newly-added' => 'Yeni Eklenen', 'stage-delete-success' => 'Aşama Başarıyla Silindi', ], 'edit' => [ 'title' => 'Pipeline\'ı Düzenle', 'save-btn' => 'Pipeline Kaydet', 'name' => 'Ad', 'rotten-days' => 'Çürük Günler', 'mark-as-default' => 'Varsayılan Olarak İşaretle', 'general' => 'Genel', 'probability' => 'Olasılık (%)', 'new-stage' => 'Yeni', 'won-stage' => 'Kazandı', 'lost-stage' => 'Kaybetti', 'stage-btn' => 'Aşama Ekle', 'stages' => 'Aşamalar', 'duplicate-name' => '"Ad" alanı tekrar edemez', 'delete-stage' => 'Aşama Sil', 'add-new-stages' => 'Yeni Aşamalar Ekle', 'add-stage-info' => 'Pipeline için yeni aşama ekleyin', 'stage-delete-success' => 'Aşama Başarıyla Silindi', ], ], 'webhooks' => [ 'index' => [ 'title' => 'Webhooks', 'create-btn' => 'Webhook Oluştur', 'create-success' => 'Webhook başarıyla oluşturuldu.', 'update-success' => 'Webhook başarıyla güncellendi.', 'delete-success' => 'Webhook başarıyla silindi.', 'delete-failed' => 'Webhook silinemedi.', 'datagrid' => [ 'id' => 'ID', 'delete' => 'Sil', 'edit' => 'Düzenle', 'name' => 'Ad', 'entity-type' => 'Varlık Türü', 'end-point' => 'Son Nokta', ], ], 'create' => [ 'title' => 'Webhook Oluştur', 'save-btn' => 'Webhook Kaydet', 'info' => 'Webhook detaylarını girin', 'url-and-parameters' => 'URL ve Parametreler', 'method' => 'Yöntem', 'post' => 'Post', 'put' => 'Put', 'url-endpoint' => 'URL Son Noktası', 'parameters' => 'Parametreler', 'add-new-parameter' => 'Yeni Parametre Ekle', 'url-preview' => 'URL Önizleme:', 'headers' => 'Başlıklar', 'add-new-header' => 'Yeni Başlık Ekle', 'body' => 'Gövde', 'default' => 'Varsayılan', 'x-www-form-urlencoded' => 'x-www-form-urlencoded', 'key-and-value' => 'Anahtar ve Değer', 'add-new-payload' => 'Yeni yük ekle', 'raw' => 'Ham', 'general' => 'Genel', 'name' => 'Ad', 'entity-type' => 'Varlık Türü', 'insert-placeholder' => 'Yer Tutucu Ekle', 'description' => 'Açıklama', 'json' => 'Json', 'text' => 'Metin', ], 'edit' => [ 'title' => 'Webhook\'u Düzenle', 'edit-btn' => 'Webhook Kaydet', 'save-btn' => 'Webhook Kaydet', 'info' => 'Webhook detaylarını girin', 'url-and-parameters' => 'URL ve Parametreler', 'method' => 'Yöntem', 'post' => 'Post', 'put' => 'Put', 'url-endpoint' => 'URL Son Noktası', 'parameters' => 'Parametreler', 'add-new-parameter' => 'Yeni Parametre Ekle', 'url-preview' => 'URL Önizleme:', 'headers' => 'Başlıklar', 'add-new-header' => 'Yeni Başlık Ekle', 'body' => 'Gövde', 'default' => 'Varsayılan', 'x-www-form-urlencoded' => 'x-www-form-urlencoded', 'key-and-value' => 'Anahtar ve Değer', 'add-new-payload' => 'Yeni yük ekle', 'raw' => 'Ham', 'general' => 'Genel', 'name' => 'Ad', 'entity-type' => 'Varlık Türü', 'insert-placeholder' => 'Yer Tutucu Ekle', 'description' => 'Açıklama', 'json' => 'Json', 'text' => 'Metin', ], ], 'warehouses' => [ 'index' => [ 'title' => 'Depolar', 'create-btn' => 'Depo Oluştur', 'create-success' => 'Depo başarıyla oluşturuldu.', 'name-exists' => 'Depo adı zaten mevcut.', 'update-success' => 'Depo başarıyla güncellendi.', 'delete-success' => 'Depo başarıyla silindi.', 'delete-failed' => 'Depo silinemedi.', 'datagrid' => [ 'id' => 'ID', 'name' => 'Ad', 'contact-name' => 'İletişim Adı', 'delete' => 'Sil', 'edit' => 'Düzenle', 'view' => 'Görüntüle', 'created-at' => 'Oluşturulma Tarihi', 'products' => 'Ürünler', 'contact-emails' => 'İletişim E-postaları', 'contact-numbers' => 'İletişim Numaraları', ], ], 'create' => [ 'title' => 'Depo Oluştur', 'save-btn' => 'Depoyu Kaydet', 'contact-info' => 'İletişim Bilgileri', ], 'edit' => [ 'title' => 'Depoyu Düzenle', 'save-btn' => 'Depoyu Kaydet', 'contact-info' => 'İletişim Bilgileri', ], 'view' => [ 'all' => 'Tümü', 'notes' => 'Notlar', 'files' => 'Dosyalar', 'location' => 'Konum', 'change-logs' => 'Değişiklik Kayıtları', 'locations' => [ 'action' => 'Eylem', 'add-location' => 'Konum Ekle', 'create-success' => 'Konum başarıyla oluşturuldu.', 'delete' => 'Sil', 'delete-failed' => 'Konum silinemedi.', 'delete-success' => 'Konum başarıyla silindi.', 'name' => 'Ad', 'save-btn' => 'Kaydet', ], 'general-information' => [ 'title' => 'Genel Bilgiler', ], 'contact-information' => [ 'title' => 'İletişim Bilgileri', ], ], ], 'attributes' => [ 'index' => [ 'title' => 'Öznitelikler', 'create-btn' => 'Öznitelik Oluştur', 'create-success' => 'Öznitelik başarıyla oluşturuldu.', 'update-success' => 'Öznitelik başarıyla güncellendi.', 'delete-success' => 'Öznitelik başarıyla silindi.', 'delete-failed' => 'Öznitelik silinemedi.', 'user-define-error' => 'Sistem özniteliği silinemez.', 'mass-delete-failed' => 'Sistem öznitelikleri silinemez.', 'datagrid' => [ 'yes' => 'Evet', 'no' => 'Hayır', 'id' => 'ID', 'code' => 'Kod', 'name' => 'Ad', 'entity-type' => 'Varlık Türü', 'type' => 'Tür', 'is-default' => 'Varsayılan mı', 'edit' => 'Düzenle', 'delete' => 'Sil', 'entity-types' => [ 'leads' => 'Potansiyeller', 'organizations' => 'Organizasyonlar', 'persons' => 'Kişiler', 'products' => 'Ürünler', 'quotes' => 'Teklifler', 'warehouses' => 'Depolar', ], 'types' => [ 'text' => 'Metin', 'textarea' => 'Metin alanı', 'price' => 'Fiyat', 'boolean' => 'Mantıksal', 'select' => 'Seçim', 'multiselect' => 'Çoklu seçim', 'checkbox' => 'Onay kutusu', 'email' => 'E-posta', 'address' => 'Adres', 'phone' => 'Telefon', 'lookup' => 'Arama', 'datetime' => 'Tarih ve saat', 'date' => 'Tarih', 'image' => 'Görsel', 'file' => 'Dosya', ], ], ], 'create' => [ 'title' => 'Öznitelik Oluştur', 'save-btn' => 'Özniteliği Kaydet', 'code' => 'Kod', 'name' => 'Ad', 'entity-type' => 'Varlık Türü', 'type' => 'Tür', 'validations' => 'Doğrulamalar', 'is-required' => 'Gerekli mi', 'input-validation' => 'Girdi Doğrulaması', 'is-unique' => 'Benzersiz mi', 'labels' => 'Etiketler', 'general' => 'Genel', 'numeric' => 'Sayısal', 'decimal' => 'Ondalık', 'url' => 'URL', 'options' => 'Seçenekler', 'option-type' => 'Seçenek Türü', 'lookup-type' => 'Arama Türü', 'add-option' => 'Seçenek Ekle', 'save-option' => 'Seçeneği Kaydet', 'option-name' => 'Seçenek Adı', 'add-attribute-options' => 'Öznitelik Seçenekleri Ekle', 'text' => 'Metin', 'textarea' => 'Metin Alanı', 'price' => 'Fiyat', 'boolean' => 'Boolean', 'select' => 'Seç', 'multiselect' => 'Çoklu Seçim', 'email' => 'E-posta', 'address' => 'Adres', 'phone' => 'Telefon', 'datetime' => 'Tarih Saat', 'date' => 'Tarih', 'image' => 'Resim', 'file' => 'Dosya', 'lookup' => 'Arama', 'entity_type' => 'Varlık türü', 'checkbox' => 'Onay Kutusu', 'is_required' => 'Gerekli mi', 'is_unique' => 'Benzersiz mi', 'actions' => 'İşlemler', ], 'edit' => [ 'actions' => 'İşlemler', 'add-attribute-options' => 'Öznitelik Seçenekleri Ekle', 'add-option' => 'Seçenek Ekle', 'address' => 'Adres', 'boolean' => 'Boolean', 'checkbox' => 'Onay Kutusu', 'code' => 'Kod', 'date' => 'Tarih', 'datetime' => 'Tarih Saat', 'decimal' => 'Ondalık', 'email' => 'E-posta', 'entity-type' => 'Varlık Türü', 'entity_type' => 'Varlık türü', 'file' => 'Dosya', 'general' => 'Genel', 'image' => 'Resim', 'input-validation' => 'Girdi Doğrulaması', 'is-required' => 'Gerekli mi', 'is-unique' => 'Benzersiz mi', 'is_required' => 'Gerekli mi', 'is_unique' => 'Benzersiz mi', 'labels' => 'Etiketler', 'lookup' => 'Arama', 'lookup-type' => 'Arama Türü', 'multiselect' => 'Çoklu Seçim', 'name' => 'Ad', 'numeric' => 'Sayısal', 'option-deleted' => 'Attribute Option is deleted successfully', 'option-name' => 'Seçenek Adı', 'option-type' => 'Seçenek Türü', 'options' => 'Seçenekler', 'phone' => 'Telefon', 'price' => 'Fiyat', 'save-btn' => 'Özniteliği Kaydet', 'save-option' => 'Seçeneği Kaydet', 'select' => 'Seç', 'text' => 'Metin', 'textarea' => 'Metin Alanı', 'title' => 'Özniteliği Düzenle', 'type' => 'Tür', 'url' => 'URL', 'validations' => 'Doğrulamalar', ], ], 'data-transfer' => [ 'imports' => [ 'create' => [ 'action' => 'Eylem', 'allowed-errors' => 'İzin Verilen Hatalar', 'back-btn' => 'Geri', 'create-update' => 'Oluştur/Güncelle', 'delete' => 'Sil', 'download-sample' => 'Örneği İndir', 'field-separator' => 'Alan Ayırıcı', 'file' => 'Dosya', 'general' => 'Genel', 'images-directory' => 'Resim Dizini Yolu', 'process-in-queue' => 'Kuyrukta İşle', 'results' => 'Sonuçlar', 'save-btn' => 'İthalatı Kaydet', 'settings' => 'Ayarlar', 'skip-errors' => 'Hataları Atla', 'stop-on-errors' => 'Hatalarda Durdur', 'title' => 'İthalat Oluştur', 'type' => 'Tür', 'validation-strategy' => 'Doğrulama Stratejisi', ], 'edit' => [ 'action' => 'Eylem', 'allowed-errors' => 'İzin Verilen Hatalar', 'back-btn' => 'Geri', 'create-update' => 'Oluştur/Güncelle', 'delete' => 'Sil', 'download-sample' => 'Örneği İndir', 'field-separator' => 'Alan Ayırıcı', 'file' => 'Dosya', 'general' => 'Genel', 'images-directory' => 'Resim Dizini Yolu', 'process-in-queue' => 'Kuyrukta İşle', 'results' => 'Sonuçlar', 'save-btn' => 'İthalatı Kaydet', 'settings' => 'Ayarlar', 'skip-errors' => 'Hataları Atla', 'stop-on-errors' => 'Hatalarda Durdur', 'title' => 'İthalatı Düzenle', 'type' => 'Tür', 'validation-strategy' => 'Doğrulama Stratejisi', ], 'index' => [ 'button-title' => 'İthalat Oluştur', 'title' => 'İthalatlar', 'datagrid' => [ 'actions' => 'Eylemler', 'completed-at' => 'Tamamlandığı Zaman', 'created' => 'Oluşturuldu', 'delete' => 'Sil', 'deleted' => 'Silindi', 'edit' => 'Düzenle', 'error-file' => 'Hata Dosyası', 'id' => 'Kimlik', 'started-at' => 'Başlama Zamanı', 'state' => 'Durum', 'summary' => 'Özet', 'type' => 'Tür', 'updated' => 'Güncellendi', 'uploaded-file' => 'Yüklenen Dosya', ], ], 'import' => [ 'back-btn' => 'Geri', 'completed-batches' => 'Tamamlanan Toplam Gruplar:', 'download-error-report' => 'Tam Raporu İndir', 'edit-btn' => 'Düzenle', 'imported-info' => 'Tebrikler! İthalatınız başarılı oldu.', 'importing-info' => 'İthalat İşlemde', 'indexing-info' => 'Kaynaklar İndeksleniyor (Fiyat, Stok ve Elastic Search) İlerliyor', 'linking-info' => 'Kaynaklar Bağlanıyor', 'progress' => 'İlerleme:', 'title' => 'İthalat', 'total-batches' => 'Toplam Gruplar:', 'total-created' => 'Oluşturulan Toplam Kayıtlar:', 'total-deleted' => 'Silinen Toplam Kayıtlar:', 'total-errors' => 'Toplam Hatalar:', 'total-invalid-rows' => 'Geçersiz Satırların Toplamı:', 'total-rows-processed' => 'İşlenen Toplam Satırlar:', 'total-updated' => 'Güncellenen Toplam Kayıtlar:', 'validate' => 'Doğrula', 'validate-info' => 'İthalatınızı kontrol etmek için Verileri Doğrula\'ya tıklayın.', 'validating-info' => 'Veriler okunmaya ve doğrulanmaya başlandı', 'validation-failed-info' => 'İthalatınız geçersiz. Lütfen aşağıdaki hataları düzeltin ve tekrar deneyin.', 'validation-success-info' => 'İthalatınız geçerli. İthalat işlemini başlatmak için İthalat\'a tıklayın.', ], 'create-success' => 'İthalat başarıyla oluşturuldu.', 'delete-failed' => 'İthalatı silme beklenmedik bir şekilde başarısız oldu.', 'delete-success' => 'İthalat başarıyla silindi.', 'not-valid' => 'İthalat geçersiz', 'nothing-to-import' => 'İthal edilecek kaynak yok.', 'setup-queue-error' => 'İthalat işlemini başlatmak için kuyruk sürücünüzü "veritabanı" veya "redis" olarak değiştirin.', 'update-success' => 'İthalat başarıyla güncellendi.', ], ], ], 'activities' => [ 'index' => [ 'title' => 'Etkinlikler', 'datagrid' => [ 'comment' => 'Yorum', 'created_at' => 'Oluşturulma Tarihi', 'created_by' => 'Oluşturan', 'edit' => 'Düzenle', 'id' => 'ID', 'done' => 'Tamamlandı mı', 'not-done' => 'Tamamlanmadı', 'lead' => 'Müşteri', 'mass-delete' => 'Toplu Sil', 'mass-update' => 'Toplu Güncelle', 'schedule-from' => 'Başlangıç Tarihi', 'schedule-to' => 'Bitiş Tarihi', 'schedule_from' => 'Başlangıç Tarihi', 'schedule_to' => 'Bitiş Tarihi', 'title' => 'Başlık', 'is_done' => 'Tamamlandı mı', 'type' => 'Tür', 'update' => 'Güncelle', 'call' => 'Arama', 'meeting' => 'Toplantı', 'lunch' => 'Öğle Yemeği', ], ], 'edit' => [ 'title' => 'Etkinliği Düzenle', 'back-btn' => 'Geri', 'save-btn' => 'Etkinliği Kaydet', 'type' => 'Etkinlik Türü', 'call' => 'Arama', 'meeting' => 'Toplantı', 'lunch' => 'Öğle Yemeği', 'schedule_to' => 'Bitiş Tarihi', 'schedule_from' => 'Başlangıç Tarihi', 'location' => 'Konum', 'comment' => 'Yorum', 'lead' => 'Müşteri', 'participants' => 'Katılımcılar', 'general' => 'Genel', 'persons' => 'Kişiler', 'no-result-found' => 'Kayıt bulunamadı.', 'users' => 'Kullanıcılar', ], 'updated' => 'Güncellendi :attribute', 'created' => 'Oluşturuldu', 'duration-overlapping' => 'Katılımcıların bu saatte başka bir toplantısı var. Devam etmek istiyor musunuz?', 'create-success' => 'Etkinlik başarıyla oluşturuldu.', 'update-success' => 'Etkinlik başarıyla güncellendi.', 'overlapping-error' => 'Katılımcıların bu saatte başka bir toplantısı var.', 'destroy-success' => 'Etkinlik başarıyla silindi.', 'delete-failed' => 'Etkinlik silinemiyor.', 'mass-update-success' => 'Etkinlikler başarıyla güncellendi.', 'mass-destroy-success' => 'Etkinlikler başarıyla silindi.', 'mass-delete-failed' => 'Etkinlikler silinemiyor.', ], 'mail' => [ 'index' => [ 'compose' => 'Oluştur', 'draft' => 'Taslak', 'inbox' => 'Gelen Kutusu', 'outbox' => 'Giden Kutusu', 'sent' => 'Gönderildi', 'trash' => 'Çöp', 'compose-mail-btn' => 'Mail Oluştur', 'btn' => 'Mail', 'mail' => [ 'title' => 'Mail Oluştur', 'to' => 'Kime', 'enter-emails' => 'E-posta eklemek için enter tuşuna basın', 'cc' => 'CC', 'bcc' => 'BCC', 'subject' => 'Konu', 'send-btn' => 'Gönder', 'message' => 'Mesaj', 'draft' => 'Taslak', ], 'datagrid' => [ 'id' => 'ID', 'from' => 'Gönderen', 'to' => 'Alıcı', 'subject' => 'Konu', 'tags' => 'Etiketler', 'content' => 'Eklentiler', 'attachments' => 'Attachments', 'date' => 'Tarih', 'move-to-inbox' => 'Gelen Kutusuna Taşı', 'move-to-trash' => 'Çöp kutusuna taşındı', 'edit' => 'Düzenle', 'view' => 'Görüntüle', 'delete' => 'Sil', ], ], 'create-success' => 'E-posta başarıyla gönderildi.', 'update-success' => 'E-posta başarıyla güncellendi.', 'mass-update-success' => 'E-postalar başarıyla güncellendi.', 'delete-success' => 'E-posta başarıyla silindi.', 'delete-failed' => 'E-posta silinemedi.', 'invalid-route' => 'Geçersiz rota için mail.', 'unauthorized' => 'Bu işlem yetkilendirilmemiştir.', 'view' => [ 'title' => 'Mails', 'subject' => ':subject', 'link-mail' => 'Maili Bağla', 'to' => 'Kime', 'cc' => 'CC', 'bcc' => 'BCC', 'reply' => 'Yanıtla', 'reply-all' => 'Hepsine Yanıtla', 'forward' => 'Yönlendir', 'delete' => 'Sil', 'enter-mails' => 'E-posta ID girin', 'rotten-days' => ':days gün boyunca geçersiz', 'search-an-existing-lead' => 'Mevcut bir müşteri arayın', 'search-an-existing-contact' => 'Mevcut bir kişi arayın', 'message' => 'Mesaj', 'add-attachments' => 'Ek Ekle', 'discard' => 'İptal Et', 'send' => 'Gönder', 'no-result-found' => 'Sonuç bulunamadı', 'add-new-contact' => 'Yeni İletişim Ekle', 'description' => 'Açıklama', 'search' => 'Ara...', 'add-new-lead' => 'Yeni Müşteri Ekle', 'create-new-contact' => 'Yeni İletişim Oluştur', 'save-contact' => 'İletişimi Kaydet', 'create-lead' => 'Müşteri Oluştur', 'linked-contact' => 'Bağlı Kişi', 'link-to-contact' => 'Kişiye Bağla', 'link-to-lead' => 'Müşteriye Bağla', 'linked-lead' => 'Bağlı Müşteri', 'lead-details' => 'Müşteri Detayları', 'contact-person' => 'İletişim Kişisi', 'product' => 'Ürün', 'tags' => [ 'create-success' => 'Etiket başarıyla oluşturuldu.', 'destroy-success' => 'Etiket başarıyla silindi.', ], ], ], 'common' => [ 'custom-attributes' => [ 'add-more' => 'Daha Fazla Ekle', 'address' => 'Adres', 'city' => 'Şehir', 'contact' => 'İletişim Numaraları', 'country' => 'Ülke', 'email' => 'E-posta', 'home' => 'Ev', 'postcode' => 'Posta Kodu', 'save' => 'Kaydet', 'select' => 'Seç', 'select-country' => 'Ülke Seç', 'select-state' => 'Eyalet Seç', 'state' => 'Eyalet', 'update-contact-title' => 'İletişim Numaralarını Güncelle', 'update-emails-title' => 'İletişim E-postalarını Güncelle', 'work' => 'İş', ], ], 'leads' => [ 'create-success' => 'Lead başarıyla oluşturuldu.', 'update-success' => 'Lead başarıyla güncellendi.', 'update-failed' => 'Potansiyel müşteriler silinemez.', 'destroy-success' => 'Lead başarıyla silindi.', 'destroy-failed' => 'Lead silinemedi.', 'file' => [ 'data-not-found' => 'Veri bulunamadı.', 'empty-content' => 'PDF içeriği boş veya çıkarılamadı.', 'failed-extract' => 'Dosyadan metin çıkarılamadı.', 'insufficient-info' => 'Yetersiz veri nedeniyle, şu anda isteğinizi işleyemiyoruz.', 'invalid-base64' => 'Geçersiz base64 formatı.', 'invalid-format' => 'Geçersiz JSON formatı.', 'invalid-response' => 'Geçersiz AI yanıt formatı.', 'missing-api-key' => 'API anahtarı veya model yapılandırması eksik.', 'not-found' => 'Dosya bulunamadı.', 'recursive-call' => 'Özyinelemeli çağrı tespit edildi.', 'text-generation-failed' => 'Metin çıkarma başarısız oldu. Dosya boş veya okunamaz olabilir.', ], 'index' => [ 'title' => 'Leads', 'create-btn' => 'Lead Oluştur', 'datagrid' => [ 'id' => 'ID', 'sales-person' => 'Satış Temsilcisi', 'subject' => 'Konu', 'source' => 'Kaynak', 'lead-value' => 'Lead Değeri', 'lead-type' => 'Potansiyel Müşteri Türü', 'tag-name' => 'Etiket Adı', 'contact-person' => 'İletişim Kişisi', 'stage' => 'Aşama', 'rotten-lead' => 'Çürümüş Lead', 'date-to' => 'Bitiş Tarihi', 'created-at' => 'Oluşturulma Tarihi', 'no' => 'Hayır', 'yes' => 'Evet', 'delete' => 'Sil', 'mass-delete' => 'Toplu Sil', 'mass-update' => 'Toplu Güncelle', ], 'kanban' => [ 'rotten-days' => 'Bu müşteri adayı :days gündür çürük', 'empty-list' => 'Müşteri Adayı Listeniz Boş', 'empty-list-description' => 'Hedeflerinizi düzenlemek için bir müşteri adayı oluşturun.', 'create-lead-btn' => 'Müşteri Adayı Oluştur', 'columns' => [ 'contact-person' => 'İletişim Kişisi', 'id' => 'ID', 'lead-type' => 'Lead Türü', 'lead-value' => 'Lead Değeri', 'sales-person' => 'Satış Temsilcisi', 'source' => 'Kaynak', 'title' => 'Başlık', 'tags' => 'Etiketler', 'expected-close-date' => 'Beklenen Kapanış Tarihi', 'created-at' => 'Oluşturulma Tarihi', ], 'toolbar' => [ 'search' => [ 'title' => 'Başlığa göre ara', ], 'filters' => [ 'apply-filters' => 'Filtreleri Uygula', 'clear-all' => 'Tümünü Temizle', 'filter' => 'Filtre', 'filters' => 'Filtreler', 'from' => 'Kimden', 'select' => 'Seç', 'to' => 'Kime', ], ], ], 'view-switcher' => [ 'all-pipelines' => 'Tüm Boru Hatları', 'create-new-pipeline' => 'Yeni Boru Hattı Oluştur', ], 'upload' => [ 'create-lead' => 'AI Kullanarak Lead Oluştur', 'file' => 'Dosya yükleme', 'file-info' => 'Yalnızca pdf, bmp, jpg, jpeg, png formatındaki dosyalar kabul edilir.', 'file-required' => 'Devam etmek için lütfen en az bir geçerli dosya seçin.', 'save-btn' => 'Kaydet', 'upload-file' => 'Dosya yükle', ], ], 'create' => [ 'title' => 'Lead Oluştur', 'save-btn' => 'Kaydet', 'details' => 'Detaylar', 'details-info' => 'Lead\'in Temel Bilgilerini Girin', 'contact-person' => 'İletişim Kişisi', 'contact-info' => 'İletişim Kişisi Hakkında Bilgiler', 'products' => 'Ürünler', 'products-info' => 'Ürünler Hakkında Bilgiler', ], 'edit' => [ 'title' => 'Lead\'i Düzenle', 'save-btn' => 'Kaydet', 'details' => 'Detaylar', 'details-info' => 'Lead\'in Temel Bilgilerini Girin', 'contact-person' => 'İletişim Kişisi', 'contact-info' => 'İletişim Kişisi Hakkında Bilgiler', 'products' => 'Ürünler', 'products-info' => 'Ürünler Hakkında Bilgiler', ], 'common' => [ 'contact' => [ 'name' => 'Ad', 'email' => 'E-posta', 'contact-number' => 'İletişim Numarası', 'organization' => 'Kuruluş', ], 'products' => [ 'product-name' => 'Ürün Adı', 'quantity' => 'Miktar', 'price' => 'Fiyat', 'amount' => 'Tutar', 'action' => 'Eylem', 'add-more' => 'Daha Fazla Ekle', 'total' => 'Toplam', ], ], 'view' => [ 'title' => 'Lead: :title', 'rotten-days' => ':days Gün', 'tabs' => [ 'description' => 'Açıklama', 'products' => 'Ürünler', 'quotes' => 'Teklifler', ], 'attributes' => [ 'title' => 'Lead Hakkında', ], 'quotes' => [ 'subject' => 'Konu', 'expired-at' => 'Son Tarih', 'sub-total' => 'Ara Toplam', 'discount' => 'İndirim', 'tax' => 'Vergi', 'adjustment' => 'Düzeltme', 'grand-total' => 'Genel Toplam', 'delete' => 'Sil', 'edit' => 'Düzenle', 'download' => 'İndir', 'destroy-success' => 'Teklif başarıyla silindi.', 'empty-title' => 'Teklif Bulunamadı', 'empty-info' => 'Bu Lead için Teklif Bulunamadı', 'add-btn' => 'Teklif Ekle', ], 'products' => [ 'product-name' => 'Ürün Adı', 'quantity' => 'Miktar', 'price' => 'Fiyat', 'amount' => 'Tutar', 'action' => 'Eylem', 'add-more' => 'Daha Fazla Ekle', 'total' => 'Toplam', 'empty-title' => 'Ürün Bulunamadı', 'empty-info' => 'Bu Lead için Ürün Bulunamadı', 'add-product' => 'Ürün Ekle', ], 'persons' => [ 'title' => 'Kişiler Hakkında', 'job-title' => ':job_title @ :organization', ], 'stages' => [ 'won' => 'Kazandı', 'lost' => 'Kayıp', 'need-more-info' => 'Daha Fazla Bilgi Gerekiyor', 'closed-at' => 'Kapanış Tarihi', 'won-value' => 'Kazanan Değer', 'lost-reason' => 'Kayıp Nedeni', 'save-btn' => 'Kaydet', ], 'tags' => [ 'create-success' => 'Etiket başarıyla oluşturuldu.', 'destroy-success' => 'Etiket başarıyla silindi.', ], ], ], 'configuration' => [ 'index' => [ 'back' => 'Geri', 'delete' => 'Sil', 'save-btn' => 'Yapılandırmayı Kaydet', 'save-success' => 'Yapılandırma Başarıyla Kaydedildi.', 'search' => 'Ara', 'select-country' => 'Ülke Seç', 'select-state' => 'Eyalet Seç', 'title' => 'Yapılandırma', 'general' => [ 'title' => 'Genel', 'info' => 'Genel Yapılandırma', 'general' => [ 'title' => 'Genel', 'info' => 'Genel ayarlarınızı burada güncelleyin.', 'locale-settings' => [ 'title' => 'Yerel Ayarlar', 'title-info' => 'Kullanıcı arayüzünde kullanılan dili tanımlar, örneğin Arapça (ar), İngilizce (en), İspanyolca (es), Farsça (fa) ve Türkçe (tr).', ], 'admin-logo' => [ 'logo-image' => 'Logo Resmi', 'title' => 'Yönetici Logosu', 'title-info' => 'Yönetici paneliniz için logo resmini yapılandırın.', ], ], 'settings' => [ 'title' => 'Ayarlar', 'info' => 'Ayarlarınızı burada güncelleyin.', 'footer' => [ 'info' => 'Powered by bölümünü burada yapılandırabiliriz.', 'powered-by' => 'Powered by metin düzenleyici', 'title' => 'Powered by Bölüm Yapılandırmaları', ], 'menu' => [ 'activities' => 'Aktiviteler', 'configuration' => 'Yapılandırma', 'contacts' => 'İletişim', 'dashboard' => 'Gösterge Paneli', 'draft' => 'Taslak', 'inbox' => 'Gelen Kutusu', 'info' => 'Menü öğelerinin adlarını burada yapılandırabiliriz.', 'leads' => 'Leadler', 'mail' => 'Mail', 'organizations' => 'Organizasyonlar', 'outbox' => 'Gönderilenler', 'persons' => 'Kişiler', 'products' => 'Ürünler', 'quotes' => 'Teklifler', 'sent' => 'Gönderildi', 'settings' => 'Ayarlar', 'title' => 'Menü Öğesi Yapılandırmaları', 'trash' => 'Çöp Kutusu', ], 'menu-color' => [ 'brand-color' => 'Brand Color', 'info' => 'Menü öğelerinin renklerini burada değiştirebiliriz.', 'title' => 'Menü Öğesi Renk Yapılandırmaları', ], ], ], 'email' => [ 'title' => 'Email Settings', 'info' => 'Email configuration for the application.', 'imap' => [ 'title' => 'IMAP Settings', 'info' => 'IMAP email configuration for receiving emails.', 'account' => [ 'title' => 'IMAP Account', 'title-info' => 'Configure your IMAP account settings here.', 'host' => 'Host', 'port' => 'Port', 'encryption' => 'Encryption Type', 'validate-cert' => 'Validate SSL Certificate', 'username' => 'IMAP Username', 'password' => 'IMAP Password', ], ], ], 'magic-ai' => [ 'title' => 'Sihirli AI', 'info' => 'Uygulama için Sihirli AI yapılandırması.', 'settings' => [ 'api-key' => 'API Anahtarı', 'api-key-info' => 'Her model için bir OpenRouter API anahtarı kullanmayı unutmayın. Bu, güvenliği ve performansı artırmak için basit bir adımdır.', 'enable' => 'Etkinleştir', 'info' => 'OpenRouter API Anahtarınız ile Magic AI deneyiminizi geliştirin. Şimdi entegre edin ve size özel, sorunsuz bir AI macerası yaşayın! Ayarları kolayca özelleştirin ve AI yolculuğunuzun kontrolünü elinize alın.', 'other' => 'Diğer Model', 'other-model' => 'Diğer modeller için OpenRouter\'dan Model ID kullanın.', 'doc-generation' => 'DOC Oluşturma', 'doc-generation-info' => 'DOC dosyalarından verileri otomatik olarak çıkartıp metin formatına dönüştürmek için DOC Oluşturma özelliğini etkinleştirin. Bu özelliği etkinleştirerek iş akışınızı kolaylaştırın ve verimliliğinizi artırın.', 'title' => 'Genel Ayarlar', 'models' => [ 'deepseek-r1' => 'Deepseek R1 Distill-llama-8b', 'gemini-2-0-flash-001' => 'Gemini 2.0 flash-001', 'gpt-4o' => 'GPT-4.0', 'gpt-4o-mini' => 'GPT-4.0 mini', 'grok-2-1212' => 'Grok 2.12', 'llama-3-2-3b-instruct' => 'Llama 3.2 3b Instruct', 'title' => 'Modeller', ], ], ], ], ], 'dashboard' => [ 'index' => [ 'title' => 'Gösterge Paneli', 'start-date' => 'Start Date', 'end-date' => 'End Date', 'revenue' => [ 'lost-revenue' => 'Kayıp Gelir', 'won-revenue' => 'Kazançlı Gelir', ], 'over-all' => [ 'average-lead-value' => 'Ortalama Lead Değeri', 'total-leads' => 'Toplam Lead', 'average-leads-per-day' => 'Günlük Ortalama Lead', 'total-quotations' => 'Toplam Teklif', 'total-persons' => 'Toplam Kişi', 'total-organizations' => 'Toplam Organizasyon', ], 'total-leads' => [ 'title' => 'Leadler', 'total' => 'Toplam Lead', 'won' => 'Kazanan Leadler', 'lost' => 'Kayıp Leadler', ], 'revenue-by-sources' => [ 'title' => 'Kaynaklara Göre Gelir', 'empty-title' => 'Veri Bulunamadı', 'empty-info' => 'Seçilen aralık için veri bulunamadı', ], 'revenue-by-types' => [ 'title' => 'Türlere Göre Gelir', 'empty-title' => 'Veri Bulunamadı', 'empty-info' => 'Seçilen aralık için veri bulunamadı', ], 'top-selling-products' => [ 'title' => 'En Çok Satılan Ürünler', 'empty-title' => 'Ürün Bulunamadı', 'empty-info' => 'Seçilen aralık için ürün bulunamadı', ], 'top-persons' => [ 'title' => 'En İyi Kişiler', 'empty-title' => 'Kişi Bulunamadı', 'empty-info' => 'Seçilen aralık için kişi bulunamadı', ], 'open-leads-by-states' => [ 'title' => 'Aşamalara Göre Açık Leadler', 'empty-title' => 'Veri Bulunamadı', 'empty-info' => 'Seçilen aralık için veri bulunamadı', ], ], ], 'layouts' => [ 'app-version' => 'Sürüm: :version', 'dashboard' => 'Gösterge Paneli', 'leads' => 'Leadler', 'quotes' => 'Teklifler', 'quote' => 'Teklif', 'mail' => [ 'title' => 'Mail', 'compose' => 'Yeni Mesaj', 'inbox' => 'Gelen Kutusu', 'draft' => 'Taslak', 'outbox' => 'Gönderilenler', 'sent' => 'Gönderildi', 'trash' => 'Çöp Kutusu', 'setting' => 'Ayar', ], 'activities' => 'Aktiviteler', 'contacts' => 'İletişim', 'persons' => 'Kişiler', 'person' => 'Kişi', 'organizations' => 'Organizasyonlar', 'organization' => 'Organizasyon', 'products' => 'Ürünler', 'product' => 'Ürün', 'settings' => 'Ayarlar', 'user' => 'Kullanıcı', 'user-info' => 'CRM’de tüm kullanıcılarınızı ve yetkilerini yönetin, ne yapmalarına izin verildiğini belirleyin.', 'groups' => 'Gruplar', 'groups-info' => 'CRM’den grupları ekleyin, düzenleyin veya silin', 'roles' => 'Roller', 'role' => 'Rol', 'roles-info' => 'CRM’den rolleri ekleyin, düzenleyin veya silin', 'users' => 'Kullanıcılar', 'users-info' => 'CRM’den kullanıcıları ekleyin, düzenleyin veya silin', 'lead' => 'Lead', 'lead-info' => 'CRM’de tüm lead ayarlarınızı yönetin', 'pipelines' => 'Pipeline’lar', 'pipelines-info' => 'CRM’den pipeline’ları ekleyin, düzenleyin veya silin', 'sources' => 'Kaynaklar', 'sources-info' => 'CRM’den kaynakları ekleyin, düzenleyin veya silin', 'types' => 'Türler', 'types-info' => 'CRM’den türleri ekleyin, düzenleyin veya silin', 'automation' => 'Otomasyon', 'automation-info' => 'CRM’de tüm otomasyon ayarlarınızı yönetin', 'attributes' => 'Nitelikler', 'attribute' => 'Nitelik', 'attributes-info' => 'CRM’den nitelikleri ekleyin, düzenleyin veya silin', 'email-templates' => 'E-posta Şablonları', 'email' => 'E-posta', 'email-templates-info' => 'CRM’den e-posta şablonları ekleyin, düzenleyin veya silin', 'events' => 'Etkinlikler', 'events-info' => 'CRM üzerinden etkinlikleri ekleyin, düzenleyin veya silin', 'campaigns' => 'Kampanyalar', 'campaigns-info' => 'CRM üzerinden kampanyaları ekleyin, düzenleyin veya silin', 'workflows' => 'İş Akışları', 'workflows-info' => 'CRM’den iş akışlarını ekleyin, düzenleyin veya silin', 'webhooks' => 'Webhooklar', 'webhooks-info' => 'CRM’den webhookları ekleyin, düzenleyin veya silin', 'other-settings' => 'Diğer Ayarlar', 'other-settings-info' => 'CRM’de tüm ekstra ayarlarınızı yönetin', 'tags' => 'Etiketler', 'tags-info' => 'CRM’den etiketleri ekleyin, düzenleyin veya silin', 'my-account' => 'Hesabım', 'sign-out' => 'Çıkış Yap', 'back' => 'Geri', 'name' => 'Ad', 'configuration' => 'Yapılandırma', 'howdy' => 'Merhaba!', 'warehouses' => 'Depolar', 'warehouse' => 'Depo', 'warehouses-info' => 'CRM’den depoları ekleyin, düzenleyin veya silin', 'data_transfer' => 'Veri Transferi', 'data_transfer_info' => 'CRM’de kişiler, ürünler ve potansiyel müşterilere ilişkin veri transferi ayarlarını yönetin', 'inventory' => 'Envanter', 'inventory-info' => 'CRM’deki tüm envanter ayarlarını yönetin', ], 'user' => [ 'account' => [ 'name' => 'İsim', 'email' => 'E-posta', 'password' => 'Şifre', 'my_account' => 'Hesabım', 'update_details' => 'Bilgileri Güncelle', 'current_password' => 'Mevcut şifre', 'confirm_password' => 'Şifreyi onayla', 'password-match' => 'Mevcut şifre eşleşmiyor.', 'account-save' => 'Hesap değişiklikleri başarıyla kaydedildi.', 'permission-denied' => 'İzin Reddedildi', 'remove-image' => 'Görseli Kaldır', 'upload_image_pix' => 'Profil Görseli Yükle (100px x 100px)', 'upload_image_format' => 'PNG veya JPG Formatında', 'image_upload_message' => 'Sadece görseller (.jpeg, .jpg, .png, ..) izinlidir.', ], ], 'emails' => [ 'common' => [ 'dear' => 'Sevgili :name', 'cheers' => 'Saygılar,
:app_name Ekibi', 'user' => [ 'dear' => 'Sevgili :username', 'create-subject' => 'Bir üye olarak eklendiniz.', 'create-body' => 'Tebrikler! Artık ekibimizin bir üyesisiniz.', 'forget-password' => [ 'subject' => 'Müşteri Şifre Sıfırlama', 'dear' => 'Sevgili :username', 'reset-password' => 'Şifreyi Sıfırla', 'info' => 'Bu e-postayı almanız, hesabınız için bir şifre sıfırlama talebi aldığımız anlamına gelir', 'final-summary' => 'Eğer şifre sıfırlama talebinde bulunmadıysanız, herhangi bir ek işlem yapmanıza gerek yoktur', 'thanks' => 'Teşekkürler!', ], ], ], ], 'validations' => [ 'message' => [ 'decimal' => ':attribute ondalıklı bir sayı olmalıdır.', ], ], 'errors' => [ 'dashboard' => 'Kontrol Paneli', 'go-back' => 'Geri Dön', 'support' => 'Sorun devam ederse, yardım için bize :email adresinden ulaşın.', '404' => [ 'description' => 'Oops! Aradığınız sayfa tatilde. Aradığınız şeyi bulamadık gibi görünüyor.', 'title' => '404 Sayfa Bulunamadı', ], '401' => [ 'description' => 'Oops! Bu sayfaya erişim izniniz yok gibi görünüyor. Gerekli yetkilere sahip değilsiniz.', 'title' => '401 Yetkisiz', ], '403' => [ 'description' => 'Oops! Bu sayfa erişime kapalı. Bu içeriği görüntülemek için gerekli izinlere sahip değilsiniz gibi görünüyor.', 'title' => '403 Yasak', ], '500' => [ 'description' => 'Oops! Bir şeyler ters gitti. Aradığınız sayfa yüklenirken sorun yaşıyoruz gibi görünüyor.', 'title' => '500 Sunucu Hatası', ], '503' => [ 'description' => 'Oops! Görünüşe göre geçici bir bakım nedeniyle kapalıyız. Lütfen kısa süre sonra tekrar kontrol edin.', 'title' => '503 Hizmet Kullanılamıyor', ], ], 'export' => [ 'csv' => 'CSV', 'download' => 'İndir', 'export' => 'Dışa Aktar', 'no-records' => 'Dışa aktarılacak kayıt bulunamadı.', 'xls' => 'XLS', 'xlsx' => 'XLSX', ], ]; ================================================ FILE: packages/Webkul/Admin/src/Resources/lang/vi/app.php ================================================ [ 'leads' => 'Những Khách hàng tiềm năng', 'lead' => 'Khách hàng tiềm năng', 'quotes' => 'Báo giá', 'mail' => 'Thư', 'inbox' => 'Hộp thư đến', 'draft' => 'Thư nháp', 'outbox' => 'Hộp thư đi', 'sent' => 'Đã gửi', 'trash' => 'Thùng rác', 'activities' => 'Hoạt động', 'webhook' => 'Webhook', 'contacts' => 'Danh bạ', 'persons' => 'Cá nhân', 'organizations' => 'Tổ chức', 'products' => 'Sản phẩm', 'settings' => 'Cài đặt', 'groups' => 'Nhóm', 'roles' => 'Vai trò', 'users' => 'Người dùng', 'user' => 'Người dùng', 'automation' => 'Tự động hóa', 'attributes' => 'Thuộc tính', 'pipelines' => 'Quy trình', 'sources' => 'Nguồn', 'types' => 'Loại', 'email-templates' => 'Mẫu email', 'workflows' => 'Quy trình làm việc', 'other-settings' => 'Cài đặt khác', 'tags' => 'Thẻ', 'configuration' => 'Cấu hình', 'create' => 'Tạo mới', 'edit' => 'Chỉnh sửa', 'view' => 'Xem', 'print' => 'In', 'delete' => 'Xóa', 'export' => 'Xuất khẩu', 'mass-delete' => 'Xóa hàng loạt', 'data-transfer' => 'Data Transfer', 'imports' => 'Imports', 'import' => 'Import', 'event' => 'Sự kiện', 'campaigns' => 'Chiến dịch', 'warehouses' => 'Kho', 'inventory' => 'Hàng tồn kho', ], 'users' => [ 'activate-warning' => 'Tài khoản của bạn chưa được kích hoạt. Vui lòng liên hệ quản trị viên.', 'login-error' => 'Thông tin đăng nhập không khớp với hồ sơ của chúng tôi.', 'not-permission' => 'Bạn không có quyền truy cập vào bảng quản trị.', 'login' => [ 'email' => 'Địa chỉ Email', 'forget-password-link' => 'Quên Mật khẩu?', 'password' => 'Mật khẩu', 'submit-btn' => 'Đăng Nhập', 'title' => 'Đăng Nhập', ], 'forget-password' => [ 'create' => [ 'email' => 'Email Đã Đăng Ký', 'email-not-exist' => 'Email Không Tồn Tại', 'page-title' => 'Quên Mật khẩu', 'reset-link-sent' => 'Liên kết đặt lại mật khẩu đã được gửi', 'sign-in-link' => 'Quay lại Đăng Nhập?', 'submit-btn' => 'Đặt Lại', 'title' => 'Khôi Phục Mật khẩu', ], ], 'reset-password' => [ 'back-link-title' => 'Quay lại Đăng Nhập?', 'confirm-password' => 'Xác Nhận Mật Khẩu', 'email' => 'Email Đã Đăng Ký', 'password' => 'Mật Khẩu', 'submit-btn' => 'Đặt Lại Mật Khẩu', 'title' => 'Đặt Lại Mật Khẩu', ], ], 'account' => [ 'edit' => [ 'back-btn' => 'Quay Lại', 'change-password' => 'Đổi Mật Khẩu', 'confirm-password' => 'Xác Nhận Mật Khẩu', 'current-password' => 'Mật Khẩu Hiện Tại', 'email' => 'Email', 'general' => 'Chung', 'invalid-password' => 'Mật khẩu hiện tại bạn nhập không đúng.', 'name' => 'Tên', 'password' => 'Mật Khẩu', 'profile-image' => 'Ảnh Hồ Sơ', 'save-btn' => 'Lưu Tài Khoản', 'title' => 'Tài Khoản Của Tôi', 'update-success' => 'Tài khoản đã được cập nhật thành công', 'upload-image-info' => 'Tải lên Ảnh Hồ Sơ (110px X 110px) ở định dạng PNG hoặc JPG', ], ], 'components' => [ 'activities' => [ 'actions' => [ 'mail' => [ 'btn' => 'Thư', 'title' => 'Soạn thư', 'to' => 'Tới', 'enter-emails' => 'Nhấn enter để thêm email', 'cc' => 'CC', 'bcc' => 'BCC', 'subject' => 'Chủ đề', 'send-btn' => 'Gửi', 'message' => 'Tin nhắn', ], 'file' => [ 'btn' => 'Tệp', 'title' => 'Thêm tệp', 'title-control' => 'Tiêu đề', 'name' => 'Tên', 'description' => 'Mô tả', 'file' => 'Tệp', 'save-btn' => 'Lưu tệp', ], 'note' => [ 'btn' => 'Ghi chú', 'title' => 'Thêm ghi chú', 'comment' => 'Bình luận', 'save-btn' => 'Lưu ghi chú', ], 'activity' => [ 'btn' => 'Hoạt động', 'title' => 'Thêm hoạt động', 'title-control' => 'Tiêu đề', 'description' => 'Mô tả', 'schedule-from' => 'Lịch từ', 'schedule-to' => 'Lịch đến', 'location' => 'Địa điểm', 'call' => 'Cuộc gọi', 'meeting' => 'Cuộc họp', 'lunch' => 'Bữa trưa', 'save-btn' => 'Lưu hoạt động', 'participants' => [ 'title' => 'Người tham gia', 'placeholder' => 'Nhập để tìm kiếm người tham gia', 'users' => 'Người dùng', 'persons' => 'Người', 'no-results' => 'Không có kết quả...', ], ], ], 'index' => [ 'all' => 'Tất cả', 'bcc' => 'Bcc', 'by-user' => 'Bởi :user', 'calls' => 'Cuộc gọi', 'cc' => 'Cc', 'change-log' => 'Nhật ký thay đổi', 'delete' => 'Xóa', 'edit' => 'Chỉnh sửa', 'emails' => 'Email', 'empty' => 'Trống', 'files' => 'Tệp tin', 'from' => 'Từ', 'location' => 'Địa điểm', 'lunches' => 'Bữa trưa', 'mark-as-done' => 'Đánh dấu hoàn thành', 'meetings' => 'Cuộc họp', 'notes' => 'Ghi chú', 'participants' => 'Người tham gia', 'planned' => 'Đã lên kế hoạch', 'quotes' => 'Báo giá', 'scheduled-on' => 'Lên lịch vào', 'system' => 'Hệ thống', 'to' => 'Đến', 'unlink' => 'Gỡ liên kết', 'view' => 'Xem', 'empty-placeholders' => [ 'all' => [ 'title' => 'Không tìm thấy hoạt động nào', 'description' => 'Không có hoạt động nào được tìm thấy. Bạn có thể thêm hoạt động bằng cách nhấn nút Hoạt động ở bảng bên trái.', ], 'planned' => [ 'title' => 'Không tìm thấy hoạt động đã lên kế hoạch', 'description' => 'Không có hoạt động đã lên kế hoạch nào được tìm thấy. Thêm hoạt động bằng cách nhấn nút Hoạt động ở bảng bên trái.', ], 'notes' => [ 'title' => 'Không tìm thấy ghi chú', 'description' => 'Không có ghi chú nào được tìm thấy. Thêm ghi chú bằng cách nhấn nút Ghi chú ở bảng bên trái.', ], 'calls' => [ 'title' => 'Không tìm thấy cuộc gọi', 'description' => 'Không có cuộc gọi nào được tìm thấy. Thêm cuộc gọi bằng cách nhấn nút Hoạt động và chọn loại Cuộc gọi.', ], 'meetings' => [ 'title' => 'Không tìm thấy cuộc họp', 'description' => 'Không có cuộc họp nào được tìm thấy. Thêm cuộc họp bằng cách nhấn nút Hoạt động và chọn loại Cuộc họp.', ], 'lunches' => [ 'title' => 'Không tìm thấy buổi ăn trưa', 'description' => 'Không có buổi ăn trưa nào được tìm thấy. Thêm ăn trưa bằng cách nhấn nút Hoạt động và chọn loại Ăn trưa.', ], 'files' => [ 'title' => 'Không tìm thấy tệp', 'description' => 'Không có tệp nào được tìm thấy. Thêm tệp bằng cách nhấn nút Tệp ở bảng bên trái.', ], 'emails' => [ 'title' => 'Không tìm thấy email', 'description' => 'Không có email nào được tìm thấy. Thêm email bằng cách nhấn nút Thư ở bảng bên trái.', ], 'system' => [ 'title' => 'Không tìm thấy nhật ký thay đổi', 'description' => 'Không có nhật ký thay đổi nào được tìm thấy.', ], ], ], ], 'media' => [ 'images' => [ 'add-image-btn' => 'Thêm hình ảnh', 'ai-add-image-btn' => 'Magic AI', 'allowed-types' => 'png, jpeg, jpg', 'not-allowed-error' => 'Chỉ chấp nhận tệp hình ảnh (.jpeg, .jpg, .png, ..).', 'placeholders' => [ 'front' => 'Mặt trước', 'next' => 'Kế tiếp', 'size' => 'Kích thước', 'use-cases' => 'Trường hợp sử dụng', 'zoom' => 'Thu phóng', ], ], 'videos' => [ 'add-video-btn' => 'Thêm video', 'allowed-types' => 'mp4, webm, mkv', 'not-allowed-error' => 'Chỉ chấp nhận tệp video (.mp4, .mov, .ogg ..).', ], ], 'datagrid' => [ 'index' => [ 'no-records-selected' => 'Chưa có bản ghi nào được chọn.', 'must-select-a-mass-action-option' => 'Bạn phải chọn một tùy chọn hành động hàng loạt.', 'must-select-a-mass-action' => 'Bạn phải chọn một hành động hàng loạt.', ], 'toolbar' => [ 'length-of' => ':length của', 'of' => 'của', 'per-page' => 'Mỗi Trang', 'results' => ':total Kết quả', 'delete' => 'Xóa', 'selected' => ':total Mục đã chọn', 'mass-actions' => [ 'submit' => 'Gửi', 'select-option' => 'Chọn Tùy chọn', 'select-action' => 'Chọn Hành động', ], 'filter' => [ 'apply-filters-btn' => 'Áp dụng Bộ lọc', 'back-btn' => 'Quay lại', 'create-new-filter' => 'Tạo Bộ lọc Mới', 'custom-filters' => 'Bộ lọc Tùy chỉnh', 'delete-error' => 'Đã xảy ra lỗi khi xóa bộ lọc, vui lòng thử lại.', 'delete-success' => 'Bộ lọc đã được xóa thành công.', 'empty-description' => 'Không có bộ lọc nào được chọn để lưu. Vui lòng chọn bộ lọc để lưu.', 'empty-title' => 'Thêm Bộ lọc để Lưu', 'name' => 'Tên', 'quick-filters' => 'Bộ lọc Nhanh', 'save-btn' => 'Lưu', 'save-filter' => 'Lưu Bộ lọc', 'saved-success' => 'Bộ lọc đã được lưu thành công.', 'selected-filters' => 'Bộ lọc đã chọn', 'title' => 'Bộ lọc', 'update' => 'Cập nhật', 'update-filter' => 'Cập nhật Bộ lọc', 'updated-success' => 'Bộ lọc đã được cập nhật thành công.', ], 'search' => [ 'title' => 'Tìm kiếm', ], ], 'filters' => [ 'select' => 'Chọn', 'title' => 'Bộ lọc', 'dropdown' => [ 'searchable' => [ 'at-least-two-chars' => 'Nhập ít nhất 2 ký tự...', 'no-results' => 'Không tìm thấy kết quả...', ], ], 'custom-filters' => [ 'clear-all' => 'Xóa tất cả', 'title' => 'Bộ lọc Tùy chỉnh', ], 'boolean-options' => [ 'false' => 'Sai', 'true' => 'Đúng', ], 'date-options' => [ 'last-month' => 'Tháng trước', 'last-six-months' => '6 Tháng trước', 'last-three-months' => '3 Tháng trước', 'this-month' => 'Tháng này', 'this-week' => 'Tuần này', 'this-year' => 'Năm nay', 'today' => 'Hôm nay', 'yesterday' => 'Hôm qua', ], ], 'table' => [ 'actions' => 'Hành động', 'no-records-available' => 'Không có Bản ghi nào.', ], ], 'modal' => [ 'confirm' => [ 'agree-btn' => 'Đồng ý', 'disagree-btn' => 'Không đồng ý', 'message' => 'Bạn có chắc chắn muốn thực hiện hành động này không?', 'title' => 'Bạn có chắc chắn?', ], ], 'tags' => [ 'index' => [ 'title' => 'Thẻ', 'added-tags' => 'Thẻ đã thêm', 'save-btn' => 'Lưu Thẻ', 'placeholder' => 'Nhập để tìm thẻ', 'add-tag' => 'Thêm ":term"...', 'aquarelle-red' => 'Đỏ Aquarelle', 'crushed-cashew' => 'Hạt điều nghiền', 'beeswax' => 'Sáp ong', 'lemon-chiffon' => 'Vàng Chanh', 'snow-flurry' => 'Tuyết Bay', 'honeydew' => 'Mật Ong', ], ], 'layouts' => [ 'powered-by' => [ 'description' => 'Được hỗ trợ bởi :krayin, một dự án mã nguồn mở được phát triển bởi :webkul.', ], 'header' => [ 'mega-search' => [ 'title' => 'Tìm kiếm Mega', 'tabs' => [ 'leads' => 'Khách hàng tiềm năng', 'quotes' => 'Báo giá', 'persons' => 'Người', 'products' => 'Sản phẩm', ], 'explore-all-products' => 'Khám phá tất cả Sản phẩm', 'explore-all-leads' => 'Khám phá tất cả Khách hàng tiềm năng', 'explore-all-contacts' => 'Khám phá tất cả Liên hệ', 'explore-all-quotes' => 'Khám phá tất cả Báo giá', 'explore-all-matching-products' => 'Khám phá tất cả sản phẩm khớp với ":query" (:count)', 'explore-all-matching-leads' => 'Khám phá tất cả khách hàng tiềm năng khớp với ":query" (:count)', 'explore-all-matching-contacts' => 'Khám phá tất cả liên hệ khớp với ":query" (:count)', 'explore-all-matching-quotes' => 'Khám phá tất cả báo giá khớp với ":query" (:count)', ], ], ], 'attributes' => [ 'edit' => [ 'delete' => 'Xóa', ], 'lookup' => [ 'click-to-add' => 'Nhấn để thêm', 'search' => 'Tìm kiếm...', 'no-result-found' => 'Không tìm thấy kết quả', ], ], 'lookup' => [ 'click-to-add' => 'Nhấn để Thêm', 'no-results' => 'Không tìm thấy kết quả', 'add-as-new' => 'Thêm như mới', 'search' => 'Tìm kiếm...', ], 'flash-group' => [ 'success' => 'Thành công', 'error' => 'Lỗi', 'warning' => 'Cảnh báo', 'info' => 'Thông tin', ], 'tiny-mce' => [ 'http-error' => 'Lỗi HTTP', 'invalid-json' => 'Phản hồi JSON không hợp lệ từ máy chủ.', 'upload-failed' => 'Tải tệp lên không thành công. Vui lòng thử lại.', ], ], 'quotes' => [ 'index' => [ 'title' => 'Báo giá', 'create-btn' => 'Tạo Báo giá', 'create-success' => 'Báo giá đã được tạo thành công.', 'update-success' => 'Báo giá đã được cập nhật thành công.', 'delete-success' => 'Báo giá đã được xóa thành công.', 'delete-failed' => 'Không thể xóa báo giá.', 'datagrid' => [ 'subject' => 'Chủ đề', 'sales-person' => 'Nhân viên bán hàng', 'expired-at' => 'Hết hạn vào', 'created-at' => 'Tạo vào', 'person' => 'Người', 'subtotal' => 'Tổng phụ', 'discount' => 'Giảm giá', 'tax' => 'Thuế', 'adjustment' => 'Điều chỉnh', 'grand-total' => 'Tổng cộng', 'edit' => 'Chỉnh sửa', 'delete' => 'Xóa', 'print' => 'In', ], 'pdf' => [ 'adjustment' => 'Điều chỉnh', 'amount' => 'Số tiền', 'billing-address' => 'Địa chỉ thanh toán', 'date' => 'Ngày', 'discount' => 'Giảm giá', 'expired-at' => 'Hết hạn vào', 'grand-total' => 'Tổng cộng', 'person' => 'Người', 'price' => 'Giá', 'product-name' => 'Tên sản phẩm', 'quantity' => 'Số lượng', 'quote-id' => 'ID Báo giá', 'sales-person' => 'Nhân viên bán hàng', 'shipping-address' => 'Địa chỉ giao hàng', 'sku' => 'Mã sản phẩm (SKU)', 'sub-total' => 'Tổng phụ', 'subject' => 'Chủ đề', 'tax' => 'Thuế', 'title' => 'Báo giá', ], ], 'create' => [ 'title' => 'Tạo Báo giá', 'save-btn' => 'Lưu Báo giá', 'quote-info' => 'Thông tin Báo giá', 'quote-info-info' => 'Nhập thông tin cơ bản của báo giá.', 'address-info' => 'Thông tin Địa chỉ', 'address-info-info' => 'Thông tin về địa chỉ liên quan đến báo giá.', 'quote-items' => 'Mục Báo giá', 'search-products' => 'Tìm kiếm Sản phẩm', 'link-to-lead' => 'Liên kết tới lead', 'quote-item-info' => 'Thêm Yêu cầu Sản phẩm cho báo giá này.', 'quote-name' => 'Tên Báo giá', 'quantity' => 'Số lượng', 'price' => 'Giá', 'discount' => 'Giảm giá', 'tax' => 'Thuế', 'total' => 'Tổng cộng', 'amount' => 'Số tiền', 'add-item' => '+ Thêm Mục', 'sub-total' => 'Tổng phụ (:symbol)', 'total-discount' => 'Giảm giá (:symbol)', 'total-tax' => 'Thuế (:symbol)', 'total-adjustment' => 'Điều chỉnh (:symbol)', 'grand-total' => 'Tổng cộng (:symbol)', 'discount-amount' => 'Số tiền giảm giá', 'tax-amount' => 'Số tiền thuế', 'adjustment-amount' => 'Số tiền điều chỉnh', 'product-name' => 'Tên Sản phẩm', 'action' => 'Hành động', ], 'edit' => [ 'title' => 'Chỉnh sửa Báo giá', 'save-btn' => 'Lưu Báo giá', 'quote-info' => 'Thông tin Báo giá', 'quote-info-info' => 'Nhập thông tin cơ bản của báo giá.', 'address-info' => 'Thông tin Địa chỉ', 'address-info-info' => 'Thông tin về địa chỉ liên quan đến báo giá.', 'quote-items' => 'Mục Báo giá', 'link-to-lead' => 'Liên kết tới lead', 'quote-item-info' => 'Thêm Yêu cầu Sản phẩm cho báo giá này.', 'quote-name' => 'Tên Báo giá', 'quantity' => 'Số lượng', 'price' => 'Giá', 'search-products' => 'Tìm kiếm Sản phẩm', 'discount' => 'Giảm giá', 'tax' => 'Thuế', 'total' => 'Tổng cộng', 'amount' => 'Số tiền', 'add-item' => '+ Thêm Mục', 'sub-total' => 'Tổng phụ (:symbol)', 'total-discount' => 'Giảm giá (:symbol)', 'total-tax' => 'Thuế (:symbol)', 'total-adjustment' => 'Điều chỉnh (:symbol)', 'grand-total' => 'Tổng cộng (:symbol)', 'discount-amount' => 'Số tiền giảm giá', 'tax-amount' => 'Số tiền thuế', 'adjustment-amount' => 'Số tiền điều chỉnh', 'product-name' => 'Tên Sản phẩm', 'action' => 'Hành động', ], ], 'contacts' => [ 'persons' => [ 'index' => [ 'title' => 'Người', 'create-btn' => 'Tạo Người', 'create-success' => 'Người đã được tạo thành công.', 'update-success' => 'Người đã được cập nhật thành công.', 'all-delete-success' => 'Tất cả người được chọn đã được xóa thành công.', 'partial-delete-warning' => 'Một số người đã được xóa thành công. Những người khác không thể xóa vì có liên kết với khách hàng tiềm năng.', 'none-delete-warning' => 'Không thể xóa bất kỳ người nào được chọn vì họ có liên kết với khách hàng tiềm năng.', 'no-selection' => 'Chưa chọn người nào để xóa.', 'delete-failed' => 'Xóa người được chọn không thành công.', 'datagrid' => [ 'contact-numbers' => 'Số Liên hệ', 'delete' => 'Xóa', 'edit' => 'Chỉnh sửa', 'emails' => 'Email', 'id' => 'ID', 'view' => 'Xem', 'name' => 'Tên', 'organization-name' => 'Tên Tổ chức', ], ], 'view' => [ 'title' => ':name', 'about-person' => 'Thông tin về Người', 'about-organization' => 'Thông tin về Tổ chức', 'activities' => [ 'index' => [ 'all' => 'Tất cả', 'calls' => 'Cuộc gọi', 'meetings' => 'Cuộc họp', 'lunches' => 'Bữa trưa', 'files' => 'Tệp', 'quotes' => 'Báo giá', 'notes' => 'Ghi chú', 'emails' => 'Email', 'by-user' => 'Bởi :user', 'scheduled-on' => 'Đã lên lịch vào', 'location' => 'Vị trí', 'participants' => 'Người tham gia', 'mark-as-done' => 'Đánh dấu là Đã hoàn thành', 'delete' => 'Xóa', 'edit' => 'Chỉnh sửa', ], 'actions' => [ 'mail' => [ 'btn' => 'Mail', 'title' => 'Soạn Mail', 'to' => 'Đến', 'cc' => 'CC', 'bcc' => 'BCC', 'subject' => 'Chủ đề', 'send-btn' => 'Gửi', 'message' => 'Tin nhắn', ], 'file' => [ 'btn' => 'Tệp', 'title' => 'Thêm Tệp', 'title-control' => 'Tiêu đề', 'name' => 'Tên Tệp', 'description' => 'Mô tả', 'file' => 'Tệp', 'save-btn' => 'Lưu Tệp', ], 'note' => [ 'btn' => 'Ghi chú', 'title' => 'Thêm Ghi chú', 'comment' => 'Bình luận', 'save-btn' => 'Lưu Ghi chú', ], 'activity' => [ 'btn' => 'Hoạt động', 'title' => 'Thêm Hoạt động', 'title-control' => 'Tiêu đề', 'description' => 'Mô tả', 'schedule-from' => 'Lên lịch từ', 'schedule-to' => 'Lên lịch đến', 'location' => 'Vị trí', 'call' => 'Cuộc gọi', 'meeting' => 'Cuộc họp', 'lunch' => 'Bữa trưa', 'save-btn' => 'Lưu Hoạt động', ], ], ], 'tags' => [ 'create-success' => 'Thẻ được tạo thành công.', 'destroy-success' => 'Thẻ đã được xóa thành công.', ], ], 'create' => [ 'title' => 'Tạo Người', 'save-btn' => 'Lưu Người', ], 'edit' => [ 'title' => 'Chỉnh sửa Người', 'save-btn' => 'Lưu Người', ], ], 'organizations' => [ 'index' => [ 'title' => 'Tổ chức', 'create-btn' => 'Tạo Tổ chức', 'create-success' => 'Tổ chức đã được tạo thành công.', 'update-success' => 'Tổ chức đã được cập nhật thành công.', 'delete-success' => 'Tổ chức đã được xóa thành công.', 'delete-failed' => 'Không thể xóa tổ chức.', 'datagrid' => [ 'delete' => 'Xóa', 'edit' => 'Chỉnh sửa', 'id' => 'ID', 'name' => 'Tên', 'persons-count' => 'Số người', ], ], 'create' => [ 'title' => 'Tạo Tổ chức', 'save-btn' => 'Lưu Tổ chức', ], 'edit' => [ 'title' => 'Chỉnh sửa Tổ chức', 'save-btn' => 'Lưu Tổ chức', ], ], ], 'products' => [ 'index' => [ 'title' => 'Sản phẩm', 'create-btn' => 'Tạo Sản phẩm', 'create-success' => 'Sản phẩm đã được tạo thành công.', 'update-success' => 'Sản phẩm đã được cập nhật thành công.', 'delete-success' => 'Sản phẩm đã được xóa thành công.', 'delete-failed' => 'Không thể xóa sản phẩm.', 'datagrid' => [ 'allocated' => 'Đã phân bổ', 'delete' => 'Xóa', 'edit' => 'Chỉnh sửa', 'id' => 'ID', 'in-stock' => 'Có sẵn', 'name' => 'Tên', 'on-hand' => 'Sẵn có', 'tag-name' => 'Tên thẻ', 'price' => 'Giá', 'sku' => 'SKU', 'view' => 'Xem', ], ], 'create' => [ 'save-btn' => 'Lưu Sản phẩm', 'title' => 'Tạo Sản phẩm', 'general' => 'Thông tin chung', 'price' => 'Giá', ], 'edit' => [ 'title' => 'Chỉnh sửa Sản phẩm', 'save-btn' => 'Lưu Sản phẩm', 'general' => 'Thông tin chung', 'price' => 'Giá', ], 'view' => [ 'sku' => 'SKU', 'all' => 'Tất cả', 'notes' => 'Ghi chú', 'files' => 'Tệp', 'inventories' => 'Tồn kho', 'change-logs' => 'Nhật ký thay đổi', 'attributes' => [ 'about-product' => 'Thông tin về sản phẩm', ], 'inventory' => [ 'source' => 'Nguồn', 'in-stock' => 'Có sẵn', 'allocated' => 'Đã phân bổ', 'on-hand' => 'Sẵn có', 'actions' => 'Hành động', 'assign' => 'Phân bổ', 'add-source' => 'Thêm nguồn', 'location' => 'Vị trí', 'add-more' => 'Thêm nữa', 'save' => 'Lưu', ], ], ], 'settings' => [ 'title' => 'Cài đặt', 'groups' => [ 'index' => [ 'create-btn' => 'Tạo Nhóm', 'title' => 'Nhóm', 'create-success' => 'Tạo nhóm thành công.', 'update-success' => 'Cập nhật nhóm thành công.', 'destroy-success' => 'Xóa nhóm thành công.', 'delete-failed' => 'Không thể xóa nhóm.', 'delete-failed-associated-users' => 'Không thể xóa nhóm vì đang được sử dụng bởi người dùng.', 'datagrid' => [ 'delete' => 'Xóa', 'description' => 'Mô tả', 'edit' => 'Chỉnh sửa', 'id' => 'ID', 'name' => 'Tên', ], 'edit' => [ 'title' => 'Chỉnh sửa Nhóm', ], 'create' => [ 'name' => 'Tên', 'title' => 'Tạo Nhóm', 'description' => 'Mô tả', 'save-btn' => 'Lưu Nhóm', ], ], ], 'roles' => [ 'index' => [ 'being-used' => 'Vai trò không thể xóa, vì đang được sử dụng trong người dùng quản trị.', 'create-btn' => 'Tạo Vai trò', 'create-success' => 'Vai trò đã được tạo thành công.', 'current-role-delete-error' => 'Không thể xóa vai trò đã gán cho người dùng hiện tại.', 'delete-failed' => 'Không thể xóa vai trò.', 'delete-success' => 'Vai trò đã được xóa thành công.', 'last-delete-error' => 'Cần ít nhất một vai trò.', 'settings' => 'Cài đặt', 'title' => 'Vai trò', 'update-success' => 'Vai trò đã được cập nhật thành công.', 'user-define-error' => 'Không thể xóa vai trò hệ thống.', 'datagrid' => [ 'all' => 'Tất cả', 'custom' => 'Tùy chỉnh', 'delete' => 'Xóa', 'description' => 'Mô tả', 'edit' => 'Chỉnh sửa', 'id' => 'ID', 'name' => 'Tên', 'permission-type' => 'Loại quyền', ], ], 'create' => [ 'access-control' => 'Kiểm soát truy cập', 'all' => 'Tất cả', 'back-btn' => 'Quay lại', 'custom' => 'Tùy chỉnh', 'description' => 'Mô tả', 'general' => 'Thông tin chung', 'name' => 'Tên', 'permissions' => 'Quyền', 'save-btn' => 'Lưu Vai trò', 'title' => 'Tạo Vai trò', ], 'edit' => [ 'access-control' => 'Kiểm soát truy cập', 'all' => 'Tất cả', 'back-btn' => 'Quay lại', 'custom' => 'Tùy chỉnh', 'description' => 'Mô tả', 'general' => 'Thông tin chung', 'name' => 'Tên', 'permissions' => 'Quyền', 'save-btn' => 'Lưu Vai trò', 'title' => 'Chỉnh sửa Vai trò', ], ], 'types' => [ 'index' => [ 'create-btn' => 'Tạo Loại', 'create-success' => 'Loại đã được tạo thành công.', 'delete-failed' => 'Không thể xóa loại.', 'delete-success' => 'Loại đã được xóa thành công.', 'title' => 'Các Loại', 'update-success' => 'Loại đã được cập nhật thành công.', 'datagrid' => [ 'delete' => 'Xóa', 'description' => 'Mô tả', 'edit' => 'Chỉnh sửa', 'id' => 'ID', 'name' => 'Tên', ], 'create' => [ 'name' => 'Tên', 'save-btn' => 'Lưu Loại', 'title' => 'Tạo Loại', ], 'edit' => [ 'title' => 'Chỉnh sửa Loại', ], ], ], 'sources' => [ 'index' => [ 'title' => 'Nguồn', 'create-btn' => 'Tạo Nguồn', 'create-success' => 'Tạo nguồn thành công.', 'delete-failed' => 'Không thể xóa nguồn.', 'delete-success' => 'Xóa nguồn thành công.', 'update-success' => 'Cập nhật nguồn thành công.', 'delete-failed-associated-leads' => 'Không thể xóa nguồn vì nó đang được liên kết với các khách hàng tiềm năng. Vui lòng hủy liên kết hoặc cập nhật các khách hàng đó trước khi xóa.', 'datagrid' => [ 'delete' => 'Xóa', 'edit' => 'Chỉnh sửa', 'id' => 'ID', 'name' => 'Tên', ], 'create' => [ 'name' => 'Tên', 'save-btn' => 'Lưu Nguồn', 'title' => 'Tạo Nguồn', ], 'edit' => [ 'title' => 'Chỉnh sửa Nguồn', ], ], ], 'workflows' => [ 'index' => [ 'title' => 'Quy trình', 'create-btn' => 'Tạo Quy trình', 'create-success' => 'Quy trình đã được tạo thành công.', 'update-success' => 'Quy trình đã được cập nhật thành công.', 'delete-success' => 'Quy trình đã được xóa thành công.', 'delete-failed' => 'Không thể xóa quy trình.', 'datagrid' => [ 'delete' => 'Xóa', 'description' => 'Mô tả', 'edit' => 'Chỉnh sửa', 'id' => 'ID', 'name' => 'Tên', ], ], 'helpers' => [ 'update-related-leads' => 'Cập nhật các đầu mối liên quan', 'send-email-to-sales-owner' => 'Gửi email đến người sở hữu bán hàng', 'send-email-to-participants' => 'Gửi email đến các tham gia', 'add-webhook' => 'Thêm Webhook', 'update-lead' => 'Cập nhật đầu mối', 'update-person' => 'Cập nhật người', 'send-email-to-person' => 'Gửi email đến người', 'add-tag' => 'Thêm Thẻ', 'add-note-as-activity' => 'Thêm Ghi chú như Hoạt động', 'update-quote' => 'Cập nhật báo giá', ], 'create' => [ 'title' => 'Tạo Quy trình', 'event' => 'Sự kiện', 'back-btn' => 'Quay lại', 'save-btn' => 'Lưu Quy trình', 'name' => 'Tên', 'basic-details' => 'Thông tin cơ bản', 'description' => 'Mô tả', 'actions' => 'Hành động', 'basic-details-info' => 'Nhập thông tin cơ bản của quy trình.', 'event-info' => 'Một sự kiện kích hoạt, kiểm tra, điều kiện và thực hiện các hành động đã được định nghĩa.', 'conditions' => 'Điều kiện', 'conditions-info' => 'Điều kiện là các quy tắc kiểm tra kịch bản, được kích hoạt trong các dịp cụ thể.', 'actions-info' => 'Một hành động không chỉ giảm khối lượng công việc mà còn làm cho tự động hóa CRM dễ dàng hơn.', 'value' => 'Giá trị', 'condition-type' => 'Loại điều kiện', 'all-condition-are-true' => 'Tất cả điều kiện đều đúng', 'any-condition-are-true' => 'Bất kỳ điều kiện nào cũng đúng', 'add-condition' => 'Thêm Điều kiện', 'add-action' => 'Thêm Hành động', 'yes' => 'Có', 'no' => 'Không', 'email' => 'Email', 'is-equal-to' => 'Bằng với', 'is-not-equal-to' => 'Không bằng với', 'equals-or-greater-than' => 'Bằng hoặc lớn hơn', 'equals-or-less-than' => 'Bằng hoặc nhỏ hơn', 'greater-than' => 'Lớn hơn', 'less-than' => 'Nhỏ hơn', 'type' => 'Loại', 'contain' => 'Chứa', 'contains' => 'Chứa', 'does-not-contain' => 'Không chứa', ], 'edit' => [ 'title' => 'Chỉnh sửa Quy trình', 'event' => 'Sự kiện', 'back-btn' => 'Quay lại', 'save-btn' => 'Lưu Quy trình', 'name' => 'Tên', 'basic-details' => 'Thông tin cơ bản', 'description' => 'Mô tả', 'actions' => 'Hành động', 'type' => 'Loại', 'basic-details-info' => 'Nhập thông tin cơ bản của quy trình.', 'event-info' => 'Một sự kiện kích hoạt, kiểm tra, điều kiện và thực hiện các hành động đã được định nghĩa.', 'conditions' => 'Điều kiện', 'conditions-info' => 'Điều kiện là các quy tắc kiểm tra kịch bản, được kích hoạt trong các dịp cụ thể.', 'actions-info' => 'Một hành động không chỉ giảm khối lượng công việc mà còn làm cho tự động hóa CRM dễ dàng hơn.', 'value' => 'Giá trị', 'condition-type' => 'Loại điều kiện', 'all-condition-are-true' => 'Tất cả điều kiện đều đúng', 'any-condition-are-true' => 'Bất kỳ điều kiện nào cũng đúng', 'add-condition' => 'Thêm Điều kiện', 'add-action' => 'Thêm Hành động', 'yes' => 'Có', 'no' => 'Không', 'email' => 'Email', 'is-equal-to' => 'Bằng với', 'is-not-equal-to' => 'Không bằng với', 'equals-or-greater-than' => 'Bằng hoặc lớn hơn', 'equals-or-less-than' => 'Bằng hoặc nhỏ hơn', 'greater-than' => 'Lớn hơn', 'less-than' => 'Nhỏ hơn', 'contain' => 'Chứa', 'contains' => 'Chứa', 'does-not-contain' => 'Không chứa', ], ], 'webforms' => [ 'index' => [ 'title' => 'Biểu mẫu Web', 'create-btn' => 'Tạo Biểu mẫu Web', 'create-success' => 'Biểu mẫu Web đã được tạo thành công.', 'update-success' => 'Biểu mẫu Web đã được cập nhật thành công.', 'delete-success' => 'Biểu mẫu Web đã được xóa thành công.', 'delete-failed' => 'Biểu mẫu Web không thể bị xóa.', 'datagrid' => [ 'id' => 'ID', 'title' => 'Tiêu đề', 'edit' => 'Chỉnh sửa', 'delete' => 'Xóa', ], ], 'create' => [ 'title' => 'Tạo Biểu mẫu Web', 'add-attribute-btn' => 'Thêm Nút Thuộc Tính', 'attribute-label-color' => 'Màu Nhãn Thuộc Tính', 'attributes' => 'Thuộc Tính', 'attributes-info' => 'Thêm các thuộc tính tùy chỉnh vào biểu mẫu.', 'background-color' => 'Màu Nền', 'create-lead' => 'Tạo Dẫn Dắt', 'customize-webform' => 'Tùy Chỉnh Biểu Mẫu Web', 'customize-webform-info' => 'Tùy chỉnh biểu mẫu web của bạn với màu sắc của các phần tử theo lựa chọn của bạn.', 'description' => 'Mô tả', 'display-custom-message' => 'Hiển thị thông điệp tùy chỉnh', 'form-background-color' => 'Màu Nền Biểu Mẫu', 'form-submit-btn-color' => 'Màu Nút Gửi Biểu Mẫu', 'form-submit-button-color' => 'Màu Nút Gửi Biểu Mẫu', 'form-title-color' => 'Màu Tiêu Đề Biểu Mẫu', 'general' => 'Chung', 'leads' => 'Dẫn Dắt', 'person' => 'Người', 'save-btn' => 'Lưu Biểu Mẫu Web', 'submit-button-label' => 'Nhãn Nút Gửi', 'submit-success-action' => 'Hành Động Thành Công Khi Gửi', 'redirect-to-url' => 'Chuyển Hướng Đến URL', 'choose-value' => 'Chọn Giá Trị', 'select-file' => 'Chọn Tập Tin', 'select-image' => 'Chọn Hình Ảnh', 'enter-value' => 'Nhập Giá Trị', ], 'edit' => [ 'add-attribute-btn' => 'Thêm Nút Thuộc Tính', 'attribute-label-color' => 'Màu Nhãn Thuộc Tính', 'attributes' => 'Thuộc Tính', 'attributes-info' => 'Thêm các thuộc tính tùy chỉnh vào biểu mẫu.', 'background-color' => 'Màu Nền', 'choose-value' => 'Chọn Giá Trị', 'code-snippet' => 'Mã Snippet', 'copied' => 'Đã Sao Chép', 'copy' => 'Sao Chép', 'create-lead' => 'Tạo Dẫn Dắt', 'customize-webform' => 'Tùy Chỉnh Biểu Mẫu Web', 'customize-webform-info' => 'Tùy chỉnh biểu mẫu web của bạn với màu sắc của các phần tử theo lựa chọn của bạn.', 'description' => 'Mô tả', 'display-custom-message' => 'Hiển thị thông điệp tùy chỉnh', 'embed' => 'Nhúng', 'enter-value' => 'Nhập Giá Trị', 'form-background-color' => 'Màu Nền Biểu Mẫu', 'form-submit-btn-color' => 'Màu Nút Gửi Biểu Mẫu', 'form-submit-button-color' => 'Màu Nút Gửi Biểu Mẫu', 'form-title-color' => 'Màu Tiêu Đề Biểu Mẫu', 'general' => 'Chung', 'leads' => 'Dẫn Dắt', 'person' => 'Người', 'preview' => 'Xem Trước', 'public-url' => 'URL Công Khai', 'redirect-to-url' => 'Chuyển Hướng Đến URL', 'save-btn' => 'Lưu Biểu Mẫu Web', 'select-file' => 'Chọn Tập Tin', 'select-image' => 'Chọn Hình Ảnh', 'submit-button-label' => 'Nhãn Nút Gửi', 'submit-success-action' => 'Hành Động Thành Công Khi Gửi', 'title' => 'Chỉnh Sửa Biểu Mẫu Web', ], ], 'email-template' => [ 'index' => [ 'create-btn' => 'Tạo Mẫu Email', 'title' => 'Mẫu Email', 'create-success' => 'Mẫu Email đã được tạo thành công.', 'update-success' => 'Mẫu Email đã được cập nhật thành công.', 'delete-success' => 'Mẫu Email đã được xóa thành công.', 'delete-failed' => 'Mẫu Email không thể bị xóa.', 'datagrid' => [ 'delete' => 'Xóa', 'edit' => 'Chỉnh sửa', 'id' => 'ID', 'name' => 'Tên', 'subject' => 'Chủ đề', ], ], 'create' => [ 'title' => 'Tạo Mẫu Email', 'save-btn' => 'Lưu Mẫu Email', 'email-template' => 'Mẫu Email', 'subject' => 'Chủ đề', 'content' => 'Nội dung', 'subject-placeholders' => 'Biến thể Chủ đề', 'general' => 'Chung', 'name' => 'Tên', ], 'edit' => [ 'title' => 'Chỉnh Sửa Mẫu Email', 'save-btn' => 'Lưu Mẫu Email', 'email-template' => 'Mẫu Email', 'subject' => 'Chủ đề', 'content' => 'Nội dung', 'subject-placeholders' => 'Biến thể Chủ đề', 'general' => 'Chung', 'name' => 'Tên', ], ], 'marketing' => [ 'events' => [ 'index' => [ 'create-btn' => 'Tạo Sự kiện', 'title' => 'Sự kiện', 'create-success' => 'Sự kiện đã được tạo thành công.', 'update-success' => 'Sự kiện đã được cập nhật thành công.', 'delete-success' => 'Sự kiện đã được xóa thành công.', 'delete-failed' => 'Không thể xóa sự kiện.', 'mass-delete-success' => 'Các sự kiện đã được xóa thành công', 'delete-failed-associated-campaigns' => 'Không thể xóa sự kiện vì nó đang được liên kết với các chiến dịch. Vui lòng hủy liên kết hoặc cập nhật các chiến dịch đó trước khi xóa.', 'datagrid' => [ 'delete' => 'Xóa', 'edit' => 'Chỉnh sửa', 'id' => 'ID', 'name' => 'Tên', 'description' => 'Mô tả', 'date' => 'Ngày', ], 'create' => [ 'title' => 'Tạo Sự kiện', 'name' => 'Tên', 'date' => 'Ngày', 'description' => 'Mô tả', 'save-btn' => 'Lưu Sự kiện', ], 'edit' => [ 'title' => 'Chỉnh sửa Sự kiện', ], ], ], 'campaigns' => [ 'index' => [ 'create-btn' => 'Tạo Chiến dịch', 'title' => 'Chiến dịch', 'create-success' => 'Chiến dịch đã được tạo thành công.', 'update-success' => 'Chiến dịch đã được cập nhật thành công.', 'delete-success' => 'Chiến dịch đã được xóa thành công.', 'delete-failed' => 'Không thể xóa chiến dịch.', 'mass-delete-success' => 'Các chiến dịch đã được xóa thành công', 'datagrid' => [ 'id' => 'ID', 'name' => 'Tên', 'subject' => 'Chủ đề', 'status' => 'Trạng thái', 'active' => 'Kích hoạt', 'inactive' => 'Không kích hoạt', 'edit' => 'Chỉnh sửa', 'delete' => 'Xóa', ], 'create' => [ 'title' => 'Tạo Chiến dịch', 'name' => 'Tên', 'type' => 'Loại', 'subject' => 'Chủ đề', 'event' => 'Sự kiện', 'email-template' => 'Mẫu Email', 'status' => 'Trạng thái', ], 'edit' => [ 'title' => 'Chỉnh sửa Chiến dịch', ], ], ], ], 'tags' => [ 'index' => [ 'create-btn' => 'Tạo Thẻ', 'title' => 'Thẻ', 'create-success' => 'Thẻ đã được tạo thành công.', 'update-success' => 'Thẻ đã được cập nhật thành công.', 'delete-success' => 'Thẻ đã được xóa thành công.', 'delete-failed' => 'Thẻ không thể bị xóa.', 'datagrid' => [ 'delete' => 'Xóa', 'edit' => 'Chỉnh sửa', 'id' => 'ID', 'name' => 'Tên', 'users' => 'Người dùng', 'created-at' => 'Ngày tạo', ], 'create' => [ 'name' => 'Tên', 'save-btn' => 'Lưu Thẻ', 'title' => 'Tạo Thẻ', 'color' => 'Màu sắc', ], 'edit' => [ 'title' => 'Chỉnh Sửa Thẻ', ], ], ], 'users' => [ 'index' => [ 'create-btn' => 'Tạo Người Dùng', 'create-success' => 'Người dùng đã được tạo thành công.', 'delete-failed' => 'Người dùng không thể bị xóa.', 'delete-success' => 'Người dùng đã được xóa thành công.', 'last-delete-error' => 'Cần ít nhất một người dùng.', 'mass-delete-failed' => 'Người dùng không thể bị xóa.', 'mass-delete-success' => 'Người dùng đã được xóa thành công.', 'mass-update-failed' => 'Người dùng không thể được cập nhật.', 'mass-update-success' => 'Người dùng đã được cập nhật thành công.', 'title' => 'Người Dùng', 'update-success' => 'Người dùng đã được cập nhật thành công.', 'user-define-error' => 'Không thể xóa người dùng hệ thống.', 'active' => 'Kích hoạt', 'inactive' => 'Không kích hoạt', 'datagrid' => [ 'active' => 'Kích hoạt', 'created-at' => 'Ngày tạo', 'delete' => 'Xóa', 'edit' => 'Chỉnh sửa', 'email' => 'Email', 'id' => 'ID', 'inactive' => 'Không kích hoạt', 'name' => 'Tên', 'status' => 'Trạng thái', 'update-status' => 'Cập nhật Trạng thái', 'users' => 'Người dùng', ], 'create' => [ 'confirm-password' => 'Xác nhận Mật khẩu', 'email' => 'Email', 'general' => 'Chung', 'global' => 'Toàn cầu', 'group' => 'Nhóm', 'individual' => 'Cá nhân', 'name' => 'Tên', 'password' => 'Mật khẩu', 'permission' => 'Quyền hạn', 'role' => 'Vai trò', 'save-btn' => 'Lưu Người Dùng', 'status' => 'Trạng thái', 'title' => 'Tạo Người Dùng', 'view-permission' => 'Xem Quyền Hạn', 'select-at-lest-one-group' => 'Select at least one group', ], 'edit' => [ 'title' => 'Chỉnh Sửa Người Dùng', ], ], ], 'pipelines' => [ 'index' => [ 'title' => 'Quy Trình', 'create-btn' => 'Tạo Quy Trình', 'create-success' => 'Quy trình đã được tạo thành công.', 'update-success' => 'Quy trình đã được cập nhật thành công.', 'default-required' => 'Cần ít nhất một pipeline mặc định.', 'delete-success' => 'Quy trình đã được xóa thành công.', 'delete-failed' => 'Quy trình không thể bị xóa.', 'default-delete-error' => 'Quy trình mặc định không thể bị xóa.', 'datagrid' => [ 'delete' => 'Xóa', 'edit' => 'Chỉnh sửa', 'id' => 'ID', 'is-default' => 'Có phải là mặc định', 'name' => 'Tên', 'no' => 'Không', 'rotten-days' => 'Ngày Hỏng', 'yes' => 'Có', ], ], 'create' => [ 'title' => 'Tạo Quy Trình', 'save-btn' => 'Lưu Quy Trình', 'name' => 'Tên', 'rotten-days' => 'Ngày Hỏng', 'mark-as-default' => 'Đánh dấu là Mặc định', 'general' => 'Chung', 'probability' => 'Xác Suất (%)', 'new-stage' => 'Mới', 'won-stage' => 'Thắng', 'lost-stage' => 'Thua', 'stage-btn' => 'Thêm Giai Đoạn', 'stages' => 'Các Giai Đoạn', 'duplicate-name' => 'Trường "Tên" không được trùng lặp', 'delete-stage' => 'Xóa Giai Đoạn', 'add-new-stages' => 'Thêm Giai Đoạn Mới', 'add-stage-info' => 'Thêm giai đoạn mới cho Quy trình của bạn', 'newly-added' => 'Mới Thêm', 'stage-delete-success' => 'Giai Đoạn đã được xóa thành công', ], 'edit' => [ 'title' => 'Chỉnh Sửa Quy Trình', 'save-btn' => 'Lưu Quy Trình', 'name' => 'Tên', 'rotten-days' => 'Ngày Hỏng', 'mark-as-default' => 'Đánh dấu là Mặc định', 'general' => 'Chung', 'probability' => 'Xác Suất (%)', 'new-stage' => 'Mới', 'won-stage' => 'Thắng', 'lost-stage' => 'Thua', 'stage-btn' => 'Thêm Giai Đoạn', 'stages' => 'Các Giai Đoạn', 'duplicate-name' => 'Trường "Tên" không được trùng lặp', 'delete-stage' => 'Xóa Giai Đoạn', 'add-new-stages' => 'Thêm Giai Đoạn Mới', 'add-stage-info' => 'Thêm giai đoạn mới cho Quy trình của bạn', 'stage-delete-success' => 'Giai Đoạn đã được xóa thành công', ], ], 'webhooks' => [ 'index' => [ 'title' => 'Webhooks', 'create-btn' => 'Tạo Webhook', 'create-success' => 'Webhook đã được tạo thành công.', 'update-success' => 'Webhook đã được cập nhật thành công.', 'delete-success' => 'Webhook đã được xóa thành công.', 'delete-failed' => 'Webhook không thể bị xóa.', 'datagrid' => [ 'id' => 'ID', 'delete' => 'Xóa', 'edit' => 'Chỉnh sửa', 'name' => 'Tên', 'entity-type' => 'Loại Đối Tượng', 'end-point' => 'Điểm Kết Thúc', ], ], 'create' => [ 'title' => 'Tạo Webhook', 'save-btn' => 'Lưu Webhook', 'info' => 'Nhập thông tin chi tiết của webhooks', 'url-and-parameters' => 'URL Và Tham Số', 'method' => 'Phương Thức', 'post' => 'Post', 'put' => 'Put', 'url-endpoint' => 'Điểm Kết Thúc URL', 'parameters' => 'Tham Số', 'add-new-parameter' => 'Thêm Tham Số Mới', 'url-preview' => 'Xem Trước URL:', 'headers' => 'Tiêu Đề', 'add-new-header' => 'Thêm Tiêu Đề Mới', 'body' => 'Nội Dung', 'default' => 'Mặc định', 'x-www-form-urlencoded' => 'x-www-form-urlencoded', 'key-and-value' => 'Khóa và Giá trị', 'add-new-payload' => 'Thêm payload mới', 'raw' => 'Thô', 'general' => 'Chung', 'name' => 'Tên', 'entity-type' => 'Loại Đối Tượng', 'insert-placeholder' => 'Chèn Placeholder', 'description' => 'Mô Tả', 'json' => 'Json', 'text' => 'Văn bản', ], 'edit' => [ 'title' => 'Chỉnh Sửa Webhook', 'edit-btn' => 'Lưu Webhook', 'save-btn' => 'Lưu Webhook', 'info' => 'Nhập thông tin chi tiết của webhooks', 'url-and-parameters' => 'URL Và Tham Số', 'method' => 'Phương Thức', 'post' => 'Post', 'put' => 'Put', 'url-endpoint' => 'Điểm Kết Thúc URL', 'parameters' => 'Tham Số', 'add-new-parameter' => 'Thêm Tham Số Mới', 'url-preview' => 'Xem Trước URL:', 'headers' => 'Tiêu Đề', 'add-new-header' => 'Thêm Tiêu Đề Mới', 'body' => 'Nội Dung', 'default' => 'Mặc định', 'x-www-form-urlencoded' => 'x-www-form-urlencoded', 'key-and-value' => 'Khóa và Giá trị', 'add-new-payload' => 'Thêm payload mới', 'raw' => 'Thô', 'general' => 'Chung', 'name' => 'Tên', 'entity-type' => 'Loại Đối Tượng', 'insert-placeholder' => 'Chèn Placeholder', 'description' => 'Mô Tả', 'json' => 'Json', 'text' => 'Văn bản', ], ], 'warehouses' => [ 'index' => [ 'title' => 'Kho Hàng', 'create-btn' => 'Tạo Kho Hàng', 'create-success' => 'Kho hàng đã được tạo thành công.', 'name-exists' => 'Tên kho hàng đã tồn tại.', 'update-success' => 'Kho hàng đã được cập nhật thành công.', 'delete-success' => 'Kho hàng đã được xóa thành công.', 'delete-failed' => 'Kho hàng không thể bị xóa.', 'datagrid' => [ 'id' => 'ID', 'name' => 'Tên', 'contact-name' => 'Tên Liên Hệ', 'delete' => 'Xóa', 'edit' => 'Chỉnh sửa', 'view' => 'Xem', 'created-at' => 'Ngày Tạo', 'products' => 'Sản Phẩm', 'contact-emails' => 'Email Liên Hệ', 'contact-numbers' => 'Số Điện Thoại Liên Hệ', ], ], 'create' => [ 'title' => 'Tạo Kho Hàng', 'save-btn' => 'Lưu Kho Hàng', 'contact-info' => 'Thông Tin Liên Hệ', ], 'edit' => [ 'title' => 'Chỉnh Sửa Kho Hàng', 'save-btn' => 'Lưu Kho Hàng', 'contact-info' => 'Thông Tin Liên Hệ', ], 'view' => [ 'all' => 'Tất Cả', 'notes' => 'Ghi Chú', 'files' => 'Tệp', 'location' => 'Vị Trí', 'change-logs' => 'Nhật Ký Thay Đổi', 'locations' => [ 'action' => 'Hành Động', 'add-location' => 'Thêm Vị Trí', 'create-success' => 'Vị trí đã được tạo thành công.', 'delete' => 'Xóa', 'delete-failed' => 'Vị trí không thể bị xóa.', 'delete-success' => 'Vị trí đã được xóa thành công.', 'name' => 'Tên', 'save-btn' => 'Lưu', ], 'general-information' => [ 'title' => 'Thông Tin Chung', ], 'contact-information' => [ 'title' => 'Thông Tin Liên Hệ', ], ], ], 'attributes' => [ 'index' => [ 'title' => 'Thuộc Tính', 'create-btn' => 'Tạo Thuộc Tính', 'create-success' => 'Thuộc tính đã được tạo thành công.', 'update-success' => 'Thuộc tính đã được cập nhật thành công.', 'delete-success' => 'Thuộc tính đã được xóa thành công.', 'delete-failed' => 'Không thể xóa thuộc tính.', 'user-define-error' => 'Không thể xóa thuộc tính hệ thống.', 'mass-delete-failed' => 'Các thuộc tính hệ thống không thể bị xóa.', 'datagrid' => [ 'yes' => 'Có', 'no' => 'Không', 'id' => 'ID', 'code' => 'Mã', 'name' => 'Tên', 'entity-type' => 'Loại Thực Thể', 'type' => 'Loại', 'is-default' => 'Mặc Định', 'edit' => 'Chỉnh sửa', 'delete' => 'Xóa', 'entity-types' => [ 'leads' => 'Khách hàng tiềm năng', 'organizations' => 'Tổ chức', 'persons' => 'Người', 'products' => 'Sản phẩm', 'quotes' => 'Báo giá', 'warehouses' => 'Kho hàng', ], 'types' => [ 'text' => 'Văn bản', 'textarea' => 'Vùng văn bản', 'price' => 'Giá', 'boolean' => 'Boolean', 'select' => 'Chọn', 'multiselect' => 'Chọn nhiều', 'checkbox' => 'Hộp kiểm', 'email' => 'Email', 'address' => 'Địa chỉ', 'phone' => 'Điện thoại', 'lookup' => 'Tìm kiếm', 'datetime' => 'Ngày giờ', 'date' => 'Ngày', 'image' => 'Hình ảnh', 'file' => 'Tệp tin', ], ], ], 'create' => [ 'title' => 'Tạo Thuộc Tính', 'save-btn' => 'Lưu Thuộc Tính', 'code' => 'Mã', 'name' => 'Tên', 'entity-type' => 'Loại Thực Thể', 'type' => 'Loại', 'validations' => 'Xác Thực', 'is-required' => 'Bắt Buộc', 'input-validation' => 'Xác Thực Đầu Vào', 'is-unique' => 'Là Độc Nhất', 'labels' => 'Nhãn', 'general' => 'Chung', 'numeric' => 'Số', 'decimal' => 'Thập Phân', 'url' => 'Url', 'options' => 'Tùy Chọn', 'option-type' => 'Loại Tùy Chọn', 'lookup-type' => 'Loại Tra Cứu', 'add-option' => 'Thêm Tùy Chọn', 'save-option' => 'Lưu Tùy Chọn', 'option-name' => 'Tên Tùy Chọn', 'add-attribute-options' => 'Thêm Tùy Chọn Thuộc Tính', 'text' => 'Văn Bản', 'textarea' => 'Khung Văn Bản', 'price' => 'Giá', 'boolean' => 'Boolean', 'select' => 'Chọn', 'multiselect' => 'Chọn Nhiều', 'email' => 'Email', 'address' => 'Địa Chỉ', 'phone' => 'Điện Thoại', 'datetime' => 'Ngày Giờ', 'date' => 'Ngày', 'image' => 'Hình Ảnh', 'file' => 'Tệp', 'lookup' => 'Tra Cứu', 'entity_type' => 'Loại thực thể', 'checkbox' => 'Hộp Kiểm', 'is_required' => 'Bắt Buộc', 'is_unique' => 'Là Độc Nhất', 'actions' => 'Hành Động', ], 'edit' => [ 'actions' => 'Hành Động', 'add-attribute-options' => 'Thêm Tùy Chọn Thuộc Tính', 'add-option' => 'Thêm Tùy Chọn', 'address' => 'Địa Chỉ', 'boolean' => 'Boolean', 'checkbox' => 'Hộp Kiểm', 'code' => 'Mã', 'date' => 'Ngày', 'datetime' => 'Ngày Giờ', 'decimal' => 'Thập Phân', 'email' => 'Email', 'entity-type' => 'Loại Thực Thể', 'entity_type' => 'Loại thực thể', 'file' => 'Tệp', 'general' => 'Chung', 'image' => 'Hình Ảnh', 'input-validation' => 'Xác Thực Đầu Vào', 'is-required' => 'Bắt Buộc', 'is-unique' => 'Là Độc Nhất', 'is_required' => 'Bắt Buộc', 'is_unique' => 'Là Độc Nhất', 'labels' => 'Nhãn', 'lookup' => 'Tra Cứu', 'lookup-type' => 'Loại Tra Cứu', 'multiselect' => 'Chọn Nhiều', 'name' => 'Tên', 'numeric' => 'Số', 'option-deleted' => 'Attribute Option is deleted successfully', 'option-name' => 'Tên Tùy Chọn', 'option-type' => 'Loại Tùy Chọn', 'options' => 'Tùy Chọn', 'phone' => 'Điện Thoại', 'price' => 'Giá', 'save-btn' => 'Lưu Thuộc Tính', 'save-option' => 'Lưu Tùy Chọn', 'select' => 'Chọn', 'text' => 'Văn Bản', 'textarea' => 'Khung Văn Bản', 'title' => 'Chỉnh Sửa Thuộc Tính', 'type' => 'Loại', 'url' => 'Url', 'validations' => 'Xác Thực', ], ], 'data-transfer' => [ 'imports' => [ 'create' => [ 'action' => 'Action', 'allowed-errors' => 'Allowed Errors', 'back-btn' => 'Back', 'create-update' => 'Create/Update', 'delete' => 'Delete', 'download-sample' => 'Download Sample', 'field-separator' => 'Field Separator', 'file' => 'File', 'general' => 'General', 'images-directory' => 'Images Directory Path', 'process-in-queue' => 'Process In Queue', 'results' => 'Results', 'save-btn' => 'Save Import', 'settings' => 'Settings', 'skip-errors' => 'Skip Errors', 'stop-on-errors' => 'Stop on Errors', 'title' => 'Create Import', 'type' => 'Type', 'validation-strategy' => 'Validation Strategy', ], 'edit' => [ 'action' => 'Action', 'allowed-errors' => 'Allowed Errors', 'back-btn' => 'Back', 'create-update' => 'Create/Update', 'delete' => 'Delete', 'download-sample' => 'Download Sample', 'field-separator' => 'Field Separator', 'file' => 'File', 'general' => 'General', 'images-directory' => 'Images Directory Path', 'process-in-queue' => 'Process In Queue', 'results' => 'Results', 'save-btn' => 'Save Import', 'settings' => 'Settings', 'skip-errors' => 'Skip Errors', 'stop-on-errors' => 'Stop on Errors', 'title' => 'Edit Import', 'type' => 'Type', 'validation-strategy' => 'Validation Strategy', ], 'index' => [ 'button-title' => 'Create Import', 'title' => 'Imports', 'datagrid' => [ 'actions' => 'Actions', 'completed-at' => 'Completed At', 'created' => 'Created', 'delete' => 'Delete', 'deleted' => 'Deleted', 'edit' => 'Edit', 'error-file' => 'Error File', 'id' => 'ID', 'started-at' => 'Started At', 'state' => 'State', 'summary' => 'Summary', 'type' => 'Type', 'updated' => 'Updated', 'uploaded-file' => 'Uploaded File', ], ], 'import' => [ 'back-btn' => 'Back', 'completed-batches' => 'Total Batches Completed:', 'download-error-report' => 'Download Full Report', 'edit-btn' => 'Edit', 'imported-info' => 'Congratulations! Your import was successful.', 'importing-info' => 'Import In Process', 'indexing-info' => 'Resources Indexing (Price, Inventory and Elastic Search) In Progress', 'linking-info' => 'Resources Linking In Progress', 'progress' => 'Progress:', 'title' => 'Import', 'total-batches' => 'Total Batches:', 'total-created' => 'Total Records Created:', 'total-deleted' => 'Total Records Deleted:', 'total-errors' => 'Total Errors:', 'total-invalid-rows' => 'Total Invalid Rows:', 'total-rows-processed' => 'Total Rows Processed:', 'total-updated' => 'Total Records Updated:', 'validate' => 'Validate', 'validate-info' => 'Click on Validate Data to check your import.', 'validating-info' => 'The data started reading and Validating', 'validation-failed-info' => 'Your import is invalid. Please fix the following errors and try again.', 'validation-success-info' => 'Your import is valid. Click on Import to start the import process.', ], 'create-success' => 'Import created successfully.', 'delete-failed' => 'Import deletion failed unexpectedly.', 'delete-success' => 'Import deleted successfully.', 'not-valid' => 'Import is invalid', 'nothing-to-import' => 'There are no resources to import.', 'setup-queue-error' => 'Please change your queue driver to "database" or "redis" to start the import process.', 'update-success' => 'Import updated successfully.', ], ], ], 'activities' => [ 'index' => [ 'title' => 'Hoạt Động', 'datagrid' => [ 'comment' => 'Ghi Chú', 'created_at' => 'Thời Gian Tạo', 'created_by' => 'Người Tạo', 'edit' => 'Chỉnh Sửa', 'id' => 'ID', 'done' => 'Đã Hoàn Thành', 'not-done' => 'Chưa Hoàn Thành', 'lead' => 'Người Dẫn Dắt', 'mass-delete' => 'Xóa Hàng Loạt', 'mass-update' => 'Cập Nhật Hàng Loạt', 'schedule-from' => 'Lịch Từ', 'schedule-to' => 'Lịch Đến', 'schedule_from' => 'Lịch Từ', 'schedule_to' => 'Lịch Đến', 'title' => 'Tiêu Đề', 'is_done' => 'Đã Hoàn Thành', 'type' => 'Loại', 'update' => 'Cập Nhật', 'call' => 'Cuộc Gọi', 'meeting' => 'Cuộc Họp', 'lunch' => 'Bữa Trưa', ], ], 'edit' => [ 'title' => 'Chỉnh Sửa Hoạt Động', 'back-btn' => 'Quay Lại', 'save-btn' => 'Lưu Hoạt Động', 'type' => 'Loại Hoạt Động', 'call' => 'Cuộc Gọi', 'meeting' => 'Cuộc Họp', 'lunch' => 'Bữa Trưa', 'schedule_to' => 'Lịch Đến', 'schedule_from' => 'Lịch Từ', 'location' => 'Địa Điểm', 'comment' => 'Ghi Chú', 'lead' => 'Người Dẫn Dắt', 'participants' => 'Người Tham Gia', 'general' => 'Chung', 'persons' => 'Người', 'no-result-found' => 'Không tìm thấy bản ghi.', 'users' => 'Người Dùng', ], 'updated' => 'Đã cập nhật :attribute', 'created' => 'Đã tạo', 'duration-overlapping' => 'Người tham gia có một cuộc họp khác vào thời điểm này. Bạn có muốn tiếp tục không?', 'create-success' => 'Hoạt động được tạo thành công.', 'update-success' => 'Hoạt động được cập nhật thành công.', 'overlapping-error' => 'Người tham gia có một cuộc họp khác vào thời điểm này.', 'destroy-success' => 'Hoạt động đã được xóa thành công.', 'delete-failed' => 'Không thể xóa hoạt động.', 'mass-update-success' => 'Hoạt động được cập nhật thành công.', 'mass-destroy-success' => 'Hoạt động đã được xóa thành công.', 'mass-delete-failed' => 'Không thể xóa hoạt động.', ], 'mail' => [ 'index' => [ 'compose' => 'Soạn Thư', 'draft' => 'Thư Nháp', 'inbox' => 'Hộp Thư Đến', 'outbox' => 'Hộp Thư Đi', 'sent' => 'Đã Gửi', 'trash' => 'Thùng Rác', 'compose-mail-btn' => 'Soạn Thư', 'btn' => 'Thư', 'mail' => [ 'title' => 'Soạn Thư', 'to' => 'Đến', 'enter-emails' => 'Nhấn enter để thêm email', 'cc' => 'CC', 'bcc' => 'BCC', 'subject' => 'Chủ Đề', 'send-btn' => 'Gửi', 'message' => 'Tin Nhắn', 'draft' => 'Thư Nháp', ], 'datagrid' => [ 'id' => 'ID', 'from' => 'Từ', 'to' => 'Đến', 'subject' => 'Chủ đề', 'tags' => 'Thẻ', 'content' => 'Nội dung', 'attachments' => 'Tệp đính kèm', 'date' => 'Ngày', 'move-to-inbox' => 'Di chuyển vào hộp thư đến', 'move-to-trash' => 'Đã chuyển vào thùng rác', 'edit' => 'Chỉnh sửa', 'view' => 'Xem', 'delete' => 'Xóa', ], ], 'create-success' => 'Email đã được gửi thành công.', 'update-success' => 'Email đã được cập nhật thành công.', 'mass-update-success' => 'Các email đã được cập nhật thành công.', 'delete-success' => 'Email đã được xóa thành công.', 'delete-failed' => 'Email không thể bị xóa.', 'invalid-route' => 'Đường dẫn không hợp lệ cho email.', 'unauthorized' => 'Hành động này không được phép.', 'view' => [ 'title' => 'Thư', 'subject' => ':subject', 'link-mail' => 'Liên Kết Thư', 'to' => 'Đến', 'cc' => 'CC', 'bcc' => 'BCC', 'reply' => 'Trả Lời', 'reply-all' => 'Trả Lời Tất Cả', 'forward' => 'Chuyển Tiếp', 'delete' => 'Xóa', 'enter-mails' => 'Nhập ID email', 'rotten-days' => 'Người dẫn dắt đã hỏng trong :days ngày', 'search-an-existing-lead' => 'Tìm kiếm người dẫn dắt hiện có', 'search-an-existing-contact' => 'Tìm kiếm liên hệ hiện có', 'message' => 'Tin Nhắn', 'add-attachments' => 'Thêm Tệp Đính Kèm', 'discard' => 'Bỏ Qua', 'send' => 'Gửi', 'no-result-found' => 'Không tìm thấy kết quả', 'add-new-contact' => 'Thêm Liên Hệ Mới', 'description' => 'Mô Tả', 'search' => 'Tìm kiếm...', 'add-new-lead' => 'Thêm Người Dẫn Dắt Mới', 'create-new-contact' => 'Tạo Liên Hệ Mới', 'save-contact' => 'Lưu Liên Hệ', 'create-lead' => 'Tạo Người Dẫn Dắt', 'linked-contact' => 'Liên Hệ Đã Liên Kết', 'link-to-contact' => 'Liên Kết Với Liên Hệ', 'link-to-lead' => 'Liên Kết Với Người Dẫn Dắt', 'linked-lead' => 'Người Dẫn Dắt Đã Liên Kết', 'lead-details' => 'Chi Tiết Người Dẫn Dắt', 'contact-person' => 'Người Liên Hệ', 'product' => 'Sản Phẩm', 'tags' => [ 'create-success' => 'Thẻ đã được tạo thành công.', 'destroy-success' => 'Thẻ đã được xóa thành công.', ], ], ], 'common' => [ 'custom-attributes' => [ 'add-more' => 'Thêm Nữa', 'address' => 'Địa Chỉ', 'city' => 'Thành Phố', 'contact' => 'Số Liên Lạc', 'country' => 'Quốc Gia', 'email' => 'Email', 'home' => 'Nhà', 'postcode' => 'Mã Bưu Chính', 'save' => 'Lưu', 'select' => 'Chọn', 'select-country' => 'Chọn Quốc Gia', 'select-state' => 'Chọn Bang', 'state' => 'Bang', 'update-contact-title' => 'Cập Nhật Số Liên Lạc', 'update-emails-title' => 'Cập Nhật Email Liên Hệ', 'work' => 'Công Việc', ], ], 'leads' => [ 'create-success' => 'Tạo khách hàng tiềm năng thành công.', 'update-success' => 'Cập nhật khách hàng tiềm năng thành công.', 'update-failed' => 'Leads can not be deleted.', 'destroy-success' => 'Xóa khách hàng tiềm năng thành công.', 'destroy-failed' => 'Không thể xóa khách hàng tiềm năng.', 'file' => [ 'data-not-found' => 'Không tìm thấy dữ liệu.', 'empty-content' => 'Nội dung PDF trống hoặc không thể trích xuất.', 'failed-extract' => 'Không thể trích xuất văn bản từ tệp.', 'insufficient-info' => 'Do dữ liệu không đủ, chúng tôi không thể xử lý yêu cầu của bạn vào lúc này.', 'invalid-base64' => 'Định dạng base64 không hợp lệ.', 'invalid-format' => 'Định dạng JSON không hợp lệ.', 'invalid-response' => 'Định dạng phản hồi AI không hợp lệ.', 'missing-api-key' => 'Thiếu khóa API hoặc cấu hình mô hình.', 'not-found' => 'Không tìm thấy tệp.', 'recursive-call' => 'Phát hiện cuộc gọi đệ quy.', 'text-generation-failed' => 'Trích xuất văn bản thất bại. Tệp có thể trống hoặc không đọc được.', ], 'index' => [ 'title' => 'Khách Hàng Tiềm Năng', 'create-btn' => 'Tạo Khách Hàng Tiềm Năng', 'datagrid' => [ 'id' => 'ID', 'sales-person' => 'Nhân Viên Kinh Doanh', 'subject' => 'Chủ Đề', 'source' => 'Nguồn', 'lead-value' => 'Giá Trị Khách Hàng', 'lead-type' => 'Loại Khách Hàng', 'tag-name' => 'Tên Thẻ', 'contact-person' => 'Người Liên Hệ', 'stage' => 'Giai Đoạn', 'rotten-lead' => 'Khách Hàng Tiềm Năng Hết Hạn', 'date-to' => 'Ngày Đến', 'created-at' => 'Tạo Vào Lúc', 'no' => 'Không', 'yes' => 'Có', 'delete' => 'Xóa', 'mass-delete' => 'Xóa Hàng Loạt', 'mass-update' => 'Cập Nhật Hàng Loạt', ], 'kanban' => [ 'rotten-days' => 'Khách hàng tiềm năng đã hết hạn trong :days ngày', 'empty-list' => 'Danh sách khách hàng tiềm năng của bạn trống', 'empty-list-description' => 'Tạo một khách hàng tiềm năng để tổ chức các mục tiêu của bạn.', 'create-lead-btn' => 'Tạo Khách Hàng Tiềm Năng', 'columns' => [ 'contact-person' => 'Người Liên Hệ', 'id' => 'ID', 'lead-type' => 'Loại Khách Hàng', 'lead-value' => 'Giá Trị Khách Hàng', 'sales-person' => 'Nhân Viên Kinh Doanh', 'source' => 'Nguồn', 'title' => 'Tiêu Đề', 'tags' => 'Thẻ', 'expected-close-date' => 'Ngày Dự Kiến Đóng', 'created-at' => 'Tạo Vào Lúc', ], 'toolbar' => [ 'search' => [ 'title' => 'Tìm kiếm theo tiêu đề', ], 'filters' => [ 'apply-filters' => 'Áp Dụng Bộ Lọc', 'clear-all' => 'Xóa Tất Cả', 'filter' => 'Bộ Lọc', 'filters' => 'Bộ Lọc', 'from' => 'Từ', 'select' => 'Chọn', 'to' => 'Đến', ], ], ], 'view-switcher' => [ 'all-pipelines' => 'Tất Cả Các Quy Trình', 'create-new-pipeline' => 'Tạo Quy Trình Mới', ], 'upload' => [ 'create-lead' => 'Tạo Khách Hàng Tiềm Năng Bằng AI', 'file' => 'Tải tệp lên', 'file-info' => 'Chỉ chấp nhận các tệp định dạng pdf, bmp, jpg, jpeg, png.', 'file-required' => 'Vui lòng chọn ít nhất một tệp hợp lệ để tiếp tục.', 'save-btn' => 'Lưu', 'upload-file' => 'Tải lên tệp', ], ], 'create' => [ 'title' => 'Tạo Khách Hàng Tiềm Năng', 'save-btn' => 'Lưu', 'details' => 'Chi Tiết', 'details-info' => 'Nhập Thông Tin Cơ Bản Của Khách Hàng Tiềm Năng', 'contact-person' => 'Người Liên Hệ', 'contact-info' => 'Thông Tin Về Người Liên Hệ', 'products' => 'Sản Phẩm', 'products-info' => 'Thông Tin Về Sản Phẩm', ], 'edit' => [ 'title' => 'Chỉnh Sửa Khách Hàng Tiềm Năng', 'save-btn' => 'Lưu', 'details' => 'Chi Tiết', 'details-info' => 'Nhập Thông Tin Cơ Bản Của Khách Hàng Tiềm Năng', 'contact-person' => 'Người Liên Hệ', 'contact-info' => 'Thông Tin Về Người Liên Hệ', 'products' => 'Sản Phẩm', 'products-info' => 'Thông Tin Về Sản Phẩm', ], 'common' => [ 'contact' => [ 'name' => 'Tên', 'email' => 'Email', 'contact-number' => 'Số Liên Lạc', 'organization' => 'Tổ Chức', ], 'products' => [ 'product-name' => 'Tên Sản Phẩm', 'quantity' => 'Số Lượng', 'price' => 'Giá', 'amount' => 'Tổng Tiền', 'action' => 'Hành Động', 'add-more' => 'Thêm Nữa', 'total' => 'Tổng Cộng', ], ], 'view' => [ 'title' => 'Khách Hàng Tiềm Năng: :title', 'rotten-days' => ':days Ngày', 'tabs' => [ 'description' => 'Mô Tả', 'products' => 'Sản Phẩm', 'quotes' => 'Báo Giá', ], 'attributes' => [ 'title' => 'Về Khách Hàng Tiềm Năng', ], 'quotes' => [ 'subject' => 'Chủ Đề', 'expired-at' => 'Hết Hạn Vào', 'sub-total' => 'Tạm Tính', 'discount' => 'Giảm Giá', 'tax' => 'Thuế', 'adjustment' => 'Điều Chỉnh', 'grand-total' => 'Tổng Cộng', 'delete' => 'Xóa', 'edit' => 'Chỉnh Sửa', 'download' => 'Tải Xuống', 'destroy-success' => 'Xóa báo giá thành công.', 'empty-title' => 'Không Có Báo Giá', 'empty-info' => 'Không Có Báo Giá Cho Khách Hàng Tiềm Năng Này', 'add-btn' => 'Thêm Báo Giá', ], 'products' => [ 'product-name' => 'Tên Sản Phẩm', 'quantity' => 'Số Lượng', 'price' => 'Giá', 'amount' => 'Tổng Tiền', 'action' => 'Hành Động', 'add-more' => 'Thêm Nữa', 'total' => 'Tổng Cộng', 'empty-title' => 'Không Có Sản Phẩm', 'empty-info' => 'Không Có Sản Phẩm Cho Khách Hàng Tiềm Năng Này', 'add-product' => 'Thêm Sản Phẩm', ], 'persons' => [ 'title' => 'Về Người Liên Hệ', 'job-title' => ':job_title tại :organization', ], 'stages' => [ 'won' => 'Thắng', 'lost' => 'Thua', 'need-more-info' => 'Cần Thêm Thông Tin', 'closed-at' => 'Đóng Vào', 'won-value' => 'Giá Trị Thắng', 'lost-reason' => 'Lý Do Thua', 'save-btn' => 'Lưu', ], 'tags' => [ 'create-success' => 'Tạo thẻ thành công.', 'destroy-success' => 'Xóa thẻ thành công.', ], ], ], 'configuration' => [ 'index' => [ 'back' => 'Quay lại', 'delete' => 'Xóa', 'save-btn' => 'Lưu Cấu hình', 'save-success' => 'Cấu hình đã được lưu thành công.', 'search' => 'Tìm kiếm', 'select-country' => 'Chọn Quốc gia', 'select-state' => 'Chọn Bang', 'title' => 'Cấu hình', 'general' => [ 'title' => 'Chung', 'info' => 'Cấu hình chung', 'general' => [ 'title' => 'Chung', 'info' => 'Cập nhật cài đặt chung của bạn tại đây.', 'locale-settings' => [ 'title' => 'Cài đặt ngôn ngữ', 'title-info' => 'Định nghĩa ngôn ngữ được sử dụng trong giao diện người dùng, như tiếng Ả Rập (ar), tiếng Anh (en), tiếng Tây Ban Nha (es), tiếng Ba Tư (fa) và tiếng Thổ Nhĩ Kỳ (tr).', ], 'admin-logo' => [ 'logo-image' => 'Hình ảnh Logo', 'title' => 'Logo Quản trị', 'title-info' => 'Cấu hình hình ảnh logo cho bảng điều khiển quản trị của bạn.', ], ], 'settings' => [ 'title' => 'Settings', 'info' => 'Update your settings here.', 'footer' => [ 'info' => 'We can configure the powered by section here.', 'powered-by' => 'Powered by text editor', 'title' => 'Powered by Section Configurations', ], 'menu' => [ 'activities' => 'Activities', 'configuration' => 'Configuration', 'contacts' => 'Contacts', 'dashboard' => 'Dashboard', 'draft' => 'Draft', 'inbox' => 'Inbox', 'info' => 'We can configure the menu items name here.', 'leads' => 'Leads', 'mail' => 'Mail', 'organizations' => 'Organizations', 'outbox' => 'Outbox', 'persons' => 'Persons', 'products' => 'Products', 'quotes' => 'Quotes', 'sent' => 'Sent', 'settings' => 'Settings', 'title' => 'Menu Item Configurations', 'trash' => 'Trash', ], 'menu-color' => [ 'brand-color' => 'Brand Color', 'info' => 'We can change the menu items colors here.', 'title' => 'Menu Item Color Configurations', ], ], ], 'email' => [ 'title' => 'Cài đặt Email', 'info' => 'Cấu hình email cho ứng dụng.', 'imap' => [ 'title' => 'Cài đặt IMAP', 'info' => 'Cấu hình email IMAP để nhận email.', 'account' => [ 'title' => 'Tài khoản IMAP', 'title-info' => 'Cấu hình cài đặt tài khoản IMAP của bạn tại đây.', 'host' => 'Máy chủ', 'port' => 'Cổng', 'encryption' => 'Loại mã hóa', 'validate-cert' => 'Xác thực chứng chỉ SSL', 'username' => 'Tên người dùng IMAP', 'password' => 'Mật khẩu IMAP', ], ], ], 'magic-ai' => [ 'title' => 'Magic AI', 'info' => 'Cấu hình Magic AI cho ứng dụng.', 'settings' => [ 'api-key' => 'Khóa API', 'api-key-info' => 'Nhớ sử dụng khóa API OpenRouter cho mỗi mô hình. Đây là một bước đơn giản để tăng cường bảo mật và hiệu suất.', 'enable' => 'Kích hoạt', 'info' => 'Nâng cao trải nghiệm Magic AI của bạn với Khóa API OpenRouter. Tích hợp ngay bây giờ để có một cuộc phiêu lưu AI liền mạch và cá nhân hóa chỉ dành cho bạn! Dễ dàng tùy chỉnh cài đặt và kiểm soát hành trình AI của bạn.', 'other' => 'Mô hình khác', 'other-model' => 'Đối với các mô hình khác, sử dụng ID Mô hình từ OpenRouter.', 'doc-generation' => 'Tạo DOC', 'doc-generation-info' => 'Bật tính năng Tạo DOC để tự động trích xuất dữ liệu từ các tệp DOC và chuyển đổi chúng sang định dạng văn bản. Nâng cao năng suất và hiệu quả công việc bằng cách bật tính năng này để đơn giản hóa quy trình làm việc của bạn.', 'title' => 'General Settings', 'models' => [ 'deepseek-r1' => 'Deepseek R1 Distill-llama-8b', 'gemini-2-0-flash-001' => 'Gemini 2.0 flash-001', 'gpt-4o' => 'GPT-4.0', 'gpt-4o-mini' => 'GPT-4.0 mini', 'grok-2-1212' => 'Grok 2.12', 'llama-3-2-3b-instruct' => 'Llama 3.2 3b Instruct', 'title' => 'Mô hình', ], ], ], ], ], 'dashboard' => [ 'index' => [ 'title' => 'Bảng Điều Khiển', 'start-date' => 'Start Date', 'end-date' => 'End Date', 'revenue' => [ 'lost-revenue' => 'Doanh Thu Bị Mất', 'won-revenue' => 'Doanh Thu Đã Đạt', ], 'over-all' => [ 'average-lead-value' => 'Giá Trị Lead Trung Bình', 'total-leads' => 'Tổng Số Lead', 'average-leads-per-day' => 'Số Lead Trung Bình Mỗi Ngày', 'total-quotations' => 'Tổng Số Báo Giá', 'total-persons' => 'Tổng Số Người Liên Hệ', 'total-organizations' => 'Tổng Số Tổ Chức', ], 'total-leads' => [ 'title' => 'Leads', 'total' => 'Tổng Số Lead', 'won' => 'Lead Đã Đạt', 'lost' => 'Lead Bị Mất', ], 'revenue-by-sources' => [ 'title' => 'Doanh Thu Theo Nguồn', 'empty-title' => 'Không Có Dữ Liệu', 'empty-info' => 'Không có dữ liệu cho khoảng thời gian được chọn', ], 'revenue-by-types' => [ 'title' => 'Doanh Thu Theo Loại', 'empty-title' => 'Không Có Dữ Liệu', 'empty-info' => 'Không có dữ liệu cho khoảng thời gian được chọn', ], 'top-selling-products' => [ 'title' => 'Sản Phẩm Bán Chạy Nhất', 'empty-title' => 'Không Tìm Thấy Sản Phẩm', 'empty-info' => 'Không có sản phẩm nào cho khoảng thời gian được chọn', ], 'top-persons' => [ 'title' => 'Người Liên Hệ Hàng Đầu', 'empty-title' => 'Không Tìm Thấy Người Liên Hệ', 'empty-info' => 'Không có người liên hệ nào cho khoảng thời gian được chọn', ], 'open-leads-by-states' => [ 'title' => 'Khách hàng tiềm năng mở theo giai đoạn', 'empty-title' => 'Không Có Dữ Liệu', 'empty-info' => 'Không có dữ liệu cho khoảng thời gian được chọn', ], ], ], 'layouts' => [ 'app-version' => 'Phiên Bản : :version', 'dashboard' => 'Bảng Điều Khiển', 'leads' => 'Leads', 'quotes' => 'Báo Giá', 'quote' => 'Báo Giá', 'mail' => [ 'title' => 'Thư', 'compose' => 'Soạn Thư', 'inbox' => 'Hộp Thư Đến', 'draft' => 'Thư Nháp', 'outbox' => 'Hộp Thư Đi', 'sent' => 'Đã Gửi', 'trash' => 'Thùng Rác', 'setting' => 'Cài Đặt', ], 'activities' => 'Hoạt Động', 'contacts' => 'Liên Hệ', 'persons' => 'Người Liên Hệ', 'person' => 'Người Liên Hệ', 'organizations' => 'Tổ Chức', 'organization' => 'Tổ Chức', 'products' => 'Sản Phẩm', 'product' => 'Sản Phẩm', 'settings' => 'Cài Đặt', 'user' => 'Người Dùng', 'user-info' => 'Quản lý tất cả người dùng và quyền hạn của họ trong CRM, những gì họ được phép làm.', 'groups' => 'Nhóm', 'groups-info' => 'Thêm, chỉnh sửa hoặc xóa nhóm khỏi CRM', 'roles' => 'Vai Trò', 'role' => 'Vai Trò', 'roles-info' => 'Thêm, chỉnh sửa hoặc xóa vai trò khỏi CRM', 'users' => 'Người Dùng', 'users-info' => 'Thêm, chỉnh sửa hoặc xóa người dùng khỏi CRM', 'lead' => 'Lead', 'lead-info' => 'Quản lý tất cả các cài đặt liên quan đến leads trong CRM', 'pipelines' => 'Pipelines', 'pipelines-info' => 'Thêm, chỉnh sửa hoặc xóa pipelines khỏi CRM', 'sources' => 'Nguồn', 'sources-info' => 'Thêm, chỉnh sửa hoặc xóa nguồn khỏi CRM', 'types' => 'Loại', 'types-info' => 'Thêm, chỉnh sửa hoặc xóa loại khỏi CRM', 'automation' => 'Tự Động Hóa', 'automation-info' => 'Quản lý tất cả các cài đặt liên quan đến tự động hóa trong CRM', 'attributes' => 'Thuộc Tính', 'attribute' => 'Thuộc Tính', 'attributes-info' => 'Thêm, chỉnh sửa hoặc xóa thuộc tính khỏi CRM', 'email-templates' => 'Mẫu Email', 'email' => 'Email', 'email-templates-info' => 'Thêm, chỉnh sửa hoặc xóa mẫu email khỏi CRM', 'events' => 'Sự kiện', 'events-info' => 'Thêm, chỉnh sửa hoặc xóa sự kiện từ CRM', 'campaigns' => 'Chiến dịch', 'campaigns-info' => 'Thêm, chỉnh sửa hoặc xóa chiến dịch từ CRM', 'workflows' => 'Quy Trình', 'workflows-info' => 'Thêm, chỉnh sửa hoặc xóa quy trình khỏi CRM', 'webhooks' => 'Webhook', 'webhooks-info' => 'Thêm, chỉnh sửa hoặc xóa webhook từ CRM', 'other-settings' => 'Cài Đặt Khác', 'other-settings-info' => 'Quản lý tất cả các cài đặt khác trong CRM', 'tags' => 'Thẻ', 'tags-info' => 'Thêm, chỉnh sửa hoặc xóa thẻ khỏi CRM', 'my-account' => 'Tài Khoản Của Tôi', 'sign-out' => 'Đăng Xuất', 'back' => 'Quay lại', 'name' => 'Tên', 'configuration' => 'Cấu hình', 'howdy' => 'Xin chào!', 'warehouses' => 'Kho hàng', 'warehouse' => 'Kho hàng', 'warehouses-info' => 'Thêm, chỉnh sửa hoặc xóa kho hàng từ CRM', 'inventory' => 'Hàng tồn kho', 'inventory-info' => 'Quản lý tất cả các cài đặt liên quan đến hàng tồn kho trong CRM', 'data_transfer' => 'Data Transfer', 'data_transfer_info' => 'Manage persons, products and leads data transfer related settings in the CRM', ], 'user' => [ 'account' => [ 'name' => 'Tên', 'email' => 'Email', 'password' => 'Mật Khẩu', 'my_account' => 'Tài Khoản Của Tôi', 'update_details' => 'Cập Nhật Thông Tin', 'current_password' => 'Mật Khẩu Hiện Tại', 'confirm_password' => 'Xác Nhận Mật Khẩu', 'password-match' => 'Mật khẩu hiện tại không khớp.', 'account-save' => 'Thay đổi tài khoản đã được lưu thành công.', 'permission-denied' => 'Từ Chối Quyền Truy Cập', 'remove-image' => 'Xóa Hình Ảnh', 'upload_image_pix' => 'Tải Lên Ảnh Hồ Sơ (100px x 100px)', 'upload_image_format' => 'Định Dạng PNG hoặc JPG', 'image_upload_message' => 'Chỉ chấp nhận hình ảnh (.jpeg, .jpg, .png, ..).', ], ], 'emails' => [ 'common' => [ 'dear' => 'Kính gửi :name', 'cheers' => 'Trân trọng,
Đội ngũ :app_name', 'user' => [ 'dear' => 'Kính gửi :username', 'create-subject' => 'Bạn đã được thêm làm thành viên.', 'create-body' => 'Chúc mừng! Bạn đã trở thành thành viên của đội ngũ chúng tôi.', 'forget-password' => [ 'subject' => 'Khách hàng yêu cầu đặt lại mật khẩu', 'dear' => 'Kính gửi :username', 'reset-password' => 'Đặt Lại Mật Khẩu', 'info' => 'Bạn nhận được email này vì chúng tôi đã nhận được yêu cầu đặt lại mật khẩu cho tài khoản của bạn.', 'final-summary' => 'Nếu bạn không yêu cầu đặt lại mật khẩu, không cần thực hiện thêm hành động nào.', 'thanks' => 'Cảm ơn!', ], ], ], ], 'validations' => [ 'message' => [ 'decimal' => 'The :attribute must be a decimal.', ], ], 'errors' => [ 'dashboard' => 'Bảng điều khiển', 'go-back' => 'Quay lại', 'support' => 'Nếu sự cố vẫn tiếp diễn, vui lòng liên hệ với chúng tôi tại :email để được hỗ trợ.', '404' => [ 'description' => 'Rất tiếc! Trang bạn đang tìm kiếm hiện không có ở đây. Có vẻ như chúng tôi không thể tìm thấy những gì bạn đang tìm kiếm.', 'title' => '404 Không Tìm Thấy Trang', ], '401' => [ 'description' => 'Rất tiếc! Có vẻ như bạn không được phép truy cập vào trang này. Có vẻ bạn đang thiếu thông tin xác thực cần thiết.', 'title' => '401 Không Được Phép', ], '403' => [ 'description' => 'Rất tiếc! Trang này bị hạn chế. Có vẻ bạn không có quyền truy cập vào nội dung này.', 'title' => '403 Cấm Truy Cập', ], '500' => [ 'description' => 'Rất tiếc! Đã xảy ra sự cố. Có vẻ như chúng tôi đang gặp khó khăn trong việc tải trang mà bạn đang tìm kiếm.', 'title' => '500 Lỗi Máy Chủ Nội Bộ', ], '503' => [ 'description' => 'Rất tiếc! Có vẻ chúng tôi đang tạm ngừng để bảo trì. Vui lòng quay lại sau.', 'title' => '503 Dịch Vụ Không Khả Dụng', ], ], 'export' => [ 'csv' => 'CSV', 'download' => 'Tải Xuống', 'export' => 'Xuất', 'no-records' => 'Không có bản ghi nào được tìm thấy.', 'xls' => 'XLS', 'xlsx' => 'XLSX', ], ]; ================================================ FILE: packages/Webkul/Admin/src/Resources/views/activities/datagrid/is-done.blade.php ================================================ ================================================ FILE: packages/Webkul/Admin/src/Resources/views/activities/edit.blade.php ================================================ @lang('admin::app.activities.edit.title') {!! view_render_event('admin.activities.edit.form.before') !!}
@lang('admin::app.activities.edit.title')
{!! view_render_event('admin.activities.edit.save_button.before') !!} {!! view_render_event('admin.activities.edit.save_button.after') !!}
{!! view_render_event('admin.activities.edit.form_controls.before') !!}
@lang('admin::app.activities.edit.schedule_from')
@lang('admin::app.activities.edit.schedule_to')
@lang('admin::app.activities.edit.comment') @lang('admin::app.activities.edit.participants')
@lang('admin::app.activities.edit.lead') {!! view_render_event('admin.activities.edit.form_controls.after') !!}
{!! view_render_event('admin.activities.edit.accordion.general.before') !!}

@lang('admin::app.activities.edit.general')

@lang('admin::app.activities.edit.title') @lang('admin::app.activities.edit.type') @lang('admin::app.activities.edit.location')
{!! view_render_event('admin.activities.edit.accordion.general.after') !!}
{!! view_render_event('admin.activities.edit.form.after') !!} @pushOnce('scripts') @endPushOnce
================================================ FILE: packages/Webkul/Admin/src/Resources/views/activities/index.blade.php ================================================ @lang('admin::app.activities.index.title') {!! view_render_event('admin.activities.index.activities.before') !!}
@lang('admin::app.activities.index.title')
@if ( request()->get('view-type') == 'table' || ! request()->has('view-type') ) @endif
{!! view_render_event('admin.activities.index.activities.after') !!} @pushOnce('scripts') @endPushOnce @pushOnce('styles') @endPushOnce
================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/accordion/index.blade.php ================================================ @props([ 'isActive' => true, ])
merge(['class' => 'box-shadow rounded-lg border border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-900']) }}> @isset($header) @endisset @isset($content) @endisset
@pushOnce('scripts') @endPushOnce ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/activities/actions/activity/participants.blade.php ================================================ {!! view_render_event('admin.components.activities.actions.activity.participants.before') !!} {!! view_render_event('admin.components.activities.actions.activity.participants.after') !!} @pushOnce('scripts') @endPushOnce ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/activities/actions/activity.blade.php ================================================ @props([ 'entity' => null, 'entityControlName' => null, ])
{!! view_render_event('admin.components.activities.actions.activity.create_btn.before') !!} {!! view_render_event('admin.components.activities.actions.activity.create_btn.after') !!} {!! view_render_event('admin.components.activities.actions.activity.before') !!} {!! view_render_event('admin.components.activities.actions.activity.after') !!}
@pushOnce('scripts') @endPushOnce ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/activities/actions/file.blade.php ================================================ @props([ 'entity' => null, 'entityControlName' => null, ])
{!! view_render_event('admin.components.activities.actions.file.create_btn.before') !!} {!! view_render_event('admin.components.activities.actions.file.create_btn.after') !!} {!! view_render_event('admin.components.activities.actions.file.before') !!} {!! view_render_event('admin.components.activities.actions.file.after') !!}
@pushOnce('scripts') @endPushOnce ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/activities/actions/mail.blade.php ================================================ @props([ 'entity' => null, 'entityControlName' => null, ])
{!! view_render_event('admin.components.activities.actions.mail.create_btn.before') !!} {!! view_render_event('admin.components.activities.actions.mail.create_btn.after') !!} {!! view_render_event('admin.components.activities.actions.mail.before') !!} {!! view_render_event('admin.components.activities.actions.mail.after') !!}
@pushOnce('scripts') @endPushOnce ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/activities/actions/note.blade.php ================================================ @props([ 'entity' => null, 'entityControlName' => null, ])
{!! view_render_event('admin.components.activities.actions.note.create_btn.before') !!} {!! view_render_event('admin.components.activities.actions.note.create_btn.after') !!} {!! view_render_event('admin.components.activities.actions.note.before') !!} {!! view_render_event('admin.components.activities.actions.note.after') !!}
@pushOnce('scripts') @endPushOnce ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/activities/index.blade.php ================================================ @props([ 'endpoint', 'emailDetachEndpoint' => null, 'activeType' => 'all', 'types' => null, 'extraTypes' => null, ]) {!! view_render_event('admin.components.activities.before') !!} @foreach ($extraTypes ?? [] as $type) @endforeach {!! view_render_event('admin.components.activities.after') !!} @pushOnce('scripts') @endPushOnce ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/attachments/index.blade.php ================================================ @props([ 'name' => 'attachments', 'validations' => null, 'uploadedAttachments' => [], 'allowMultiple' => false, 'hideButton' => false, ]) @pushOnce('scripts') @endPushOnce ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/attributes/edit/address.blade.php ================================================ @if (isset($attribute)) @endif @pushOnce('scripts') @endPushOnce ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/attributes/edit/boolean.blade.php ================================================ code) ?: $value ?> ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/attributes/edit/checkbox.blade.php ================================================ @php $options = $attribute->lookup_type ? app('Webkul\Attribute\Repositories\AttributeRepository')->getLookUpOptions($attribute->lookup_type) : $attribute->options()->orderBy('sort_order')->get(); $selectedOption = old($attribute->code, $value); $selectedOption = is_array($selectedOption) ? $selectedOption : explode(',', $selectedOption); @endphp @foreach ($options as $option) @endforeach ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/attributes/edit/date.blade.php ================================================ @php if (! empty($value)) { if ($value instanceof \Carbon\Carbon) { $value = $value->format('Y-m-d'); } elseif (is_string($value)) { $value = \Carbon\Carbon::parse($value)->format('Y-m-d'); } } $value = old($attribute->code, $value); @endphp ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/attributes/edit/datetime.blade.php ================================================ ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/attributes/edit/email.blade.php ================================================ @if (isset($attribute))
@lang("admin::app.common.custom-attributes.add-more")
@endif @pushOnce('scripts') @endPushOnce ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/attributes/edit/file.blade.php ================================================
@if ($value)
@endif
@if ($value)
@endif ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/attributes/edit/image.blade.php ================================================
@if ($value) {{ $attribute->code }} @endif
@if ($value)
@endif ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/attributes/edit/index.blade.php ================================================ @props([ 'attribute' => '', 'value' => '', 'validations' => '', ]) @switch($attribute->type) @case('text') @break @case('email') @break @case('phone') @break @case('lookup') @break @case('select') @break @case('multiselect') @break @case('price') @break @case('image') @break @case('file') @break @case('textarea') @break @case('address') @break @case('date') @break @case('datetime') @break @case('boolean') @break @case('checkbox') @break @endswitch ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/attributes/edit/lookup.blade.php ================================================ @if (isset($attribute)) @php $lookUpEntityData = app('Webkul\Attribute\Repositories\AttributeRepository')->getLookUpEntity($attribute->lookup_type, old($attribute->code) ?: $value); @endphp
@lang('admin::app.components.attributes.lookup.click-to-add')
@endif @pushOnce('scripts') @endPushOnce ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/attributes/edit/multiselect.blade.php ================================================ @php $options = $attribute->lookup_type ? app('Webkul\Attribute\Repositories\AttributeRepository')->getLookUpOptions($attribute->lookup_type) : $attribute->options()->orderBy('sort_order')->get(); $selectedOption = old($attribute->code) ?: $value; @endphp ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/attributes/edit/phone.blade.php ================================================ @if (isset($attribute))
@lang("admin::app.common.custom-attributes.add-more")
@endif @pushOnce('scripts') @endPushOnce ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/attributes/edit/price.blade.php ================================================ @if (isset($attribute)) @endif @pushOnce('scripts') @endPushOnce ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/attributes/edit/select.blade.php ================================================ @php $options = $attribute->lookup_type ? app('Webkul\Attribute\Repositories\AttributeRepository')->getLookUpOptions($attribute->lookup_type) : $attribute->options()->orderBy('sort_order')->get(); @endphp @foreach ($options as $option) @endforeach ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/attributes/edit/text.blade.php ================================================ ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/attributes/edit/textarea.blade.php ================================================ ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/attributes/index.blade.php ================================================ @foreach ($customAttributes as $attribute) @php $validations = []; if ($attribute->is_required) { $validations[] = 'required'; } if ($attribute->type == 'price') { $validations[] = 'decimal'; } $validations[] = $attribute->validation; $validations = implode('|', array_filter($validations)); @endphp {{ $attribute->name }} @if ($attribute->type == 'price') ({{ core()->currencySymbol(config('app.currency')) }}) @endif @if (isset($attribute)) @endif @endforeach ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/attributes/view/address.blade.php ================================================ ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/attributes/view/boolean.blade.php ================================================ ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/attributes/view/checkbox.blade.php ================================================ @php $options = $attribute->lookup_type ? app('Webkul\Attribute\Repositories\AttributeRepository')->getLookUpOptions($attribute->lookup_type) : $attribute->options()->orderBy('sort_order')->get(); $selectedOption = old($attribute->code) ?: $value; @endphp ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/attributes/view/date.blade.php ================================================ @php if (! empty($value)) { if ($value instanceof \Carbon\Carbon) { $value = $value->format('Y-m-d'); } elseif (is_string($value)) { $value = \Carbon\Carbon::parse($value)->format('Y-m-d'); } } @endphp ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/attributes/view/datetime.blade.php ================================================ ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/attributes/view/email.blade.php ================================================ ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/attributes/view/file.blade.php ================================================ ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/attributes/view/image.blade.php ================================================ ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/attributes/view/lookup.blade.php ================================================ @php $lookUpEntity = app('Webkul\Attribute\Repositories\AttributeRepository')->getLookUpEntity($attribute->lookup_type, $value); @endphp ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/attributes/view/multiselect.blade.php ================================================ @php $options = $attribute->lookup_type ? app('Webkul\Attribute\Repositories\AttributeRepository')->getLookUpOptions($attribute->lookup_type) : $attribute->options()->orderBy('sort_order')->get(); $selectedOption = old($attribute->code) ?: $value; @endphp ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/attributes/view/phone.blade.php ================================================ ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/attributes/view/price.blade.php ================================================ ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/attributes/view/select.blade.php ================================================ @php $options = $attribute->lookup_type ? app('Webkul\Attribute\Repositories\AttributeRepository')->getLookUpOptions($attribute->lookup_type) : $attribute->options()->orderBy('sort_order')->get(); @endphp ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/attributes/view/text.blade.php ================================================ ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/attributes/view/textarea.blade.php ================================================ ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/attributes/view.blade.php ================================================ @props([ 'customAttributes' => [], 'entity' => null, 'allowEdit' => false, 'url' => null, ])
@foreach ($customAttributes as $attribute) @if (view()->exists($typeView = 'admin::components.attributes.view.' . $attribute->type))
{{ $attribute->name }}
@include ($typeView, [ 'attribute' => $attribute, 'value' => isset($entity) ? $entity[$attribute->code] : null, 'allowEdit' => $allowEdit, 'url' => $url, ])
@endif @endforeach
================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/avatar/index.blade.php ================================================
@pushOnce('scripts') @endPushOnce ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/breadcrumbs/index.blade.php ================================================ @props([ 'name' => '', 'entity' => null, 'route' => null, ])
@if($route) {{ Breadcrumbs::view('admin::partials.breadcrumbs', $name, $route, $entity) }} @else {{ Breadcrumbs::view('admin::partials.breadcrumbs', $name, $entity) }} @endif
================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/button/index.blade.php ================================================ @pushOnce('scripts') @endPushOnce ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/charts/bar.blade.php ================================================ @pushOnce('scripts') @endPushOnce ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/charts/doughnut.blade.php ================================================ @pushOnce('scripts') @endPushOnce ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/charts/line.blade.php ================================================ @pushOnce('scripts') @endPushOnce ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/datagrid/export/index.blade.php ================================================
@lang('admin::app.export.export')
@pushOnce('scripts') @endPushOnce ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/datagrid/export/temp.blade.php ================================================ @foreach ($columns as $column) @endforeach @foreach ($records as $record) @foreach($columns as $column) @if ($closure = $column->getClosure()) @else @endif @endforeach @endforeach
{{ $column->getLabel() }}
{!! $closure($record) !!}{{ $record->{$column->getIndex()} }}
================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/datagrid/index.blade.php ================================================ @props([ 'isMultiRow' => false, 'toolbarLeftBefore' => null, 'toolbarLeftAfter' => null, 'toolbarRightBefore' => null, 'toolbarRightAfter' => null, ]) {{ $slot }} @pushOnce('scripts') @endPushOnce ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/datagrid/table.blade.php ================================================ @props(['isMultiRow' => false]) {{ $slot }} @pushOnce('scripts') @endpushOnce ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/datagrid/toolbar/filter.blade.php ================================================ {{ $slot }} @pushOnce('scripts') @endpushOnce ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/datagrid/toolbar/mass-action.blade.php ================================================ {{ $slot }} @pushOnce('scripts') @endPushOnce ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/datagrid/toolbar/pagination.blade.php ================================================ {{ $slot }} @pushOnce('scripts') @endPushOnce ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/datagrid/toolbar/search.blade.php ================================================ {{ $slot }} @pushOnce('scripts') @endPushOnce ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/datagrid/toolbar.blade.php ================================================ ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/drawer/index.blade.php ================================================ @props([ 'isActive' => false, 'position' => 'right', 'width' => '500px', ]) @isset($toggle) @endisset @isset($header) @endisset @isset($content) @endisset @isset($footer) @endisset @pushOnce('scripts') @endPushOnce ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/dropdown/index.blade.php ================================================ @props(['position' => 'bottom-left']) merge(['class' => 'relative']) }}> @isset($toggle) {{ $toggle }} @endisset @isset($content) @endisset @isset($menu) @endisset @pushOnce('scripts') @endPushOnce ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/dropdown/menu/item.blade.php ================================================
  • merge(['class' => 'cursor-pointer px-5 py-2 text-sm text-gray-800 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-950 ']) }}> {{ $slot }}
  • ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/example.blade.php ================================================ @php $bgColors = ['bg-orange-100', 'bg-red-100', 'bg-green-100', 'bg-blue-100', 'bg-purple-100']; $textColors = ['text-orange-800', 'text-red-800', 'text-green-800', 'text-blue-800', 'text-purple-800']; @endphp @foreach ($bgColors as $bgColor)
    @endforeach @foreach ($textColors as $textColor)
    @endforeach ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/flash-group/index.blade.php ================================================ @pushOnce('scripts') @endpushOnce ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/flash-group/item.blade.php ================================================ @pushOnce('scripts') @endpushOnce ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/flat-picker/date.blade.php ================================================ {{ $slot }} @pushOnce('scripts') @endPushOnce ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/flat-picker/datetime.blade.php ================================================ {{ $slot }} @pushOnce('scripts') @endPushOnce ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/form/control-group/control.blade.php ================================================ @props([ 'type' => 'text', 'name' => '', ]) @switch($type) @case('hidden') @case('text') @case('email') @case('password') @case('number') only(['name', ':name', 'value', ':value', 'v-model', 'rules', ':rules', 'label', ':label']) }} name="{{ $name }}" > except(['value', ':value', 'v-model', 'rules', ':rules', 'label', ':label'])->merge(['class' => 'w-full rounded border border-gray-200 px-2.5 py-2 text-sm font-normal text-gray-800 transition-all hover:border-gray-400 focus:border-gray-400 dark:border-gray-800 dark:bg-gray-900 dark:text-gray-300 dark:hover:border-gray-400 dark:focus:border-gray-400']) }} /> @break @case('price') only(['name', ':name', 'value', ':value', 'v-model', 'rules', ':rules', 'label', ':label']) }} name="{{ $name }}" >
    @if (isset($currency)) attributes->merge(['class' => 'py-2.5 text-gray-500 ltr:pl-4 rtl:pr-4']) }}> {{ $currency }} @else {{ config('app.currency') }} @endif except(['value', ':value', 'v-model', 'rules', ':rules', 'label', ':label'])->merge(['class' => 'w-full p-2.5 text-sm text-gray-600 dark:bg-gray-900 dark:text-gray-300']) }} />
    @break @case('file') only(['name', ':name', 'value', ':value', 'v-model', 'rules', ':rules', 'label', ':label']) }} name="{{ $name }}" > except(['value', ':value', 'v-model', 'rules', ':rules', 'label', ':label'])->merge(['class' => 'w-full dark:file:bg-gray-800 dark:file:dark:text-white rounded border border-gray-200 px-2.5 py-2 text-sm font-normal text-gray-800 transition-all hover:border-gray-400 focus:border-gray-400 dark:border-gray-800 dark:bg-gray-900 dark:text-gray-300 dark:hover:border-gray-400 dark:focus:border-gray-400']) }} @change="handleChange" @blur="handleBlur" /> @break @case('color') except('class') }} > except(['value'])->merge(['class' => 'w-full appearance-none rounded-md border text-sm text-gray-600 transition-all hover:border-gray-400 dark:text-gray-300 dark:hover:border-gray-400']) }} > @break @case('textarea') only(['name', ':name', 'value', ':value', 'v-model', 'rules', ':rules', 'label', ':label']) }} name="{{ $name }}" > @php $defaultAttributes = [ 'class' => 'w-full rounded border border-gray-200 px-2.5 py-2 text-sm font-normal text-gray-800 transition-all hover:border-gray-400 focus:border-gray-400 dark:border-gray-800 dark:bg-gray-900 dark:text-gray-300 dark:hover:border-gray-400 dark:focus:border-gray-400' ]; if ($attributes->get('tinymce', false) || $attributes->get(':tinymce', false)) { $defaultAttributes['id'] = $attributes->get(':id', 'id'); } @endphp @if ($attributes->get('tinymce', false) || $attributes->get(':tinymce', false)) @endif @break @case('date') only(['name', ':name', 'value', ':value', 'v-model', 'rules', ':rules', 'label', ':label'])->merge(['rules' => 'regex:^\d{4}-\d{2}-\d{2}$']) }} name="{{ $name }}" > except(['value', ':value', 'v-model', 'rules', ':rules', 'label', ':label'])->merge(['class' => 'w-full rounded border border-gray-200 px-2.5 py-2 text-sm font-normal text-gray-800 transition-all hover:border-gray-400 focus:border-gray-400 dark:border-gray-800 dark:bg-gray-900 dark:text-gray-300 dark:hover:border-gray-400 dark:focus:border-gray-400']) }} autocomplete="off" /> @break @case('datetime') only(['name', ':name', 'value', ':value', 'v-model', 'rules', ':rules', 'label', ':label'])->merge(['rules' => 'regex:^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$']) }} name="{{ $name }}" > except(['value', ':value', 'v-model', 'rules', ':rules', 'label', ':label'])->merge(['class' => 'w-full rounded border border-gray-200 px-2.5 py-2 text-sm font-normal text-gray-800 transition-all hover:border-gray-400 focus:border-gray-400 dark:border-gray-800 dark:bg-gray-900 dark:text-gray-300 dark:hover:border-gray-400 dark:focus:border-gray-400']) }} autocomplete="off" > @break @case('select') only(['name', ':name', 'value', ':value', 'v-model', 'rules', ':rules', 'label', ':label']) }} name="{{ $name }}" > @break @case('multiselect') except([])->merge(['class' => 'flex w-full flex-col rounded-md border bg-white px-3 py-2.5 text-sm font-normal text-gray-600 transition-all hover:border-gray-400 dark:border-gray-800 dark:bg-gray-900 dark:text-gray-300 dark:hover:border-gray-400']) }} name="{{ $name }}" multiple > {{ $slot }} @break @case('checkbox') @break @case('radio') @break @case('switch') @break @case('image') @break @case('inline') @break @case('custom') {{ $slot }} @break @case('tags') @break @endswitch @pushOnce('scripts') @endpushOnce ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/form/control-group/controls/inline/address.blade.php ================================================ @props([ 'allowEdit' => true, ]) except('value') }} :value='@json($attributes->get('value'))' :allow-edit="{{ $allowEdit ? 'true' : 'false' }}" >
    @pushOnce('scripts') @endPushOnce ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/form/control-group/controls/inline/boolean.blade.php ================================================ @props([ 'allowEdit' => true, ]) except('options') }} :allow-edit="{{ $allowEdit ? 'true' : 'false' }}" >
    @pushOnce('scripts') @endPushOnce ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/form/control-group/controls/inline/date.blade.php ================================================ @props([ 'allowEdit' => true, ])
    @pushOnce('scripts') @endPushOnce ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/form/control-group/controls/inline/datetime.blade.php ================================================ @props([ 'allowEdit' => true, ])
    @pushOnce('scripts') @endPushOnce ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/form/control-group/controls/inline/email.blade.php ================================================ @props([ 'allowEdit' => true, ]) except('value') }} :value={{ json_encode($attributes->get('value')) }} :allow-edit="{{ $allowEdit ? 'true' : 'false' }}" >
    @pushOnce('scripts') @endPushOnce ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/form/control-group/controls/inline/file.blade.php ================================================ @props([ 'allowEdit' => true, ])
    @pushOnce('scripts') @endPushOnce ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/form/control-group/controls/inline/image.blade.php ================================================ @props([ 'allowEdit' => true, ])
    @pushOnce('scripts') @endPushOnce ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/form/control-group/controls/inline/lookup.blade.php ================================================ @props([ 'allowEdit' => true, 'attribute' => [], ])
    @pushOnce('scripts') @endPushOnce ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/form/control-group/controls/inline/multiselect.blade.php ================================================ @props([ 'allowEdit' => true, 'data' => [], ]) except('data') }} :data="{{ json_encode($data) }}" :allow-edit="{{ $allowEdit ? 'true' : 'false' }}" >
    @pushOnce('scripts') @endPushOnce ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/form/control-group/controls/inline/phone.blade.php ================================================ @props([ 'allowEdit' => true, ]) except('value') }} :value={{ json_encode($attributes->get('value')) }} :allow-edit="{{ $allowEdit ? 'true' : 'false' }}" >
    @pushOnce('scripts') @endPushOnce ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/form/control-group/controls/inline/select.blade.php ================================================ @props([ 'allowEdit' => true, 'options' => [], ]) except('options') }} :options="{{ json_encode($options) }}" :allow-edit="{{ $allowEdit ? 'true' : 'false' }}" >
    @pushOnce('scripts') @endPushOnce ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/form/control-group/controls/inline/text.blade.php ================================================ @props([ 'allowEdit' => true, ])
    @pushOnce('scripts') @endPushOnce ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/form/control-group/controls/tags.blade.php ================================================ @pushOnce('scripts') @endpushOnce ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/form/control-group/error.blade.php ================================================ @props([ 'name' => null, 'controlName' => '', ])

    merge(['class' => 'mt-1 text-xs italic text-red-600']) }} v-text="message" >

    ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/form/control-group/index.blade.php ================================================
    merge(['class' => 'mb-4']) }}> {{ $slot }}
    ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/form/control-group/label.blade.php ================================================ ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/form/index.blade.php ================================================ @if ($attributes->has('as')) {{ $slot }} @else @props([ 'method' => 'POST', ]) @php $method = strtoupper($method); @endphp @unless(in_array($method, ['HEAD', 'GET', 'OPTIONS'])) @csrf @endunless @if (! in_array($method, ['GET', 'POST'])) @method($method) @endif {{ $slot }} @endif ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/layouts/anonymous.blade.php ================================================ {{ $title ?? '' }} @stack('meta') {{ vite()->set(['src/Resources/assets/css/app.css', 'src/Resources/assets/js/app.js']) }} @if ($favicon = core()->getConfigData('general.design.admin_logo.favicon')) @else @endif @php $brandColor = core()->getConfigData('general.settings.menu_color.brand_color') ?? '#0E90D9'; @endphp @stack('styles') {!! view_render_event('admin.layout.head') !!} {!! view_render_event('admin.layout.body.before') !!}
    {!! view_render_event('admin.layout.content.before') !!} {{ $slot }} {!! view_render_event('admin.layout.content.after') !!}
    {!! view_render_event('admin.layout.body.after') !!} @stack('scripts') {!! view_render_event('admin.layout.vue-app-mount.before') !!} {!! view_render_event('admin.layout.vue-app-mount.after') !!} ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/layouts/header/desktop/mega-search.blade.php ================================================
    @pushOnce('scripts') @endPushOnce ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/layouts/header/index.blade.php ================================================
    @include('admin::components.layouts.header.desktop.mega-search') @include('admin::components.layouts.header.quick-creation')
    @include('admin::components.layouts.header.mobile.mega-search')
    @include('admin::components.layouts.header.quick-creation')
    @php($user = auth()->guard('user')->user()) @if ($user->image) @else @endif

    @lang('admin::app.layouts.app-version', ['version' => core()->version()])

    @pushOnce('scripts') @endPushOnce ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/layouts/header/mobile/mega-search.blade.php ================================================ @pushOnce('scripts') @endPushOnce ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/layouts/header/quick-creation.blade.php ================================================
    @if (bouncer()->hasPermission('leads.create') || bouncer()->hasPermission('quotes.create') || bouncer()->hasPermission('mail.create') || bouncer()->hasPermission('contacts.persons.create') || bouncer()->hasPermission('contacts.organizations.create') || bouncer()->hasPermission('products.create') || bouncer()->hasPermission('settings.automation.attributes.create') || bouncer()->hasPermission('settings.user.roles.create') || bouncer()->hasPermission('settings.user.users.create') )
    @if (bouncer()->hasPermission('leads.create')) @endif @if (bouncer()->hasPermission('quotes.create')) @endif @if (bouncer()->hasPermission('mail.create')) @endif @if (bouncer()->hasPermission('contacts.persons.create')) @endif @if (bouncer()->hasPermission('contacts.organizations.create')) @endif @if (bouncer()->hasPermission('products.create')) @endif @if (bouncer()->hasPermission('settings.automation.attributes.create')) @endif @if (bouncer()->hasPermission('settings.user.roles.create')) @endif @if (bouncer()->hasPermission('settings.user.users.create')) @endif
    @endif
    ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/layouts/index.blade.php ================================================ {!! view_render_event('admin.layout.head.before') !!} {{ $title }} @stack('meta') {{ vite()->set(['src/Resources/assets/css/app.css', 'src/Resources/assets/js/app.js']) }} @if ($favicon = core()->getConfigData('general.design.admin_logo.favicon')) @else @endif @php $brandColor = core()->getConfigData('general.settings.menu_color.brand_color') ?? '#0E90D9'; @endphp @stack('styles') {!! view_render_event('admin.layout.head.after') !!} {!! view_render_event('admin.layout.body.before') !!}
    {!! view_render_event('admin.layout.content.before') !!} {!! view_render_event('admin.layout.content.after') !!}
    {!! view_render_event('admin.layout.body.after') !!} @stack('scripts') {!! view_render_event('admin.layout.vue-app-mount.before') !!} {!! view_render_event('admin.layout.vue-app-mount.after') !!} ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/layouts/sidebar/desktop/index.blade.php ================================================
    ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/layouts/sidebar/mobile/index.blade.php ================================================ @pushOnce('scripts') @endPushOnce ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/layouts/tabs.blade.php ================================================ @php $tabs = menu()->getCurrentActiveMenu('admin')?->getChildren(); @endphp @if ( $tabs && $tabs->isNotEmpty() )
    @foreach ($tabs as $tab)
    {{ $tab->getName() }}
    @endforeach
    @endif ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/lookup/index.blade.php ================================================ @pushOnce('scripts') @endPushOnce ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/media/images.blade.php ================================================ @props([ 'name' => 'images', 'allowMultiple' => false, 'showPlaceholders' => false, 'uploadedImages' => [], 'width' => '120px', 'height' => '120px' ]) @pushOnce('scripts') @endPushOnce ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/media/videos.blade.php ================================================ @props([ 'name' => 'images', 'allowMultiple' => false, 'uploadedVideos' => [], 'width' => '210px', 'height' => '120px' ]) get('class') }} > @pushOnce('scripts') @endPushOnce ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/modal/confirm.blade.php ================================================ @pushOnce('scripts') @endPushOnce ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/modal/index.blade.php ================================================ @props([ 'isActive' => false, 'position' => 'center', 'size' => 'normal', ]) @isset($toggle) @endisset @isset($header) @endisset @isset($content) @endisset @isset($footer) @endisset @pushOnce('scripts') @endPushOnce ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/shimmer/accordion/index.blade.php ================================================

    ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/shimmer/activities/index.blade.php ================================================
    @for ($i = 0; $i < 5; $i++)
    @endfor
    @for ($i = 0; $i < 5; $i++)
    @endfor
    ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/shimmer/charts/bar.blade.php ================================================ @props(['count' => 30])
    @foreach (range(1, 10) as $i)
    @endforeach
    @foreach (range(1, $count) as $i)
    @endforeach
    @foreach (range(1, $count) as $i)
    @endforeach
    ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/shimmer/common/address.blade.php ================================================
    ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/shimmer/dashboard/index/open-leads-by-states.blade.php ================================================
    @for ($i = 0; $i < 4; $i++)
    @endfor
    ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/shimmer/dashboard/index/over-all.blade.php ================================================
    @for ($i = 1; $i <= 6; $i++)
    @endfor
    ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/shimmer/dashboard/index/revenue-by-sources.blade.php ================================================
    ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/shimmer/dashboard/index/revenue-by-types.blade.php ================================================
    ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/shimmer/dashboard/index/revenue.blade.php ================================================
    ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/shimmer/dashboard/index/top-persons.blade.php ================================================
    @for ($i = 1; $i <= 5; $i++)
    @endfor
    ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/shimmer/dashboard/index/top-selling-products.blade.php ================================================
    @for ($i = 1; $i <= 5; $i++)
    @endfor
    ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/shimmer/dashboard/index/total-leads.blade.php ================================================
    ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/shimmer/datagrid/index.blade.php ================================================ @props(['isMultiRow' => false])
    ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/shimmer/datagrid/table/body.blade.php ================================================ @props(['isMultiRow' => false]) @for ($i = 0; $i < 10; $i++) @if (! $isMultiRow)
    @else
    @endif @endfor ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/shimmer/datagrid/table/head.blade.php ================================================ @props(['isMultiRow' => false]) @if (! $isMultiRow)
    @else
    @endif ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/shimmer/datagrid/toolbar/filter.blade.php ================================================ {{--
    --}} ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/shimmer/datagrid/toolbar/pagination.blade.php ================================================

    ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/shimmer/datagrid/toolbar/search.blade.php ================================================
    ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/shimmer/datagrid/toolbar.blade.php ================================================

    ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/shimmer/header/mega-search/configurations.blade.php ================================================ @for ($i = 0; $i < 3; $i++)

    @endfor ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/shimmer/header/mega-search/leads.blade.php ================================================ @for ($i = 0; $i < 3; $i++)

    @endfor ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/shimmer/header/mega-search/persons.blade.php ================================================ @for ($i = 0; $i < 3; $i++)

    @endfor ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/shimmer/header/mega-search/products.blade.php ================================================ @for ($i = 0; $i < 3; $i++)

    @endfor ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/shimmer/header/mega-search/quotes.blade.php ================================================ @for ($i = 0; $i < 3; $i++)

    @endfor ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/shimmer/header/mega-search/settings.blade.php ================================================ @for ($i = 0; $i < 3; $i++)

    @endfor ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/shimmer/image/index.blade.php ================================================
    merge(['class' => 'shimmer bg-neutral-100']) }}>
    @pushOnce('scripts') @endPushOnce ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/shimmer/leads/datagrid.blade.php ================================================
    ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/shimmer/leads/index/kanban/toolbar.blade.php ================================================
    ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/shimmer/leads/index/kanban.blade.php ================================================
    @for ($i = 1; $i <= 6; $i++)
    @for ($j = 1; $j <= 3; $j++)
    @endfor
    @endfor
    ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/shimmer/leads/view/mail/index.blade.php ================================================ @props([ 'count' => 1, ])
    @for ($i = 0; $i < $count; $i++)
    @endfor
    ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/shimmer/leads/view/stages.blade.php ================================================ @props(['count' => 5])
    @for ($i = 0; $i < $count; $i++)
    @endfor
    ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/shimmer/mail/datagrid/index.blade.php ================================================
    ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/shimmer/mail/datagrid/table/body.blade.php ================================================ @for ($i = 0; $i < 10; $i++)
    @endfor ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/shimmer/mail/datagrid/table/head.blade.php ================================================
    ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/shimmer/person/view/activities/index.blade.php ================================================
    @for ($i = 0; $i < 5; $i++)
    @endfor
    @for ($i = 0; $i < 5; $i++)
    @endfor
    ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/shimmer/person/view/stages.blade.php ================================================ @props(['count' => 5])
    @for ($i = 0; $i < $count; $i++)
    @endfor
    ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/shimmer/person/view/tags.blade.php ================================================ @props(['count' => 5])
    @for ($i = 0; $i < $count; $i++)
    @endfor
    ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/shimmer/pipelines/kanban.blade.php ================================================
    @for ($i = 1; $i <= 6; $i++)
    @endfor
    ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/shimmer/quotes/index.blade.php ================================================
    ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/shimmer/settings/attributes.blade.php ================================================
    @for ($i = 1; $i < 5; $i++)
    @endfor

    @for ($i = 1; $i < 4; $i++)
    @endfor

    ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/shimmer/settings/web-forms/body.blade.php ================================================
    ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/shimmer/settings/web-forms/head.blade.php ================================================
    ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/shimmer/settings/web-forms/index.blade.php ================================================
    ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/shimmer/tabs/index.blade.php ================================================
    ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/shimmer/tags/index.blade.php ================================================ @props(['count' => 5])
    @for ($i = 0; $i < $count; $i++)
    @endfor
    ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/shimmer/tinymce/index.blade.php ================================================
    merge(['class' => 'shimmer block bg-neutral-100']) }}>
    ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/shimmer/tree/index.blade.php ================================================ @for ($j = 0; $j < 3; $j++)
    @for ($k = 0; $k < 5; $k++)
    @endfor
    @endfor ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/spinner/index.blade.php ================================================ @props(['color' => 'currentColor']) ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/table/index.blade.php ================================================ merge(['class' => 'table-fixed w-full min-w-[800px] text-left text-sm border border-gray-200 dark:border-gray-800']) }}> {{ $slot }}
    ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/table/tbody/index.blade.php ================================================ merge(['class' => 'bg-white dark:bg-gray-900 dark:text-gray-300 dark:border-gray-800']) }}> {{ $slot }} ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/table/tbody/tr.blade.php ================================================ merge(['scope' => 'row', 'class' => 'border-b border-gray-200 last:border-b-0 dark:border-gray-800']) }}> {{ $slot }} ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/table/td.blade.php ================================================ merge(['scope' => 'row', 'class' => 'whitespace-nowrap px-6 py-4']) }}> {{ $slot }} ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/table/th.blade.php ================================================ merge(['scope' => 'col', 'class' => 'whitespace-nowrap px-6 py-4 font-semibold']) }}> {{ $slot }} ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/table/thead/index.blade.php ================================================ merge(['class' => 'bg-gray-50 dark:border-gray-800 dark:bg-gray-900 dark:text-gray-300']) }}> {{ $slot }} ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/table/thead/tr.blade.php ================================================ merge(['scope' => 'row', 'class' => 'border-b border-gray-200 dark:border-gray-800']) }}> {{ $slot }} ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/tabs/index.blade.php ================================================ @props(['position' => 'left']) @pushOnce('scripts') @endPushOnce ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/tabs/item.blade.php ================================================ @props([ 'title' => '', 'isSelected' => false, ]) merge(['class' => 'p-4']) }} > @pushOnce('scripts') @endPushOnce ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/tags/index.blade.php ================================================ @props([ 'attachEndpoint', 'detachEndpoint', 'addedTags' => [], ]) @pushOnce('scripts') @endPushOnce ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/tinymce/index.blade.php ================================================ @php($placeholders = app('\Webkul\Automation\Helpers\Entity')->getEmailTemplatePlaceholders()) @pushOnce('scripts') @endPushOnce ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/tree/checkbox.blade.php ================================================ @pushOnce('scripts') @endPushOnce ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/tree/radio.blade.php ================================================ @pushOnce('scripts') @endPushOnce ================================================ FILE: packages/Webkul/Admin/src/Resources/views/components/tree/view.blade.php ================================================ @props([ 'inputType' => 'checkbox', 'selectionType' => 'hierarchical', ]) @if ($inputType == 'checkbox') @else @endif except(['input-type', 'selection-type']) }} input-type="{{ $inputType }}" selection-type="{{ $selectionType }}" > @pushOnce('scripts') @endPushOnce ================================================ FILE: packages/Webkul/Admin/src/Resources/views/configuration/edit.blade.php ================================================ @php $activeConfiguration = system_config()->getActiveConfigurationItem(); $name = $activeConfiguration->getName(); @endphp {{ strip_tags($name) }} {!! view_render_event('admin.configuration.edit.form_controls.before') !!}

    {{ $name }}

    {!! view_render_event('admin.configuration.edit.back_button.before') !!} @lang('admin::app.configuration.index.back') {!! view_render_event('admin.configuration.edit.back_button.after') !!} {!! view_render_event('admin.configuration.edit.save_button.before') !!} {!! view_render_event('admin.configuration.edit.save_button.after') !!}
    @foreach ($activeConfiguration->getChildren() as $child)

    {{ $child->getName() }}

    {!! $child->getInfo() !!}

    {!! view_render_event('admin.configuration.edit.form_controls.before') !!} @foreach ($child->getFields() as $field) @if ( $field->getType() == 'blade' && view()->exists($path = $field->getPath()) ) {!! view($path, compact('field', 'child'))->render() !!} @else @include ('admin::configuration.field-type') @endif @endforeach {!! view_render_event('admin.configuration.edit.form_controls.after') !!}
    @endforeach
    {!! view_render_event('admin.configuration.edit.form_controls.after') !!}
    ================================================ FILE: packages/Webkul/Admin/src/Resources/views/configuration/field-type.blade.php ================================================ @php($value = old($field->getNameKey()) ?? system_config()->getConfigData($field->getNameKey()))
    @pushOnce('scripts') @endPushOnce ================================================ FILE: packages/Webkul/Admin/src/Resources/views/configuration/index.blade.php ================================================ @lang('admin::app.configuration.index.title') {!! view_render_event('admin.configuration.index.header.before') !!}

    @lang('admin::app.configuration.index.title')

    {!! view_render_event('admin.configuration.index.header.configuration_search.before') !!}
    {!! view_render_event('admin.configuration.index.header.configuration_search.after') !!}
    {!! view_render_event('admin.configuration.index.header.after') !!} {!! view_render_event('admin.configuration.index.content.before') !!}
    @foreach (system_config()->getItems() as $item)

    {{ $item->getName() }}

    {{ $item->getInfo() }}

    @foreach ($item->getChildren() as $key => $child) @if ($icon = $child->getIcon())
    @endif

    {{ $child->getName() }}

    {{ $child->getInfo() }}

    @endforeach
    @endforeach
    {!! view_render_event('admin.configuration.index.content.after') !!} @pushOnce('scripts') @endpushOnce
    ================================================ FILE: packages/Webkul/Admin/src/Resources/views/contacts/organizations/create.blade.php ================================================ @lang('admin::app.contacts.organizations.create.title') {!! view_render_event('admin.organizations.create.form.before') !!}
    {!! view_render_event('admin.organizations.create.breadcrumbs.before') !!} {!! view_render_event('admin.organizations.create.breadcrumbs.before') !!}
    @lang('admin::app.contacts.organizations.create.title')
    {!! view_render_event('admin.organizations.create.save_buttons.before') !!} {!! view_render_event('admin.organizations.create.save_buttons.before') !!}
    {!! view_render_event('admin.contacts.organizations.create.form_controls.before') !!} {!! view_render_event('admin.contacts.organizations.edit.form_controls.after') !!}
    {!! view_render_event('admin.organizations.create.form.after') !!}
    ================================================ FILE: packages/Webkul/Admin/src/Resources/views/contacts/organizations/edit.blade.php ================================================ @lang('admin::app.contacts.organizations.edit.title') {!! view_render_event('admin.organizations.edit.form.before') !!}
    {!! view_render_event('admin.organizations.edit.breadcrumbs.before', ['organization' => $organization]) !!} {!! view_render_event('admin.organizations.edit.breadcrumbs.before', ['organization' => $organization]) !!}
    @lang('admin::app.contacts.organizations.edit.title')
    {!! view_render_event('admin.organizations.edit.save_button.before', ['organization' => $organization]) !!} {!! view_render_event('admin.organizations.edit.save_button.after', ['organization' => $organization]) !!}
    {!! view_render_event('admin.contacts.organizations.edit.form_controls.before') !!} {!! view_render_event('admin.contacts.organizations.edit.form_controls.after') !!}
    {!! view_render_event('admin.organizations.edit.form.after') !!}
    ================================================ FILE: packages/Webkul/Admin/src/Resources/views/contacts/organizations/index.blade.php ================================================ @lang('admin::app.contacts.organizations.index.title')
    {!! view_render_event('admin.organizations.index.breadcrumbs.before') !!} {!! view_render_event('admin.organizations.index.breadcrumbs.before') !!}
    @lang('admin::app.contacts.organizations.index.title')
    {!! view_render_event('admin.organizations.index.create_button.before') !!} @if (bouncer()->hasPermission('contacts.organizations.create')) @lang('admin::app.contacts.organizations.index.create-btn') @endif {!! view_render_event('admin.organizations.index.create_button.after') !!}
    {!! view_render_event('admin.organizations.datagrid.index.before') !!} {!! view_render_event('admin.organizations.datagrid.index.after') !!}
    ================================================ FILE: packages/Webkul/Admin/src/Resources/views/contacts/persons/create.blade.php ================================================ @lang('admin::app.contacts.persons.create.title') {!! view_render_event('admin.persons.create.form.before') !!}
    {!! view_render_event('admin.persons.create.breadcrumbs.before') !!} {!! view_render_event('admin.persons.create.breadcrumbs.after') !!}
    @lang('admin::app.contacts.persons.create.title')
    {!! view_render_event('admin.persons.create.create_button.before') !!} {!! view_render_event('admin.persons.create.create_button.after') !!}
    {!! view_render_event('admin.persons.create.form_controls.before') !!} {!! view_render_event('admin.persons.create.form_controls.after') !!}
    {!! view_render_event('admin.persons.create.form.after') !!} @pushOnce('scripts') @endPushOnce
    ================================================ FILE: packages/Webkul/Admin/src/Resources/views/contacts/persons/edit.blade.php ================================================ @lang('admin::app.contacts.persons.edit.title') {!! view_render_event('admin.persons.edit.form.before') !!}
    {!! view_render_event('admin.persons.edit.breadcrumbs.before') !!} {!! view_render_event('admin.persons.edit.breadcrumbs.after') !!}
    @lang('admin::app.contacts.persons.edit.title')
    {!! view_render_event('admin.persons.edit.save_button.before') !!} {!! view_render_event('admin.persons.edit.save_button.after') !!}
    {!! view_render_event('admin.contacts.persons.edit.form_controls.before') !!} {!! view_render_event('admin.contacts.persons.edit.form_controls.after') !!}
    {!! view_render_event('admin.persons.edit.form.after') !!} @pushOnce('scripts') @endPushOnce
    ================================================ FILE: packages/Webkul/Admin/src/Resources/views/contacts/persons/index.blade.php ================================================ @lang('admin::app.contacts.persons.index.title')
    @lang('admin::app.contacts.persons.index.title')
    {!! view_render_event('admin.persons.index.create_button.before') !!} @if (bouncer()->hasPermission('contacts.persons.create')) @lang('admin::app.contacts.persons.index.create-btn') @endif {!! view_render_event('admin.persons.index.create_button.after') !!}
    {!! view_render_event('admin.persons.index.datagrid.before') !!} {!! view_render_event('admin.persons.index.datagrid.after') !!}
    @pushOnce('scripts') @endPushOnce
    ================================================ FILE: packages/Webkul/Admin/src/Resources/views/contacts/persons/view/attributes.blade.php ================================================ {!! view_render_event('admin.contacts.persons.view.attributes.before', ['person' => $person]) !!}

    @lang('admin::app.contacts.persons.view.about-person')

    {!! view_render_event('admin.contacts.persons.view.attributes.form_controls.before', ['person' => $person]) !!}
    {!! view_render_event('admin.contacts.persons.view.attributes.form_controls.attributes_view.before', ['person' => $person]) !!} {!! view_render_event('admin.contacts.persons.view.attributes.form_controls.attributes_view.after', ['person' => $person]) !!}
    {!! view_render_event('admin.contacts.persons.view.attributes.form_controls.after', ['person' => $person]) !!}
    {!! view_render_event('admin.contacts.persons.view.attributes.before', ['person' => $person]) !!} ================================================ FILE: packages/Webkul/Admin/src/Resources/views/contacts/persons/view/organization.blade.php ================================================ {!! view_render_event('admin.contacts.persons.view.organization.before', ['person' => $person]) !!} @if ($person?->organization)

    @lang('admin::app.contacts.persons.view.about-organization')

    {!! view_render_event('admin.contacts.persons.view.organization.avatar.before', ['person' => $person]) !!} {!! view_render_event('admin.contacts.persons.view.organization.avatar.after', ['person' => $person]) !!}
    {!! view_render_event('admin.contacts.persons.view.organization.name.before', ['person' => $person]) !!} {{ $person->organization->name }} {!! view_render_event('admin.contacts.persons.view.organization.name.after', ['person' => $person]) !!} {!! view_render_event('admin.contacts.persons.view.organization.address.before', ['person' => $person]) !!} @if ($person->organization->address)
    @isset($person->organization->address['address']) {{ $person->organization->address['address'] }} @endisset @if( isset($person->organization->address['postcode']) && isset($person->organization->address['city']) ) {{ $person->organization->address['postcode'] . ' ' . $person->organization->address['city'] }} @endif @isset($person->organization->address['state']) {{ core()->state_name($person->organization->address['state']) }} @endisset @isset($person->organization->address['country']) {{ core()->country_name($person->organization->address['country']) }} @endisset
    @endif {!! view_render_event('admin.contacts.persons.view.organization.address.after', ['person' => $person]) !!}
    @endif {!! view_render_event('admin.contacts.persons.view.organization.after', ['person' => $person]) !!} ================================================ FILE: packages/Webkul/Admin/src/Resources/views/contacts/persons/view.blade.php ================================================ @lang('admin::app.contacts.persons.view.title', ['name' => strip_tags($person->name)])
    {!! view_render_event('admin.contact.persons.view.left.before', ['person' => $person]) !!}
    {!! view_render_event('admin.contact.persons.view.tags.before', ['person' => $person]) !!} {!! view_render_event('admin.contact.persons.view.tags.after', ['person' => $person]) !!}
    {!! view_render_event('admin.contact.persons.view.title.before', ['person' => $person]) !!}

    {{ $person->name }}

    {{ $person->job_title }}

    {!! view_render_event('admin.contact.persons.view.title.after', ['person' => $person]) !!}
    {!! view_render_event('admin.contact.persons.view.actions.before', ['person' => $person]) !!} {!! view_render_event('admin.contact.persons.view.actions.after', ['person' => $person]) !!}
    @include ('admin::contacts.persons.view.attributes') @include ('admin::contacts.persons.view.organization')
    {!! view_render_event('admin.contact.persons.view.left.after', ['person' => $person]) !!}
    {!! view_render_event('admin.contact.persons.view.right.before', ['person' => $person]) !!} {!! view_render_event('admin.contact.persons.view.right.after', ['person' => $person]) !!}
    ================================================ FILE: packages/Webkul/Admin/src/Resources/views/dashboard/index/open-leads-by-states.blade.php ================================================ {!! view_render_event('admin.dashboard.index.open_leads_by_states.before') !!} {!! view_render_event('admin.dashboard.index.open_leads_by_states.after') !!} @pushOnce('scripts') @endPushOnce ================================================ FILE: packages/Webkul/Admin/src/Resources/views/dashboard/index/over-all.blade.php ================================================ {!! view_render_event('admin.dashboard.index.over_all.before') !!} {!! view_render_event('admin.dashboard.index.over_all.after') !!} @pushOnce('scripts') @endPushOnce ================================================ FILE: packages/Webkul/Admin/src/Resources/views/dashboard/index/revenue-by-sources.blade.php ================================================ {!! view_render_event('admin.dashboard.index.revenue_by_sources.before') !!} {!! view_render_event('admin.dashboard.index.revenue_by_sources.after') !!} @pushOnce('scripts') @endPushOnce ================================================ FILE: packages/Webkul/Admin/src/Resources/views/dashboard/index/revenue-by-types.blade.php ================================================ {!! view_render_event('admin.dashboard.index.revenue_by_types.before') !!} {!! view_render_event('admin.dashboard.index.revenue_by_types.after') !!} @pushOnce('scripts') @endPushOnce ================================================ FILE: packages/Webkul/Admin/src/Resources/views/dashboard/index/revenue.blade.php ================================================ {!! view_render_event('admin.dashboard.index.revenue.after') !!} {!! view_render_event('admin.dashboard.index.revenue.after') !!} @pushOnce('scripts') @endPushOnce ================================================ FILE: packages/Webkul/Admin/src/Resources/views/dashboard/index/top-persons.blade.php ================================================ {!! view_render_event('admin.dashboard.index.top_persons.before') !!} {!! view_render_event('admin.dashboard.index.top_persons.after') !!} @pushOnce('scripts') @endPushOnce ================================================ FILE: packages/Webkul/Admin/src/Resources/views/dashboard/index/top-selling-products.blade.php ================================================ {!! view_render_event('admin.dashboard.index.top_selling_proudcts.before') !!} {!! view_render_event('admin.dashboard.index.top_selling_proudcts.after') !!} @pushOnce('scripts') @endPushOnce ================================================ FILE: packages/Webkul/Admin/src/Resources/views/dashboard/index/total-leads.blade.php ================================================ {!! view_render_event('admin.dashboard.index.total_leads.before') !!} {!! view_render_event('admin.dashboard.index.total_leads.after') !!} @pushOnce('scripts') @endPushOnce ================================================ FILE: packages/Webkul/Admin/src/Resources/views/dashboard/index.blade.php ================================================ @lang('admin::app.dashboard.index.title') {!! view_render_event('admin.dashboard.index.header.before') !!}
    {!! view_render_event('admin.dashboard.index.header.left.before') !!}

    @lang('admin::app.dashboard.index.title')

    {!! view_render_event('admin.dashboard.index.header.left.after') !!} {!! view_render_event('admin.dashboard.index.header.right.before') !!}
    {!! view_render_event('admin.dashboard.index.header.right.after') !!}
    {!! view_render_event('admin.dashboard.index.header.after') !!} {!! view_render_event('admin.dashboard.index.content.before') !!}
    {!! view_render_event('admin.dashboard.index.content.left.before') !!}
    @include('admin::dashboard.index.revenue') @include('admin::dashboard.index.over-all') @include('admin::dashboard.index.total-leads')
    @include('admin::dashboard.index.top-selling-products') @include('admin::dashboard.index.top-persons')
    {!! view_render_event('admin.dashboard.index.content.left.after') !!} {!! view_render_event('admin.dashboard.index.content.right.before') !!}
    @include('admin::dashboard.index.open-leads-by-states') @include('admin::dashboard.index.revenue-by-sources') @include('admin::dashboard.index.revenue-by-types')
    {!! view_render_event('admin.dashboard.index.content.left.after') !!}
    {!! view_render_event('admin.dashboard.index.content.after') !!} @pushOnce('scripts') @endPushOnce
    ================================================ FILE: packages/Webkul/Admin/src/Resources/views/emails/common/index.blade.php ================================================ @component('admin::emails.layout') {!! $body !!} @endcomponent ================================================ FILE: packages/Webkul/Admin/src/Resources/views/emails/layout.blade.php ================================================
    {{ $slot }}

    @lang('admin::app.emails.common.cheers', ['app_name' => config('app.name')])

    ================================================ FILE: packages/Webkul/Admin/src/Resources/views/emails/users/create.blade.php ================================================ @component('admin::emails.layout')

    @lang('admin::app.emails.common.user.dear', ['username' => $user_name]), 👋

    @lang('admin::app.emails.common.user.create-body')

    @endcomponent ================================================ FILE: packages/Webkul/Admin/src/Resources/views/emails/users/forget-password.blade.php ================================================ @component('admin::emails.layout')

    @lang('admin::app.emails.common.user.forget-password.dear', ['username' => $user_name]), 👋

    @lang('admin::app.emails.common.user.forget-password.info')

    @lang('admin::app.emails.common.user.forget-password.reset-password')

    @lang('admin::app.emails.common.user.forget-password.final-summary')

    @lang('admin::app.emails.common.user.forget-password.thanks')

    @endcomponent ================================================ FILE: packages/Webkul/Admin/src/Resources/views/errors/index.blade.php ================================================ @lang("admin::app.errors.{$errorCode}.title")
    {{ $errorCode }}

    @lang("admin::app.errors.{$errorCode}.description")

    @lang('admin::app.errors.support', [ 'link' => 'mailto:support@example.com', 'email' => 'support@example.com', 'class' => 'font-semibold text-blue-600 transition-all hover:underline', ])

    ================================================ FILE: packages/Webkul/Admin/src/Resources/views/leads/common/contact.blade.php ================================================ {!! view_render_event('admin.leads.create.contact_person.form_controls.before') !!} {!! view_render_event('admin.leads.create.contact_person.form_controls.after') !!} @pushOnce('scripts') @endPushOnce ================================================ FILE: packages/Webkul/Admin/src/Resources/views/leads/common/products.blade.php ================================================ {!! view_render_event('admin.leads.create.products.form_controls.before') !!} {!! view_render_event('admin.leads.create.products.form_controls.after') !!} @pushOnce('scripts') @endPushOnce ================================================ FILE: packages/Webkul/Admin/src/Resources/views/leads/create.blade.php ================================================ @lang('admin::app.leads.create.title') {!! view_render_event('admin.leads.create.form.before') !!}
    @lang('admin::app.leads.create.title')
    {!! view_render_event('admin.leads.create.save_button.before') !!}
    {!! view_render_event('admin.leads.create.form_buttons.before') !!} {!! view_render_event('admin.leads.create.form_buttons.after') !!}
    {!! view_render_event('admin.leads.create.save_button.after') !!}
    @if (request('stage_id')) @endif @if (request('pipeline_id')) @endif
    {!! view_render_event('admin.leads.create.form.after') !!} @pushOnce('scripts') @endPushOnce @pushOnce('styles') @endPushOnce
    ================================================ FILE: packages/Webkul/Admin/src/Resources/views/leads/edit.blade.php ================================================ @lang('admin::app.leads.edit.title') {!! view_render_event('admin.leads.edit.form_controls.before', ['lead' => $lead]) !!}
    @lang('admin::app.leads.edit.title')
    {!! view_render_event('admin.leads.edit.save_button.before', ['lead' => $lead]) !!}
    {!! view_render_event('admin.leads.edit.form_buttons.before') !!} {!! view_render_event('admin.leads.edit.form_buttons.after') !!}
    {!! view_render_event('admin.leads.edit.save_button.after', ['lead' => $lead]) !!}
    {!! view_render_event('admin.leads.edit.form_controls.after', ['lead' => $lead]) !!} @pushOnce('scripts') @endPushOnce @pushOnce('styles') @endPushOnce
    ================================================ FILE: packages/Webkul/Admin/src/Resources/views/leads/index/kanban/filter.blade.php ================================================ {!! view_render_event('admin.leads.index.kanban.filter.before') !!} {!! view_render_event('admin.leads.index.kanban.filter.after') !!} @pushOnce('scripts') @endpushOnce ================================================ FILE: packages/Webkul/Admin/src/Resources/views/leads/index/kanban/search.blade.php ================================================ {!! view_render_event('admin.leads.index.kanban.search.before') !!} {!! view_render_event('admin.leads.index.kanban.search.after') !!} @pushOnce('scripts') @endPushOnce ================================================ FILE: packages/Webkul/Admin/src/Resources/views/leads/index/kanban/toolbar.blade.php ================================================ {!! view_render_event('admin.leads.index.kanban.toolbar.before') !!}
    {!! view_render_event('admin.leads.index.kanban.toolbar.search.before') !!} @include('admin::leads.index.kanban.search') {!! view_render_event('admin.leads.index.kanban.toolbar.search.after') !!} {!! view_render_event('admin.leads.index.kanban.toolbar.filter.before') !!} @include('admin::leads.index.kanban.filter') {!! view_render_event('admin.leads.index.kanban.toolbar.filter.after') !!}
    {!! view_render_event('admin.leads.index.kanban.toolbar.switcher.before') !!} @include('admin::leads.index.view-switcher') {!! view_render_event('admin.leads.index.kanban.toolbar.switcher.after') !!}
    {!! view_render_event('admin.leads.index.kanban.toolbar.after') !!} ================================================ FILE: packages/Webkul/Admin/src/Resources/views/leads/index/kanban.blade.php ================================================ {!! view_render_event('admin.leads.index.kanban.before') !!}
    {!! view_render_event('admin.leads.index.kanban.after') !!} @pushOnce('scripts') @endPushOnce ================================================ FILE: packages/Webkul/Admin/src/Resources/views/leads/index/table.blade.php ================================================ {!! view_render_event('admin.leads.index.table.before') !!} @include('admin::leads.index.view-switcher') {!! view_render_event('admin.leads.index.table.after') !!} ================================================ FILE: packages/Webkul/Admin/src/Resources/views/leads/index/upload.blade.php ================================================ @pushOnce('scripts') @endPushOnce ================================================ FILE: packages/Webkul/Admin/src/Resources/views/leads/index/view-switcher.blade.php ================================================ {!! view_render_event('admin.leads.index.view_switcher.before') !!}
    {!! view_render_event('admin.leads.index.view_switcher.pipeline.button.before') !!} {!! view_render_event('admin.leads.index.view_switcher.pipeline.button.after') !!} {!! view_render_event('admin.leads.index.view_switcher.pipeline.content.header.before') !!}
    @lang('admin::app.leads.index.view-switcher.all-pipelines')
    {!! view_render_event('admin.leads.index.view_switcher.pipeline.content.header.after') !!} @foreach (app('Webkul\Lead\Repositories\PipelineRepository')->all() as $tempPipeline) {!! view_render_event('admin.leads.index.view_switcher.pipeline.content.before', ['tempPipeline' => $tempPipeline]) !!} {{ $tempPipeline->name }} {!! view_render_event('admin.leads.index.view_switcher.pipeline.content.after', ['tempPipeline' => $tempPipeline]) !!} @endforeach {!! view_render_event('admin.leads.index.view_switcher.pipeline.content.footer.before') !!} @lang('admin::app.leads.index.view-switcher.create-new-pipeline') {!! view_render_event('admin.leads.index.view_switcher.pipeline.content.footer.after') !!}
    {!! view_render_event('admin.leads.index.view_switcher.pipeline.view_type.before') !!} @if (request('view_type')) @else @endif {!! view_render_event('admin.leads.index.view_switcher.pipeline.view_type.after') !!}
    {!! view_render_event('admin.leads.index.view_switcher.after') !!} ================================================ FILE: packages/Webkul/Admin/src/Resources/views/leads/index.blade.php ================================================ @lang('admin::app.leads.index.title') {!! view_render_event('admin.leads.index.header.before') !!}
    {!! view_render_event('admin.leads.index.header.left.before') !!}
    @lang('admin::app.leads.index.title')
    {!! view_render_event('admin.leads.index.header.left.after') !!} {!! view_render_event('admin.leads.index.header.right.before') !!}
    @if(core()->getConfigData('general.magic_ai.doc_generation.enabled')) @include('admin::leads.index.upload') @endif @if ((request()->view_type ?? "kanban") == "table") @endif
    @if (bouncer()->hasPermission('leads.create')) @lang('admin::app.leads.index.create-btn') @endif
    {!! view_render_event('admin.leads.index.header.right.after') !!}
    {!! view_render_event('admin.leads.index.header.after') !!} {!! view_render_event('admin.leads.index.content.before') !!}
    @if ((request()->view_type ?? "kanban") == "table") @include('admin::leads.index.table') @else @include('admin::leads.index.kanban') @endif
    {!! view_render_event('admin.leads.index.content.after') !!}
    ================================================ FILE: packages/Webkul/Admin/src/Resources/views/leads/view/attributes.blade.php ================================================ {!! view_render_event('admin.leads.view.attributes.before', ['lead' => $lead]) !!}

    @lang('admin::app.leads.view.attributes.title')

    @if (bouncer()->hasPermission('leads.edit')) @endif
    {!! view_render_event('admin.leads.view.attributes.form_controls.before', ['lead' => $lead]) !!}
    {!! view_render_event('admin.leads.view.attributes.form_controls.attributes.view.before', ['lead' => $lead]) !!} {!! view_render_event('admin.leads.view.attributes.form_controls.attributes.view.after', ['lead' => $lead]) !!}
    {!! view_render_event('admin.leads.view.attributes.form_controls.after', ['lead' => $lead]) !!}
    {!! view_render_event('admin.leads.view.attributes.before', ['lead' => $lead]) !!} ================================================ FILE: packages/Webkul/Admin/src/Resources/views/leads/view/person.blade.php ================================================ {!! view_render_event('admin.leads.view.person.before', ['lead' => $lead]) !!} @if ($lead?->person)

    @lang('admin::app.leads.view.persons.title')

    @if (bouncer()->hasPermission('contacts.persons.edit')) @endif
    {!! view_render_event('admin.leads.view.person.avatar.before', ['lead' => $lead]) !!} {!! view_render_event('admin.leads.view.person.avatar.after', ['lead' => $lead]) !!}
    {!! view_render_event('admin.leads.view.person.name.before', ['lead' => $lead]) !!} {{ $lead->person->name }} {!! view_render_event('admin.leads.view.person.name.after', ['lead' => $lead]) !!} {!! view_render_event('admin.leads.view.person.job_title.before', ['lead' => $lead]) !!} @if ($lead->person->job_title) @if ($lead->person->organization) @lang('admin::app.leads.view.persons.job-title', [ 'job_title' => $lead->person->job_title, 'organization' => $lead->person->organization->name ]) @else {{ $lead->person->job_title }} @endif @endif {!! view_render_event('admin.leads.view.person.job_title.after', ['lead' => $lead]) !!} {!! view_render_event('admin.leads.view.person.email.before', ['lead' => $lead]) !!} @foreach ($lead->person->emails as $email)
    {{ $email['value'] }} ({{ $email['label'] }})
    @endforeach {!! view_render_event('admin.leads.view.person.email.after', ['lead' => $lead]) !!} {!! view_render_event('admin.leads.view.person.contact_numbers.before', ['lead' => $lead]) !!} @foreach ($lead->person->contact_numbers as $contactNumber)
    {{ $contactNumber['value'] }} ({{ $contactNumber['label'] }})
    @endforeach {!! view_render_event('admin.leads.view.person.contact_numbers.after', ['lead' => $lead]) !!}
    @endif {!! view_render_event('admin.leads.view.person.after', ['lead' => $lead]) !!} ================================================ FILE: packages/Webkul/Admin/src/Resources/views/leads/view/products.blade.php ================================================ {!! view_render_event('admin.leads.view.products.before', ['lead' => $lead]) !!} {!! view_render_event('admin.leads.view.products.after', ['lead' => $lead]) !!} @pushOnce('scripts') @endPushOnce ================================================ FILE: packages/Webkul/Admin/src/Resources/views/leads/view/quotes.blade.php ================================================ {!! view_render_event('admin.leads.view.quotes.before', ['lead' => $lead]) !!} {!! view_render_event('admin.leads.view.quotes.after', ['lead' => $lead]) !!} @pushOnce('scripts') @endPushOnce ================================================ FILE: packages/Webkul/Admin/src/Resources/views/leads/view/stages.blade.php ================================================ {!! view_render_event('admin.leads.view.stages.before', ['lead' => $lead]) !!} {!! view_render_event('admin.leads.view.stages.after', ['lead' => $lead]) !!} @pushOnce('scripts') @endPushOnce ================================================ FILE: packages/Webkul/Admin/src/Resources/views/leads/view.blade.php ================================================ @lang('admin::app.leads.view.title', ['title' => strip_tags($lead->title)])
    {!! view_render_event('admin.leads.view.left.before', ['lead' => $lead]) !!}
    @if (($days = $lead->rotten_days) > 0) @php $lead->tags->prepend([ 'name' => '' . trans('admin::app.leads.view.rotten-days', ['days' => $days]), 'color' => '#FEE2E2' ]); @endphp @endif {!! view_render_event('admin.leads.view.tags.before', ['lead' => $lead]) !!} {!! view_render_event('admin.leads.view.tags.after', ['lead' => $lead]) !!}
    {!! view_render_event('admin.leads.view.title.before', ['lead' => $lead]) !!}

    {{ $lead->title }}

    {!! view_render_event('admin.leads.view.title.after', ['lead' => $lead]) !!}
    {!! view_render_event('admin.leads.view.actions.before', ['lead' => $lead]) !!} @if (bouncer()->hasPermission('mail.compose')) @endif @if (bouncer()->hasPermission('activities.create')) @endif {!! view_render_event('admin.leads.view.actions.after', ['lead' => $lead]) !!}
    @include ('admin::leads.view.attributes') @include ('admin::leads.view.person')
    {!! view_render_event('admin.leads.view.left.after', ['lead' => $lead]) !!} {!! view_render_event('admin.leads.view.right.before', ['lead' => $lead]) !!}
    @include ('admin::leads.view.stages') {!! view_render_event('admin.leads.view.activities.before', ['lead' => $lead]) !!} @include ('admin::leads.view.products') @include ('admin::leads.view.quotes')
    {{ $lead->description }}
    {!! view_render_event('admin.leads.view.activities.after', ['lead' => $lead]) !!}
    {!! view_render_event('admin.leads.view.right.after', ['lead' => $lead]) !!}
    ================================================ FILE: packages/Webkul/Admin/src/Resources/views/mail/index.blade.php ================================================ @lang('admin::app.mail.index.' . $route)
    {!! view_render_event('admin.mail.create.breadcrumbs.before') !!} {!! view_render_event('admin.mail.create.breadcrumbs.after') !!}
    @lang('admin::app.mail.index.' . $route)
    {!! view_render_event('admin.mail.create.compose_mail_btn.before') !!} @if (bouncer()->hasPermission('mail.compose')) @endif {!! view_render_event('admin.mail.create.compose_mail_btn.after') !!}
    @pushOnce('scripts') @endPushOnce
    ================================================ FILE: packages/Webkul/Admin/src/Resources/views/mail/view.blade.php ================================================ @php if (! $email->is_read) { $email->is_read = true; $email->save(); } @endphp @lang('admin::app.mail.view.subject', ['subject' => strip_tags($email->subject)])
    {!! view_render_event('admin.mail.view.form.before', ['email' => $email]) !!} {!! view_render_event('admin.mail.view.form.after', ['email' => $email]) !!}
    @lang('admin::app.mail.view.title')
    {{ ucfirst($route) }} {!! view_render_event('admin.mail.view.tags.before', ['email' => $email]) !!} {!! view_render_event('admin.mail.view.tags.after', ['email' => $email]) !!}
    {!! view_render_event('admin.mail.view.email-list.before', ['email' => $email]) !!} {!! view_render_event('admin.mail.view.email-list.before', ['email' => $email]) !!}
    @pushOnce('scripts') @endPushOnce
    ================================================ FILE: packages/Webkul/Admin/src/Resources/views/partials/breadcrumbs.blade.php ================================================ @unless ($breadcrumbs->isEmpty()) @endunless ================================================ FILE: packages/Webkul/Admin/src/Resources/views/products/create.blade.php ================================================ @lang('admin::app.products.create.title') {!! view_render_event('admin.products.create.form.before') !!}
    {!! view_render_event('admin.products.create.breadcrumbs.before') !!} {!! view_render_event('admin.products.create.breadcrumbs.after') !!}
    @lang('admin::app.products.create.title')
    {!! view_render_event('admin.products.create.save_button.before') !!} @if (bouncer()->hasPermission('settings.user.groups.create')) @endif {!! view_render_event('admin.products.create.save_button.after') !!}

    @lang('admin::app.products.create.general')

    {!! view_render_event('admin.products.create.attributes.before') !!} {!! view_render_event('admin.products.create.attributes.after') !!}
    {!! view_render_event('admin.products.create.accordion.before') !!} {!! view_render_event('admin.products.create.accordion.header.before') !!}

    @lang('admin::app.products.create.price')

    {!! view_render_event('admin.products.create.accordion.header.after') !!} {!! view_render_event('admin.products.create.accordion.content.attributes.before') !!} {!! view_render_event('admin.products.create.accordion.content.attributes.after') !!}
    {!! view_render_event('admin.products.create.accordion.before') !!}
    {!! view_render_event('admin.products.create.form.after') !!}
    ================================================ FILE: packages/Webkul/Admin/src/Resources/views/products/edit.blade.php ================================================ @lang('admin::app.products.edit.title') {!! view_render_event('admin.products.edit.form.before') !!}
    @lang('admin::app.products.edit.title')
    {!! view_render_event('admin.products.edit.create_button.before', ['product' => $product]) !!} {!! view_render_event('admin.products.edit.create_button.after', ['product' => $product]) !!}

    @lang('admin::app.products.create.general')

    {!! view_render_event('admin.products.edit.attributes.before', ['product' => $product]) !!} {!! view_render_event('admin.products.edit.attributes.after', ['product' => $product]) !!}
    {!! view_render_event('admin.products.edit.accordion.before', ['product' => $product]) !!} {!! view_render_event('admin.products.edit.accordion.header.before', ['product' => $product]) !!}

    @lang('admin::app.products.create.price')

    {!! view_render_event('admin.products.edit.accordion.header.after', ['product' => $product]) !!} {!! view_render_event('admin.products.edit.accordion.content.attributes.before', ['product' => $product]) !!} {!! view_render_event('admin.products.edit.accordion.content.attributes.after', ['product' => $product]) !!}
    {!! view_render_event('admin.products.edit.accordion.after', ['product' => $product]) !!}
    {!! view_render_event('admin.products.edit.form.after') !!}
    ================================================ FILE: packages/Webkul/Admin/src/Resources/views/products/index.blade.php ================================================ @lang('admin::app.products.index.title')
    @lang('admin::app.products.index.title')
    {!! view_render_event('admin.products.index.create_button.before') !!} @if (bouncer()->hasPermission('products.create')) @endif {!! view_render_event('admin.products.index.create_button.after') !!}
    {!! view_render_event('admin.products.index.datagrid.before') !!} {!! view_render_event('admin.products.index.datagrid.after') !!}
    ================================================ FILE: packages/Webkul/Admin/src/Resources/views/products/view/attributes.blade.php ================================================ {!! view_render_event('admin.products.view.attributes.before', ['product' => $product]) !!}

    @lang('admin::app.products.view.attributes.about-product')

    {!! view_render_event('admin.products.view.attributes.view.before', ['product' => $product]) !!}
    {!! view_render_event('admin.products.view.attributes.view.after', ['product' => $product]) !!}
    {!! view_render_event('admin.products.view.attributes.before', ['product' => $product]) !!} ================================================ FILE: packages/Webkul/Admin/src/Resources/views/products/view/inventory.blade.php ================================================ {!! view_render_event('admin.products.view.inventory.before', ['product' => $product]) !!} {!! view_render_event('admin.products.view.inventory.after', ['product' => $product]) !!} @pushOnce('scripts') @endPushOnce ================================================ FILE: packages/Webkul/Admin/src/Resources/views/products/view.blade.php ================================================ {{ strip_tags($product->name) }}
    {!! view_render_event('admin.products.view.left.before', ['product' => $product]) !!}
    {!! view_render_event('admin.products.view.left.tags.before', ['product' => $product]) !!} {!! view_render_event('admin.products.view.left.tags.after', ['product' => $product]) !!}
    {!! view_render_event('admin.products.view.left.title.before', ['product' => $product]) !!}

    {{ $product->name }}

    {!! view_render_event('admin.products.view.left.title.after', ['product' => $product]) !!} {!! view_render_event('admin.products.view.left.sku.before', ['product' => $product]) !!}

    @lang('admin::app.products.view.sku') : {{ $product->sku }}

    {!! view_render_event('admin.products.view.left.sku.after', ['product' => $product]) !!}
    {!! view_render_event('admin.products.view.left.activity_actions.before', ['product' => $product]) !!}
    {!! view_render_event('admin.products.view.left.activity_actions.note.before', ['product' => $product]) !!} {!! view_render_event('admin.products.view.left.activity_actions.note.after', ['product' => $product]) !!} {!! view_render_event('admin.products.view.left.activity_actions.file.before', ['product' => $product]) !!} {!! view_render_event('admin.products.view.left.activity_actions.file.after', ['product' => $product]) !!}
    {!! view_render_event('admin.products.view.left.activity_actions.after', ['product' => $product]) !!}
    @include ('admin::products.view.attributes')
    {!! view_render_event('admin.products.view.left.after', ['product' => $product]) !!} {!! view_render_event('admin.products.view.right.before', ['product' => $product]) !!}
    {!! view_render_event('admin.products.view.right.activities.before', ['product' => $product]) !!} @include('admin::products.view.inventory') {!! view_render_event('admin.products.view.right.activities.after', ['product' => $product]) !!}
    {!! view_render_event('admin.products.view.right.after', ['product' => $product]) !!}
    ================================================ FILE: packages/Webkul/Admin/src/Resources/views/quotes/create.blade.php ================================================ @php $quote = app('\Webkul\Quote\Repositories\QuoteRepository')->getModel(); if (isset($lead)) { $quote->fill([ 'person_id' => $lead->person_id, 'user_id' => $lead->user_id, 'billing_address' => $lead->person->organization ? $lead->person->organization->address : null ]); } @endphp @lang('admin::app.quotes.create.title') {!! view_render_event('admin.contacts.quotes.create.form_controls.before') !!}
    @lang('admin::app.quotes.create.title')
    {!! view_render_event('admin.contacts.quotes.create.save_button.before') !!} {!! view_render_event('admin.contacts.quotes.create.save_button.after') !!}
    {!! view_render_event('admin.contacts.quotes.create.form_controls.after') !!} @pushOnce('scripts') @endPushOnce @pushOnce('styles') @endPushOnce
    ================================================ FILE: packages/Webkul/Admin/src/Resources/views/quotes/edit.blade.php ================================================ @lang('admin::app.quotes.edit.title') {!! view_render_event('admin.contacts.quotes.edit.form_controls.before', ['quote' => $quote]) !!}
    @lang('admin::app.quotes.edit.title')
    {!! view_render_event('admin.contacts.quotes.edit.save_button.before', ['quote' => $quote]) !!} {!! view_render_event('admin.contacts.quotes.edit.save_button.after', ['quote' => $quote]) !!}
    {!! view_render_event('admin.contacts.quotes.edit.form_controls.after', ['quote' => $quote]) !!} @pushOnce('scripts') @endPushOnce @pushOnce('styles') @endPushOnce
    ================================================ FILE: packages/Webkul/Admin/src/Resources/views/quotes/index.blade.php ================================================ @lang('admin::app.quotes.index.title')
    @lang('admin::app.quotes.index.title')
    @if (bouncer()->hasPermission('quotes.create')) @lang('admin::app.quotes.index.create-btn') @endif
    @pushOnce('scripts') @endPushOnce
    ================================================ FILE: packages/Webkul/Admin/src/Resources/views/quotes/pdf.blade.php ================================================ @php if ($locale == 'en') { $fontFamily = [ 'regular' => 'DejaVu Sans', 'bold' => 'DejaVu Sans', ]; } else { $fontFamily = [ 'regular' => 'Arial, sans-serif', 'bold' => 'Arial, sans-serif', ]; } if (in_array($locale, ['ar', 'fa', 'tr'])) { $fontFamily = [ 'regular' => 'DejaVu Sans', 'bold' => 'DejaVu Sans', ]; } @endphp
    @lang('admin::app.quotes.index.pdf.quote-id'): #{{ $quote->id }} @lang('admin::app.quotes.index.pdf.person'): {{ $quote->person->name }}
    @lang('admin::app.quotes.index.pdf.sales-person'): {{ $quote->user->name }} @lang('admin::app.quotes.index.pdf.subject'): {{ $quote->subject }}
    @lang('admin::app.quotes.index.pdf.date'): {{ core()->formatDate($quote->created_at, 'd-m-Y') }} @lang('admin::app.quotes.index.pdf.sales-person'): {{ $quote->user->name }}
    @lang('admin::app.quotes.index.pdf.expired-at'): {{ core()->formatDate($quote->expired_at, 'd-m-Y') }}
    @if ($quote->billing_address) @endif @if ($quote->shipping_address) @endif @if ($quote->billing_address) @endif @if ($quote->shipping_address) @endif
    @lang('admin::app.quotes.index.pdf.billing-address') @lang('admin::app.quotes.index.pdf.shipping-address')
    {{ $quote->billing_address['address'] ?? '' }}
    {{ $quote->billing_address['postcode'] ?? '' . ' ' .$quote->billing_address['city'] ?? '' }}
    {{ $quote->billing_address['state'] ?? '' }}
    {{ core()->country_name($quote->billing_address['country'] ?? '') }}
    {{ $quote->shipping_address['address'] ?? ''}}
    {{ $quote->shipping_address['postcode'] ?? '' . ' ' .$quote->shipping_address['city'] ?? '' }}
    {{ $quote->shipping_address['state'] ?? '' }}
    {{ core()->country_name($quote->shipping_address['country'] ?? '') }}
    @foreach ($quote->items as $item) @endforeach
    @lang('admin::app.quotes.index.pdf.sku') @lang('admin::app.quotes.index.pdf.product-name') @lang('admin::app.quotes.index.pdf.price') @lang('admin::app.quotes.index.pdf.quantity') @lang('admin::app.quotes.index.pdf.amount') @lang('admin::app.quotes.index.pdf.discount') @lang('admin::app.quotes.index.pdf.tax') @lang('admin::app.quotes.index.pdf.grand-total')
    {{ $item->sku }} {{ $item->name }} {!! core()->formatBasePrice($item->price, true) !!} {{ $item->quantity }} {!! core()->formatBasePrice($item->total, true) !!} {!! core()->formatBasePrice($item->discount_amount, true) !!} {!! core()->formatBasePrice($item->tax_amount, true) !!} {!! core()->formatBasePrice($item->total + $item->tax_amount - $item->discount_amount, true) !!}
    @lang('admin::app.quotes.index.pdf.sub-total') - {!! core()->formatBasePrice($quote->sub_total, true) !!}
    @lang('admin::app.quotes.index.pdf.tax') - {!! core()->formatBasePrice($quote->tax_amount, true) !!}
    @lang('admin::app.quotes.index.pdf.discount') - {!! core()->formatBasePrice($quote->discount_amount, true) !!}
    @lang('admin::app.quotes.index.pdf.adjustment') - {!! core()->formatBasePrice($quote->adjustment_amount, true) !!}
    @lang('admin::app.quotes.index.pdf.grand-total') - {!! core()->formatBasePrice($quote->grand_total, true) !!}
    ================================================ FILE: packages/Webkul/Admin/src/Resources/views/sessions/forgot-password.blade.php ================================================ @lang('admin::app.users.forget-password.create.page-title')
    @if ($logo = core()->getConfigData('general.design.admin_logo.logo_image')) {{ config('app.name') }} @else {{ config('app.name') }} @endif
    {!! view_render_event('admin.sessions.forgor_password.form_controls.before') !!}

    @lang('admin::app.users.forget-password.create.title')

    @lang('admin::app.users.forget-password.create.email')
    @lang('admin::app.users.forget-password.create.sign-in-link')
    {!! view_render_event('admin.sessions.forgor_password.form_controls.after') !!}
    @lang('admin::app.components.layouts.powered-by.description', [ 'krayin' => 'Krayin', 'webkul' => 'Webkul', ])
    ================================================ FILE: packages/Webkul/Admin/src/Resources/views/sessions/login.blade.php ================================================ @lang('admin::app.users.login.title')
    @if ($logo = core()->getConfigData('general.design.admin_logo.logo_image')) {{ config('app.name') }} @else {{ config('app.name') }} @endif
    {!! view_render_event('admin.sessions.login.form_controls.before') !!}

    @lang('admin::app.users.login.title')

    @lang('admin::app.users.login.email') @lang('admin::app.users.login.password')
    @lang('admin::app.users.login.forget-password-link')
    {!! view_render_event('admin.sessions.login.form_controls.after') !!}
    @lang('admin::app.components.layouts.powered-by.description', [ 'krayin' => 'Krayin', 'webkul' => 'Webkul', ])
    @push('scripts') @endpush
    ================================================ FILE: packages/Webkul/Admin/src/Resources/views/sessions/reset-password.blade.php ================================================ @lang('admin::app.users.reset-password.title')
    @if ($logo = core()->getConfigData('general.design.admin_logo.logo_image')) {{ config('app.name') }} @else {{ config('app.name') }} @endif
    {!! view_render_event('admin.sessions.reset-password.form_controls.before') !!}

    @lang('admin::app.users.reset-password.title')

    @lang('admin::app.users.reset-password.email') @lang('admin::app.users.reset-password.password') @lang('admin::app.users.reset-password.confirm-password')
    @lang('admin::app.users.reset-password.back-link-title')
    {!! view_render_event('admin.sessions.reset-password.form_controls.after') !!}
    @lang('admin::app.components.layouts.powered-by.description', [ 'krayin' => 'Krayin', 'webkul' => 'Webkul', ])
    ================================================ FILE: packages/Webkul/Admin/src/Resources/views/settings/attributes/create.blade.php ================================================ @lang('admin::app.settings.attributes.create.title') {!! view_render_event('admin.settings.attributes.create.before') !!}
    {!! view_render_event('admin.settings.attributes.create.form_controls.before') !!}
    {!! view_render_event('admin.settings.attributes.create.breadcrumbs.before') !!} {!! view_render_event('admin.settings.attributes.create.breadcrumbs.after') !!}
    {!! view_render_event('admin.settings.attributes.create.title.before') !!} @lang('admin::app.settings.attributes.create.title') {!! view_render_event('admin.settings.attributes.create.title.after') !!}
    {!! view_render_event('admin.settings.attributes.create.create_button.before') !!} @if (bouncer()->hasPermission('settings.automation.attributes.create')) @endif {!! view_render_event('admin.settings.attributes.create.create_button.after') !!}
    {!! view_render_event('admin.settings.attributes.create.form_controls.after') !!}
    {!! view_render_event('admin.settings.attributes.create.after') !!} @pushOnce('scripts') @endPushOnce
    ================================================ FILE: packages/Webkul/Admin/src/Resources/views/settings/attributes/edit.blade.php ================================================ @lang('admin::app.settings.attributes.edit.title') {!! view_render_event('admin.catalog.attributes.edit.before', ['attribute' => $attribute]) !!}
    {!! view_render_event('admin.settings.attributes.edit.form_controls.before', ['attribute' => $attribute]) !!}
    {!! view_render_event('admin.settings.attributes.edit.breadcrumbs.before', ['attribute' => $attribute]) !!} {!! view_render_event('admin.settings.attributes.edit.breadcrumbs.after', ['attribute' => $attribute]) !!}
    {!! view_render_event('admin.settings.attributes.edit.title.before', ['attribute' => $attribute]) !!} @lang('admin::app.settings.attributes.edit.title') {!! view_render_event('admin.settings.attributes.edit.title.after', ['attribute' => $attribute]) !!}
    {!! view_render_event('admin.settings.attributes.edit.edit_button.before', ['attribute' => $attribute]) !!} @if (bouncer()->hasPermission('settings.automation.attributes.edit')) @endif {!! view_render_event('admin.settings.attributes.edit.edit_button.after', ['attribute' => $attribute]) !!}
    {!! view_render_event('admin.settings.attributes.edit.form_controls.after', ['attribute' => $attribute]) !!}
    {!! view_render_event('admin.catalog.attributes.edit.after', ['attribute' => $attribute]) !!} @pushOnce('scripts') @endPushOnce
    ================================================ FILE: packages/Webkul/Admin/src/Resources/views/settings/attributes/index.blade.php ================================================ @lang('admin::app.settings.attributes.index.title')
    {!! view_render_event('admin.settings.attributes.index.breadcrumbs.before') !!} {!! view_render_event('admin.settings.attributes.index.breadcrumbs.after') !!}
    @lang('admin::app.settings.attributes.index.title')
    {!! view_render_event('admin.settings.attributes.index.create_button.before') !!} @if (bouncer()->hasPermission('settings.automation.attributes.create')) @lang('admin::app.settings.attributes.index.create-btn') @endif {!! view_render_event('admin.settings.attributes.index.create_button.after') !!}
    {!! view_render_event('admin.settings.attributes.index.datagrid.before') !!} {!! view_render_event('admin.settings.attributes.index.datagrid.after') !!}
    ================================================ FILE: packages/Webkul/Admin/src/Resources/views/settings/data-transfer/imports/create.blade.php ================================================ @lang('admin::app.settings.data-transfer.imports.create.title') {!! view_render_event('admin.settings.data_transfer.imports.create.before') !!} {!! view_render_event('admin.settings.data_transfer.imports.create.create_form_controls.before') !!}
    {!! view_render_event('admin.settings.data_transfers.create.breadcrumbs.before') !!} {!! view_render_event('admin.settings.data_transfers.create.breadcrumbs.after') !!}
    @lang('admin::app.settings.data-transfer.imports.create.title')
    {!! view_render_event('admin.settings.data_transfers.create.save_button.before') !!} @if (bouncer()->hasPermission('settings.automation.data_transfer.imports.create')) @endif {!! view_render_event('admin.settings.data_transfers.create.save_button.after') !!}
    {!! view_render_event('admin.settings.data_transfer.imports.create.card.general.before') !!}

    @lang('admin::app.settings.data-transfer.imports.create.general')

    @lang('admin::app.settings.data-transfer.imports.create.type') @foreach (config('importers') as $code => $importer) @endforeach @lang('admin::app.settings.data-transfer.imports.create.download-sample') @lang('admin::app.settings.data-transfer.imports.create.file')
    {!! view_render_event('admin.settings.data_transfer.imports.create.card.general.after') !!}
    {!! view_render_event('admin.settings.data_transfer.imports.create.card.accordion.settings.before') !!}

    @lang('admin::app.settings.data-transfer.imports.create.settings')

    @lang('admin::app.settings.data-transfer.imports.create.action') @lang('admin::app.settings.data-transfer.imports.create.validation-strategy') @lang('admin::app.settings.data-transfer.imports.create.allowed-errors') @lang('admin::app.settings.data-transfer.imports.create.field-separator') @lang('admin::app.settings.data-transfer.imports.create.process-in-queue')
    {!! view_render_event('admin.settings.data_transfer.imports.create.card.accordion.settings.after') !!}
    {!! view_render_event('admin.settings.data_transfer.imports.create.create_form_controls.after') !!}
    ================================================ FILE: packages/Webkul/Admin/src/Resources/views/settings/data-transfer/imports/edit.blade.php ================================================ @lang('admin::app.settings.data-transfer.imports.edit.title') {!! view_render_event('admin.settings.data_transfer.imports.edit.before', ['import' => $import]) !!} {!! view_render_event('admin.settings.data_transfer.imports.edit.edit_form_controls.before', ['import' => $import]) !!}
    {!! view_render_event('admin.settings.data_transfers.edit.breadcrumbs.before') !!} {!! view_render_event('admin.settings.data_transfers.edit.breadcrumbs.after') !!}
    @lang('admin::app.settings.data-transfer.imports.edit.title')
    {!! view_render_event('admin.settings.data_transfers.edit.save_button.before') !!} @if (bouncer()->hasPermission('settings.automation.data_transfer.imports.edit')) @endif {!! view_render_event('admin.settings.data_transfers.edit.save_button.after') !!}
    {!! view_render_event('admin.settings.data_transfer.imports.edit.card.general.before', ['import' => $import]) !!}

    @lang('admin::app.settings.data-transfer.imports.edit.general')

    @lang('admin::app.settings.data-transfer.imports.edit.type')
    @foreach (config('importers') as $code => $importer) @endforeach @lang('admin::app.settings.data-transfer.imports.edit.download-sample')
    @lang('admin::app.settings.data-transfer.imports.edit.file')
    @if ($import?->file_path) {{ $import?->file_name }} @endif
    {!! view_render_event('admin.settings.data_transfer.imports.edit.card.general.after', ['import' => $import]) !!}
    {!! view_render_event('admin.settings.data_transfer.imports.edit.card.accordion.settings.before', ['import' => $import]) !!}

    @lang('admin::app.settings.data-transfer.imports.edit.settings')

    @lang('admin::app.settings.data-transfer.imports.edit.action') @lang('admin::app.settings.data-transfer.imports.edit.validation-strategy') @lang('admin::app.settings.data-transfer.imports.edit.allowed-errors') @lang('admin::app.settings.data-transfer.imports.edit.field-separator') @lang('admin::app.settings.data-transfer.imports.edit.process-in-queue')
    {!! view_render_event('admin.settings.data_transfer.imports.edit.card.accordion.settings.after', ['import' => $import]) !!}
    {!! view_render_event('admin.settings.data_transfer.imports.edit.edit_form_controls.after', ['import' => $import]) !!}
    ================================================ FILE: packages/Webkul/Admin/src/Resources/views/settings/data-transfer/imports/import.blade.php ================================================ @lang('admin::app.settings.data-transfer.imports.import.title')
    {!! view_render_event('admin.settings.data_transfers.import.breadcrumbs.before') !!} {!! view_render_event('admin.settings.data_transfers.import.breadcrumbs.after') !!}
    @lang('admin::app.settings.data-transfer.imports.import.title')
    {!! view_render_event('admin.settings.data_transfers.import.edit_button.before') !!} @if (bouncer()->hasPermission('settings.automation.data_transfer.imports.edit')) @lang('admin::app.settings.data-transfer.imports.import.edit-btn') @endif {!! view_render_event('admin.settings.data_transfers.import.edit_button.after') !!}
    @pushOnce('scripts') @endPushOnce
    ================================================ FILE: packages/Webkul/Admin/src/Resources/views/settings/data-transfer/imports/index.blade.php ================================================ @lang('admin::app.settings.data-transfer.imports.index.title')
    {!! view_render_event('admin.settings.data_transfers.index.breadcrumbs.before') !!} {!! view_render_event('admin.settings.data_transfers.index.breadcrumbs.after') !!}
    @lang('admin::app.settings.data-transfer.imports.index.title')
    {!! view_render_event('admin.settings.data_transfers.index.create_button.before') !!} @if (bouncer()->hasPermission('settings.automation.data_transfer.imports.create')) @lang('admin::app.settings.data-transfer.imports.index.button-title') @endif {!! view_render_event('admin.settings.data_transfers.index.create_button.after') !!}
    {!! view_render_event('admin.settings.data_transfers.index.datagrid.before') !!} {!! view_render_event('admin.settings.data_transfers.index.datagrid.after') !!}
    ================================================ FILE: packages/Webkul/Admin/src/Resources/views/settings/email-templates/create.blade.php ================================================ @lang('admin::app.settings.email-template.create.title') {!! view_render_event('admin.settings.email_template.create.form.before') !!}
    {!! view_render_event('admin.settings.email_template.create.breadcrumbs.after') !!} {!! view_render_event('admin.settings.email_template.create.breadcrumbs.after') !!}
    @lang('admin::app.settings.email-template.create.title')
    {!! view_render_event('admin.settings.email_template.create.save_button.before') !!} {!! view_render_event('admin.settings.email_template.create.save_button.after') !!}
    {!! view_render_event('admin.email_template.create.form.after') !!} @pushOnce('scripts') @endPushOnce
    ================================================ FILE: packages/Webkul/Admin/src/Resources/views/settings/email-templates/edit.blade.php ================================================ @lang('admin::app.settings.email-template.edit.title') {!! view_render_event('admin.settings.email_template.edit.form.before') !!}
    {!! view_render_event('admin.settings.email_template.edit.breadcrumbs.before') !!} {!! view_render_event('admin.settings.email_template.edit.breadcrumbs.after') !!}
    @lang('admin::app.settings.email-template.edit.title')
    {!! view_render_event('admin.settings.email_template.edit.save_button.before') !!} {!! view_render_event('admin.settings.email_template.edit.save_button.before') !!}
    {!! view_render_event('admin.settings.email_template.edit.form.after') !!} @pushOnce('scripts') @endPushOnce
    ================================================ FILE: packages/Webkul/Admin/src/Resources/views/settings/email-templates/index.blade.php ================================================ @lang('admin::app.settings.email-template.index.title')
    {!! view_render_event('admin.settings.email_template.index.breadcrumbs.before') !!} {!! view_render_event('admin.settings.email_template.index.breadcrumbs.after') !!}
    @lang('admin::app.settings.email-template.index.title')
    {!! view_render_event('admin.settings.email_template.index.create_button.before') !!} @if (bouncer()->hasPermission('settings.automation.email_templates.create')) @lang('admin::app.settings.email-template.index.create-btn') @endif {!! view_render_event('admin.settings.email_template.index.create_button.after') !!}
    {!! view_render_event('admin.settings.email_template.index.datagrid.before') !!} {!! view_render_event('admin.settings.email_template.index.datagrid.after') !!}
    ================================================ FILE: packages/Webkul/Admin/src/Resources/views/settings/groups/index.blade.php ================================================ @lang('admin::app.settings.groups.index.title')
    {!! view_render_event('admin.settings.groups.index.breadcrumbs.before') !!} {!! view_render_event('admin.settings.groups.index.breadcrumbs.after') !!}
    @lang('admin::app.settings.groups.index.title')
    {!! view_render_event('admin.settings.groups.index.breadcrumbs.after') !!} @if (bouncer()->hasPermission('settings.user.groups.create')) @endif {!! view_render_event('admin.settings.groups.index.create_button.after') !!}
    @pushOnce('scripts') @endPushOnce
    ================================================ FILE: packages/Webkul/Admin/src/Resources/views/settings/index.blade.php ================================================ @lang('admin::app.settings.title')

    @lang('admin::app.settings.title')

    @foreach (menu()->getAdminMenuByKey('settings')->getChildren() as $setting)

    {{ $setting->getName() }}

    {{ $setting->getInfo() }}

    @foreach ($setting->getChildren() as $key => $child)

    {{ $child->getName() }}

    {{ $child->getInfo() }}

    @endforeach
    @endforeach
    ================================================ FILE: packages/Webkul/Admin/src/Resources/views/settings/marketing/campaigns/index.blade.php ================================================ @lang('admin::app.settings.marketing.campaigns.index.title')
    {!! view_render_event('admin.settings.marketing.campaigns.index.breadcrumbs.before') !!} {!! view_render_event('admin.settings.marketing.campaigns.index.breadcrumbs.after') !!}
    @lang('admin::app.settings.marketing.campaigns.index.title')
    {!! view_render_event('admin.settings.marketing.campaigns.index.breadcrumbs.after') !!} @if (bouncer()->hasPermission('settings.automation.campaigns.create')) @endif {!! view_render_event('admin.settings.marketing.campaigns.index.create_button.after') !!}
    @pushOnce('scripts') @endPushOnce
    ================================================ FILE: packages/Webkul/Admin/src/Resources/views/settings/marketing/events/index.blade.php ================================================ @lang('admin::app.settings.marketing.events.index.title')
    {!! view_render_event('admin.settings.marketing.events.index.breadcrumbs.before') !!} {!! view_render_event('admin.settings.marketing.events.index.breadcrumbs.after') !!}
    @lang('admin::app.settings.marketing.events.index.title')
    {!! view_render_event('admin.settings.marketing.events.index.breadcrumbs.after') !!} @if (bouncer()->hasPermission('settings.automation.events.create')) @endif {!! view_render_event('admin.settings.marketing.events.index.create_button.after') !!}
    @pushOnce('scripts') @endPushOnce
    ================================================ FILE: packages/Webkul/Admin/src/Resources/views/settings/pipelines/create.blade.php ================================================ @lang('admin::app.settings.pipelines.create.title') {!! view_render_event('admin.settings.pipelines.create.form.before') !!}
    {!! view_render_event('admin.settings.pipelines.create.breadcrumbs.before') !!} {!! view_render_event('admin.settings.pipelines.create.breadcrumbs.after') !!}
    @lang('admin::app.settings.pipelines.create.title')
    {!! view_render_event('admin.settings.pipelines.create.save_button.before') !!} {!! view_render_event('admin.settings.pipelines.create.save_button.after') !!}
    {!! view_render_event('admin.settings.pipelines.create.form.name.before') !!} @lang('admin::app.settings.pipelines.create.name') {!! view_render_event('admin.settings.pipelines.create.form.name.after') !!} {!! view_render_event('admin.settings.pipelines.create.form.rotten_days.before') !!} @lang('admin::app.settings.pipelines.create.rotten-days') {!! view_render_event('admin.settings.pipelines.create.form.rotten_days.after') !!} {!! view_render_event('admin.settings.pipelines.create.form.is_default.before') !!} @lang('admin::app.settings.pipelines.create.mark-as-default') {!! view_render_event('admin.settings.pipelines.create.form.is_default.after') !!}
    {!! view_render_event('admin.settings.pipelines.create.form.stages.before') !!} {!! view_render_event('admin.settings.pipelines.create.form.stages.after') !!}
    {!! view_render_event('admin.settings.pipelines.create.form.after') !!} @pushOnce('scripts') @endPushOnce
    ================================================ FILE: packages/Webkul/Admin/src/Resources/views/settings/pipelines/edit.blade.php ================================================ @lang('admin::app.settings.pipelines.edit.title') {!! view_render_event('admin.settings.pipelines.edit.form.before', ['pipeline' => $pipeline]) !!}
    {!! view_render_event('admin.settings.pipelines.edit.breadcrumbs.before', ['pipeline' => $pipeline]) !!} {!! view_render_event('admin.settings.pipelines.edit.breadcrumbs.after', ['pipeline' => $pipeline]) !!}
    @lang('admin::app.settings.pipelines.edit.title')
    {!! view_render_event('admin.settings.pipelines.edit.save_button.before', ['pipeline' => $pipeline]) !!} {!! view_render_event('admin.settings.pipelines.edit.save_button.after', ['pipeline' => $pipeline]) !!}
    {!! view_render_event('admin.settings.pipelines.edit.form.name.before', ['pipeline' => $pipeline]) !!} @lang('admin::app.settings.pipelines.edit.name') {!! view_render_event('admin.settings.pipelines.edit.form.name.after', ['pipeline' => $pipeline]) !!} {!! view_render_event('admin.settings.pipelines.edit.form.rotten_days.before', ['pipeline' => $pipeline]) !!} @lang('admin::app.settings.pipelines.edit.rotten-days') {!! view_render_event('admin.settings.pipelines.edit.form.rotten_days.after', ['pipeline' => $pipeline]) !!} {!! view_render_event('admin.settings.pipelines.edit.form.is_default.before', ['pipeline' => $pipeline]) !!} @lang('admin::app.settings.pipelines.edit.mark-as-default') {!! view_render_event('admin.settings.pipelines.edit.form.is_default.after', ['pipeline' => $pipeline]) !!}
    {!! view_render_event('admin.settings.pipelines.edit.form.after', ['pipeline' => $pipeline]) !!} @pushOnce('scripts') @endPushOnce
    ================================================ FILE: packages/Webkul/Admin/src/Resources/views/settings/pipelines/index.blade.php ================================================ @lang('admin::app.settings.pipelines.index.title')
    {!! view_render_event('admin.settings.pipelines.index.breadcrumbs.before') !!} {!! view_render_event('admin.settings.pipelines.index.breadcrumbs.after') !!}
    @lang('admin::app.settings.pipelines.index.title')
    {!! view_render_event('admin.settings.pipelines.index.create_button.before') !!} @if (bouncer()->hasPermission('settings.lead.pipelines.create')) @lang('admin::app.settings.pipelines.index.create-btn') @endif {!! view_render_event('admin.settings.pipelines.index.create_button.after') !!}
    {!! view_render_event('admin.settings.pipelines.index.datagrid.before') !!} {!! view_render_event('admin.settings.pipelines.index.datagrid.after') !!}
    ================================================ FILE: packages/Webkul/Admin/src/Resources/views/settings/roles/create.blade.php ================================================ @lang('admin::app.settings.roles.create.title') {!! view_render_event('admin.settings.roles.create.form.before') !!}
    {!! view_render_event('admin.settings.roles.create.breadcrumbs.before') !!} {!! view_render_event('admin.settings.roles.create.breadcrumbs.after') !!}
    @lang('admin::app.settings.roles.create.title')
    {!! view_render_event('admin.settings.roles.create.create_button.before') !!} @if (bouncer()->hasPermission('settings.user.roles.create')) @endif {!! view_render_event('admin.settings.roles.create.create_button.after') !!}
    {!! view_render_event('admin.settings.roles.create.content.before') !!}
    {!! view_render_event('admin.settings.roles.create.content.left.before') !!}
    {!! view_render_event('admin.settings.roles.create.content.left.after') !!} {!! view_render_event('admin.settings.roles.create.content.right.before') !!}
    {!! view_render_event('admin.settings.roles.create.accordion.general.before') !!}

    @lang('admin::app.settings.roles.create.general')

    {!! view_render_event('admin.settings.roles.create.form.name.before') !!} @lang('admin::app.settings.roles.create.name') {!! view_render_event('admin.settings.roles.create.form.name.after') !!} {!! view_render_event('admin.settings.roles.create.form.description.before') !!} @lang('admin::app.settings.roles.create.description') {!! view_render_event('admin.settings.roles.create.form.description.after') !!}
    {!! view_render_event('admin.settings.roles.create.accordion.general.after') !!}
    {!! view_render_event('admin.settings.roles.create.content.right.after') !!}
    {!! view_render_event('admin.settings.roles.create.content.after') !!}
    {!! view_render_event('admin.settings.roles.create.form.after') !!} @pushOnce('scripts') @endPushOnce
    ================================================ FILE: packages/Webkul/Admin/src/Resources/views/settings/roles/edit.blade.php ================================================ @lang('admin::app.settings.roles.edit.title') {!! view_render_event('admin.settings.roles.edit.form.before', ['role' => $role]) !!}
    {!! view_render_event('admin.settings.roles.edit.breadcrumbs.before', ['role' => $role]) !!} {!! view_render_event('admin.settings.roles.edit.breadcrumbs.after', ['role' => $role]) !!}
    @lang('admin::app.settings.roles.edit.title')
    {!! view_render_event('admin.settings.roles.edit.save_button.before', ['role' => $role]) !!} @if (bouncer()->hasPermission('settings.user.roles.edit')) @endif {!! view_render_event('admin.settings.roles.edit.save_button.after', ['role' => $role]) !!}
    {!! view_render_event('admin.settings.roles.edit.content.before', ['role' => $role]) !!}
    {!! view_render_event('admin.settings.roles.edit.content.left.before', ['role' => $role]) !!}

    @lang('admin::app.settings.roles.edit.access-control')

    {!! view_render_event('admin.settings.roles.edit.content.left.after', ['role' => $role]) !!} {!! view_render_event('admin.settings.roles.edit.content.right.before', ['role' => $role]) !!}
    {!! view_render_event('admin.settings.roles.edit.accordion.general.before', ['role' => $role]) !!}

    @lang('admin::app.settings.roles.edit.general')

    {!! view_render_event('admin.settings.roles.edit.form.name.before', ['role' => $role]) !!} @lang('admin::app.settings.roles.edit.name') {!! view_render_event('admin.settings.roles.edit.form.name.after', ['role' => $role]) !!} {!! view_render_event('admin.settings.roles.edit.form.description.before', ['role' => $role]) !!} @lang('admin::app.settings.roles.edit.description') {!! view_render_event('admin.settings.roles.edit.form.description.after', ['role' => $role]) !!}
    {!! view_render_event('admin.settings.roles.edit.accordion.general.after', ['role' => $role]) !!}
    {!! view_render_event('admin.settings.roles.edit.content.after', ['role' => $role]) !!}
    {!! view_render_event('admin.settings.roles.edit.form.after', ['role' => $role]) !!} @pushOnce('scripts') @endPushOnce
    ================================================ FILE: packages/Webkul/Admin/src/Resources/views/settings/roles/index.blade.php ================================================ @lang('admin::app.settings.roles.index.title')
    {!! view_render_event('admin.settings.roles.index.breadcrumbs.before') !!} {!! view_render_event('admin.settings.roles.index.breadcrumbs.after') !!}
    @lang('admin::app.settings.roles.index.title')
    {!! view_render_event('admin.settings.roles.index.create_button.before') !!} @if (bouncer()->hasPermission('settings.user.roles.create')) @lang('admin::app.settings.roles.index.create-btn') @endif {!! view_render_event('admin.settings.roles.index.create_button.after') !!}
    ================================================ FILE: packages/Webkul/Admin/src/Resources/views/settings/sources/index.blade.php ================================================ @lang('admin::app.settings.sources.index.title')
    {!! view_render_event('admin.settings.sources.index.breadcrumbs.before') !!} {!! view_render_event('admin.settings.sources.index.breadcrumbs.after') !!}
    @lang('admin::app.settings.sources.index.title')
    {!! view_render_event('admin.settings.sources.index.create_button.before') !!} @if (bouncer()->hasPermission('settings.lead.sources.create'))
    @endif {!! view_render_event('admin.settings.sources.index.create_button.after') !!}
    @pushOnce('scripts') @endPushOnce
    ================================================ FILE: packages/Webkul/Admin/src/Resources/views/settings/tags/index.blade.php ================================================ @lang('admin::app.settings.tags.index.title')
    {!! view_render_event('admin.settings.tags.index.breadcrumbs.before') !!} {!! view_render_event('admin.settings.tags.index.breadcrumbs.after') !!}
    @lang('admin::app.settings.tags.index.title')
    {!! view_render_event('admin.settings.tags.index.create_button.before') !!} @if (bouncer()->hasPermission('settings.other_settings.tags.create'))
    @endif {!! view_render_event('admin.settings.tags.index.create_button.after') !!}
    @pushOnce('scripts') @endPushOnce
    ================================================ FILE: packages/Webkul/Admin/src/Resources/views/settings/types/index.blade.php ================================================ @lang('admin::app.settings.types.index.title')
    @lang('admin::app.settings.types.index.title')
    {!! view_render_event('admin.settings.types.index.create_button.before') !!}
    @if (bouncer()->hasPermission('settings.lead.types.create')) @endif
    {!! view_render_event('admin.settings.types.index.create_button.after') !!}
    @pushOnce('scripts') @endPushOnce
    ================================================ FILE: packages/Webkul/Admin/src/Resources/views/settings/users/index.blade.php ================================================ @lang('admin::app.settings.users.index.title')
    @lang('admin::app.settings.users.index.title')
    {!! view_render_event('admin.settings.users.index.create_button.before') !!} @if (bouncer()->hasPermission('settings.user.users.create'))
    @endif {!! view_render_event('admin.settings.users.index.create_button.after') !!}
    @pushOnce('scripts') @endPushOnce
    ================================================ FILE: packages/Webkul/Admin/src/Resources/views/settings/warehouses/create.blade.php ================================================ @lang('admin::app.settings.warehouses.create.title') {!! view_render_event('admin.settings.warehouses.create.form.before') !!}
    {!! view_render_event('admin.settings.warehouses.create.breadcrumbs.before') !!} {!! view_render_event('admin.settings.warehouses.create.breadcrumbs.after') !!}
    @lang('admin::app.settings.warehouses.create.title')
    {!! view_render_event('admin.settings.warehouses.create.save_button.before') !!} @if (bouncer()->hasPermission('settings.inventory.warehouse.create')) @endif {!! view_render_event('admin.settings.warehouses.create.save_button.after') !!}

    @lang('admin::app.settings.warehouses.create.contact-info')

    {!! view_render_event('admin.settings.warehouses.create.left.form_controls.before') !!} {!! view_render_event('admin.settings.warehouses.create.left.form_controls.after') !!}

    @lang('admin::app.settings.roles.create.general')

    {!! view_render_event('admin.settings.warehouses.create.right.form_controls.before') !!} {!! view_render_event('admin.settings.warehouses.create.right.form_controls.after') !!}
    {!! view_render_event('admin.settings.warehouses.create.form.after') !!}
    ================================================ FILE: packages/Webkul/Admin/src/Resources/views/settings/warehouses/edit.blade.php ================================================ @lang('admin::app.settings.warehouses.edit.title') {!! view_render_event('admin.settings.warehouses.edit.form.before', ['warehouse' => $warehouse]) !!}
    {!! view_render_event('admin.settings.warehouses.edit.breadcrumbs.before') !!} {!! view_render_event('admin.settings.warehouses.edit.breadcrumbs.after') !!}
    @lang('admin::app.settings.warehouses.edit.title')
    {!! view_render_event('admin.settings.warehouse.edit.save_button.after') !!} @if (bouncer()->hasPermission('settings.inventory.warehouse.edit')) @endif {!! view_render_event('admin.settings.warehouses.edit.save_button.before') !!}

    @lang('admin::app.settings.warehouses.edit.contact-info')

    {!! view_render_event('admin.settings.warehouses.edit.left.form_controls.before', ['warehouse' => $warehouse]) !!} {!! view_render_event('admin.settings.warehouses.edit.left.form_controls.after', ['warehouse' => $warehouse]) !!}

    @lang('admin::app.settings.roles.create.general')

    {!! view_render_event('admin.settings.warehouses.edit.right.form_controls.before', ['warehouse' => $warehouse]) !!} {!! view_render_event('admin.settings.warehouses.edit.right.form_controls.after', ['warehouse' => $warehouse]) !!}
    {!! view_render_event('admin.settings.warehouses.edit.form.after', ['warehouse' => $warehouse]) !!}
    ================================================ FILE: packages/Webkul/Admin/src/Resources/views/settings/warehouses/index.blade.php ================================================ @lang('admin::app.settings.warehouses.index.title')
    @lang('admin::app.settings.warehouses.index.title')
    {!! view_render_event('admin.settings.warehouses.index.create_button.before') !!} @if (bouncer()->hasPermission('settings.inventory.warehouse.create')) @lang('admin::app.settings.warehouses.index.create-btn') @endif {!! view_render_event('admin.settings.warehouses.index.create_button.after') !!}
    {!! view_render_event('admin.settings.warehouses.index.datagrid.before') !!} {!! view_render_event('admin.settings.warehouses.index.datagrid.after') !!}
    ================================================ FILE: packages/Webkul/Admin/src/Resources/views/settings/warehouses/view/contact-information.blade.php ================================================ {!! view_render_event('admin.leads.view.person.before', ['warehouse' => $warehouse]) !!}

    @lang('admin::app.settings.warehouses.view.contact-information.title')

    {!! view_render_event('admin.leads.view.person.attributes.view.before', ['warehouse' => $warehouse]) !!} {!! view_render_event('admin.leads.view.person.attributes.view.after', ['warehouse' => $warehouse]) !!}
    {!! view_render_event('admin.leads.view.person.after', ['warehouse' => $warehouse]) !!} ================================================ FILE: packages/Webkul/Admin/src/Resources/views/settings/warehouses/view/general-information.blade.php ================================================ {!! view_render_event('admin.leads.view.person.before', ['warehouse' => $warehouse]) !!}

    @lang('admin::app.settings.warehouses.view.general-information.title')

    {!! view_render_event('admin.leads.view.person.attributes.view.before', ['warehouse' => $warehouse]) !!} {!! view_render_event('admin.leads.view.person.attributes.view.after', ['warehouse' => $warehouse]) !!}
    {!! view_render_event('admin.leads.view.person.after', ['warehouse' => $warehouse]) !!} ================================================ FILE: packages/Webkul/Admin/src/Resources/views/settings/warehouses/view/locations.blade.php ================================================ @pushOnce('scripts') @endPushOnce ================================================ FILE: packages/Webkul/Admin/src/Resources/views/settings/warehouses/view.blade.php ================================================ {{ strip_tags($warehouse->name) }}
    {!! view_render_event('admin.settings.warehouses.view.left.before', ['warehouse' => $warehouse]) !!}
    {!! view_render_event('admin.settings.warehouses.view.left.tags.before', ['warehouse' => $warehouse]) !!} {!! view_render_event('admin.settings.warehouses.view.left.tags.after', ['warehouse' => $warehouse]) !!} {!! view_render_event('admin.settings.warehouses.view.left.title.before', ['warehouse' => $warehouse]) !!}

    {{ $warehouse->name }}

    {!! view_render_event('admin.settings.warehouses.view.left.title.after', ['warehouse' => $warehouse]) !!} {!! view_render_event('admin.settings.warehouses.view.left.actions.before', ['warehouse' => $warehouse]) !!}
    {!! view_render_event('admin.settings.warehouses.view.left.actions.file.before', ['warehouse' => $warehouse]) !!} {!! view_render_event('admin.settings.warehouses.view.left.actions.file.after', ['warehouse' => $warehouse]) !!} {!! view_render_event('admin.settings.warehouses.view.left.actions.note.before', ['warehouse' => $warehouse]) !!} {!! view_render_event('admin.settings.warehouses.view.left.actions.note.after', ['warehouse' => $warehouse]) !!} {!! view_render_event('admin.settings.warehouses.view.left.actions.activity.before', ['warehouse' => $warehouse]) !!} {!! view_render_event('admin.settings.warehouses.view.left.actions.activity.after', ['warehouse' => $warehouse]) !!}
    {!! view_render_event('admin.settings.warehouses.view.left.actions.after', ['warehouse' => $warehouse]) !!}
    @include ('admin::settings.warehouses.view.general-information') @include ('admin::settings.warehouses.view.contact-information')
    {!! view_render_event('admin.settings.warehouses.view.left.after', ['warehouse' => $warehouse]) !!} {!! view_render_event('admin.settings.warehouses.view.right.before', ['warehouse' => $warehouse]) !!}
    {!! view_render_event('admin.settings.warehouses.view.right.attributes.before', ['warehouse' => $warehouse]) !!} @include ('admin::settings.warehouses.view.locations') {!! view_render_event('admin.settings.warehouses.view.right.attributes.after', ['warehouse' => $warehouse]) !!}
    {!! view_render_event('admin.warehouse.view.right.after', ['warehouse' => $warehouse]) !!}
    ================================================ FILE: packages/Webkul/Admin/src/Resources/views/settings/web-forms/create.blade.php ================================================ @lang('admin::app.settings.webforms.create.title')
    {!! view_render_event('admin.settings.webform.create.breadcrumbs.before') !!} {!! view_render_event('admin.settings.webform.create.breadcrumbs.after') !!}
    @lang('admin::app.settings.webforms.create.title')
    {!! view_render_event('admin.settings.webform.create.save_button.before') !!} {!! view_render_event('admin.settings.webform.create.save_button.after') !!}
    @pushOnce('scripts') @endPushOnce
    ================================================ FILE: packages/Webkul/Admin/src/Resources/views/settings/web-forms/edit.blade.php ================================================ @lang('admin::app.settings.webforms.edit.title')
    {!! view_render_event('admin.settings.webform.edit.breadcrumbs.before', ['webform' => $webForm]) !!} {!! view_render_event('admin.settings.webform.edit.breadcrumbs.after', ['webform' => $webForm]) !!}
    @lang('admin::app.settings.webforms.edit.title')
    {!! view_render_event('admin.settings.webform.edit.embed_button.before', ['webform' => $webForm]) !!} {!! view_render_event('admin.settings.webform.edit.embed_button.after', ['webform' => $webForm]) !!} {!! view_render_event('admin.settings.webform.edit.preview_button.before', ['webform' => $webForm]) !!} @lang('admin::app.settings.webforms.edit.preview') {!! view_render_event('admin.settings.webform.edit.preview_button.after', ['webform' => $webForm]) !!} {!! view_render_event('admin.settings.webform.edit.save_button.before', ['webform' => $webForm]) !!} {!! view_render_event('admin.settings.webform.edit.save_button.after', ['webform' => $webForm]) !!}
    @pushOnce('scripts') ' }}" /> @lang('admin::app.settings.webforms.edit.copy') {!! view_render_event('admin.settings.webform.edit.modal.form_controls.after', ['webform' => $webForm]) !!} @endPushOnce
    ================================================ FILE: packages/Webkul/Admin/src/Resources/views/settings/web-forms/index.blade.php ================================================ @lang('admin::app.settings.webforms.index.title')
    @lang('admin::app.settings.webforms.index.title')
    @if (bouncer()->hasPermission('admin.settings.web_forms.create')) @endif
    @pushOnce('scripts') @endPushOnce
    ================================================ FILE: packages/Webkul/Admin/src/Resources/views/settings/webhook/create.blade.php ================================================ @php($placeholders = app('\Webkul\Automation\Helpers\Entity')->getEmailTemplatePlaceholders()) @lang('admin::app.settings.webhooks.create.title') {!! view_render_event('admin.settings.webhook.edit.form.before') !!}
    {!! view_render_event('admin.settings.webhook.edit.breadrumbs.before') !!} {!! view_render_event('admin.settings.webhook.edit.breadrumbs.after') !!}
    @lang('admin::app.settings.webhooks.create.title')
    {!! view_render_event('admin.settings.webhook.edit.save_button.before') !!} {!! view_render_event('admin.settings.webhook.edit.save_button.after') !!}
    {!! view_render_event('admin.settings.webhook.edit.form.after') !!} @pushOnce('scripts') @endPushOnce @pushOnce('styles') @endPushOnce
    ================================================ FILE: packages/Webkul/Admin/src/Resources/views/settings/webhook/edit.blade.php ================================================ @php($placeholders = app('\Webkul\Automation\Helpers\Entity')->getEmailTemplatePlaceholders()) @lang('admin::app.settings.webhooks.edit.title') {!! view_render_event('admin.settings.webhook.edit.form.before', ['webhook' => $webhook]) !!}
    {!! view_render_event('admin.settings.webhook.edit.breadcrumbs.before', ['webhook' => $webhook]) !!} {!! view_render_event('admin.settings.webhook.edit.breadcrumbs.after', ['webhook' => $webhook]) !!}
    @lang('admin::app.settings.webhooks.edit.title')
    {!! view_render_event('admin.settings.webhook.edit.save_button.before', ['webhook' => $webhook]) !!} {!! view_render_event('admin.settings.webhook.edit.save_button.after', ['webhook' => $webhook]) !!}
    {!! view_render_event('admin.settings.webhook.edit.form.after', ['webhook' => $webhook]) !!} @pushOnce('scripts') @endPushOnce @pushOnce('styles') @endPushOnce
    ================================================ FILE: packages/Webkul/Admin/src/Resources/views/settings/webhook/index.blade.php ================================================ @lang('admin::app.settings.webhooks.index.title')
    {!! view_render_event('admin.settings.webhooks.breadcrumbs.datagrid.before') !!} {!! view_render_event('admin.settings.webhooks.breadcrumbs.datagrid.after') !!}
    @lang('admin::app.settings.webhooks.index.title')
    {!! view_render_event('admin.settings.webhooks.create_button.datagrid.before') !!} @lang('admin::app.settings.webhooks.index.create-btn') {!! view_render_event('admin.settings.webhooks.create_button.datagrid.after') !!}
    {!! view_render_event('admin.settings.webhooks.index.datagrid.before') !!} {!! view_render_event('admin.settings.webhooks.index.datagrid.after') !!}
    ================================================ FILE: packages/Webkul/Admin/src/Resources/views/settings/workflows/create.blade.php ================================================ @lang('admin::app.settings.workflows.create.title') {!! view_render_event('admin.settings.workflow.form.before') !!}
    {!! view_render_event('admin.settings.workflow.breadcrumbs.before') !!} {!! view_render_event('admin.settings.webhooks.breadcrumbs.after') !!}
    @lang('admin::app.settings.workflows.create.title')
    {!! view_render_event('admin.settings.workflow.save_button.before') !!} {!! view_render_event('admin.settings.workflow.save_button.after') !!}
    {!! view_render_event('admin.settings.workflow.form.after') !!} @pushOnce('scripts') @endPushOnce @pushOnce('styles') @endPushOnce
    ================================================ FILE: packages/Webkul/Admin/src/Resources/views/settings/workflows/edit.blade.php ================================================ @lang('admin::app.settings.workflows.edit.title') {!! view_render_event('admin.activities.edit.form.before') !!}
    {!! view_render_event('admin.settings.workflows.edit.breadcrumbs.before', ['workflow' => $workflow]) !!} {!! view_render_event('admin.settings.workflows.edit.breadcrumbs.after', ['workflow' => $workflow]) !!}
    @lang('admin::app.settings.workflows.edit.title')
    {!! view_render_event('admin.settings.workflows.edit.save_button.before', ['workflow' => $workflow]) !!} {!! view_render_event('admin.settings.workflows.edit.save_button.after', ['workflow' => $workflow]) !!}
    @pushOnce('scripts') @endPushOnce @pushOnce('styles') @endPushOnce
    ================================================ FILE: packages/Webkul/Admin/src/Resources/views/settings/workflows/index.blade.php ================================================ @lang('admin::app.settings.workflows.index.title')
    {!! view_render_event('admin.settings.workflows.index.breadcrumbs.before') !!} {!! view_render_event('admin.settings.workflows.index.breadcrumbs.after') !!}
    @lang('admin::app.settings.workflows.index.title')
    {!! view_render_event('admin.settings.workflows.index.create_button.before') !!} @if (bouncer()->hasPermission('settings.automation.workflows.create')) @lang('admin::app.settings.workflows.index.create-btn') @endif {!! view_render_event('admin.settings.workflows.index.create_button.after') !!}
    {!! view_render_event('admin.settings.workflows.index.datagrid.before') !!} {!! view_render_event('admin.settings.workflows.index.datagrid.after') !!}
    ================================================ FILE: packages/Webkul/Admin/src/Resources/views/user/account/edit.blade.php ================================================ @lang('admin::app.account.edit.title') {!! view_render_event('admin.user.account.form.before', ['user' => $user]) !!}
    {!! view_render_event('admin.user.account.breadcrumbs.before', ['user' => $user]) !!} {!! view_render_event('admin.user.account.breadcrumbs.after', ['user' => $user]) !!}
    {!! view_render_event('admin.user.account.title.before', ['user' => $user]) !!} @lang('admin::app.account.edit.title') {!! view_render_event('admin.user.account.title.after', ['user' => $user]) !!}
    {!! view_render_event('admin.user.account.save_btn.before', ['user' => $user]) !!} {!! view_render_event('admin.user.account.save_btn.after', ['user' => $user]) !!}
    {!! view_render_event('admin.user.account.left.before', ['user' => $user]) !!}

    @lang('admin::app.account.edit.general')

    @lang('admin::app.account.edit.upload-image-info')

    @lang('admin::app.account.edit.name') @lang('admin::app.account.edit.email')
    {!! view_render_event('admin.user.account.left.after', ['user' => $user]) !!} {!! view_render_event('admin.user.account.right.before', ['user' => $user]) !!}

    @lang('admin::app.account.edit.change-password')

    {!! view_render_event('admin.user.current_password.before', ['user' => $user]) !!} @lang('admin::app.account.edit.current-password') {!! view_render_event('admin.user.current_password.after', ['user' => $user]) !!} {!! view_render_event('admin.user.password.before', ['user' => $user]) !!} @lang('admin::app.account.edit.password') {!! view_render_event('admin.user.password.after', ['user' => $user]) !!} {!! view_render_event('admin.user.confirm-password.before', ['user' => $user]) !!} @lang('admin::app.account.edit.confirm-password') {!! view_render_event('admin.user.confirm-password.after', ['user' => $user]) !!}
    {!! view_render_event('admin.user.account.right.after', ['user' => $user]) !!}
    {!! view_render_event('admin.user.account.form.after') !!}
    ================================================ FILE: packages/Webkul/Admin/src/Routes/Admin/activities-routes.php ================================================ prefix('activities')->group(function () { Route::get('', 'index')->name('admin.activities.index'); Route::get('get', 'get')->name('admin.activities.get'); Route::post('create', 'store')->name('admin.activities.store'); Route::get('edit/{id}', 'edit')->name('admin.activities.edit'); Route::put('edit/{id}', 'update')->name('admin.activities.update'); Route::get('download/{id}', 'download')->name('admin.activities.file_download'); Route::delete('{id}', 'destroy')->name('admin.activities.delete'); Route::post('mass-update', 'massUpdate')->name('admin.activities.mass_update'); Route::post('mass-destroy', 'massDestroy')->name('admin.activities.mass_delete'); }); ================================================ FILE: packages/Webkul/Admin/src/Routes/Admin/auth-routes.php ================================================ group(function () { /** * Redirect route. */ Route::get('/', [Controller::class, 'redirectToLogin']); /** * Session routes. */ Route::controller(SessionController::class)->group(function () { Route::prefix('login')->group(function () { Route::get('', 'create')->name('admin.session.create'); Route::post('', 'store')->name('admin.session.store'); }); Route::middleware(['user'])->group(function () { Route::delete('logout', 'destroy')->name('admin.session.destroy'); }); }); /** * Forgot password routes. */ Route::controller(ForgotPasswordController::class)->prefix('forget-password')->group(function () { Route::get('', 'create')->name('admin.forgot_password.create'); Route::post('', 'store')->name('admin.forgot_password.store'); }); /** * Reset password routes. */ Route::controller(ResetPasswordController::class)->prefix('reset-password')->group(function () { Route::get('{token}', 'create')->name('admin.reset_password.create'); Route::post('', 'store')->name('admin.reset_password.store'); }); }); ================================================ FILE: packages/Webkul/Admin/src/Routes/Admin/configuration-routes.php ================================================ prefix('configuration')->group(function () { Route::get('search', 'search')->name('admin.configuration.search'); Route::prefix('{slug?}/{slug2?}')->group(function () { Route::get('', 'index')->name('admin.configuration.index'); Route::post('', 'store')->name('admin.configuration.store'); Route::get('{path}', 'download')->name('admin.configuration.download'); }); }); ================================================ FILE: packages/Webkul/Admin/src/Routes/Admin/contacts-routes.php ================================================ group(function () { /** * Persons routes. */ Route::controller(PersonController::class)->prefix('persons')->group(function () { Route::get('', 'index')->name('admin.contacts.persons.index'); Route::get('create', 'create')->name('admin.contacts.persons.create'); Route::post('create', 'store')->name('admin.contacts.persons.store'); Route::get('view/{id}', 'show')->name('admin.contacts.persons.view'); Route::get('edit/{id}', 'edit')->name('admin.contacts.persons.edit'); Route::put('edit/{id}', 'update')->name('admin.contacts.persons.update'); Route::get('search', 'search')->name('admin.contacts.persons.search'); Route::middleware(['throttle:100,60'])->delete('{id}', 'destroy')->name('admin.contacts.persons.delete'); Route::post('mass-destroy', 'massDestroy')->name('admin.contacts.persons.mass_delete'); /** * Tag routes. */ Route::controller(TagController::class)->prefix('{id}/tags')->group(function () { Route::post('', 'attach')->name('admin.contacts.persons.tags.attach'); Route::delete('', 'detach')->name('admin.contacts.persons.tags.detach'); }); /** * Activity routes. */ Route::controller(ActivityController::class)->prefix('{id}/activities')->group(function () { Route::get('', 'index')->name('admin.contacts.persons.activities.index'); }); }); /** * Organization routes. */ Route::controller(OrganizationController::class)->prefix('organizations')->group(function () { Route::get('', 'index')->name('admin.contacts.organizations.index'); Route::get('create', 'create')->name('admin.contacts.organizations.create'); Route::post('create', 'store')->name('admin.contacts.organizations.store'); Route::get('edit/{id?}', 'edit')->name('admin.contacts.organizations.edit'); Route::put('edit/{id}', 'update')->name('admin.contacts.organizations.update'); Route::delete('{id}', 'destroy')->name('admin.contacts.organizations.delete'); Route::put('mass-destroy', 'massDestroy')->name('admin.contacts.organizations.mass_delete'); }); }); ================================================ FILE: packages/Webkul/Admin/src/Routes/Admin/leads-routes.php ================================================ prefix('leads')->group(function () { Route::get('', 'index')->name('admin.leads.index'); Route::get('create', 'create')->name('admin.leads.create'); Route::post('create', 'store')->name('admin.leads.store'); Route::post('create-by-ai', 'createByAI')->name('admin.leads.create_by_ai'); Route::get('view/{id}', 'view')->name('admin.leads.view'); Route::get('edit/{id}', 'edit')->name('admin.leads.edit'); Route::put('edit/{id}', 'update')->name('admin.leads.update'); Route::put('attributes/edit/{id}', 'updateAttributes')->name('admin.leads.attributes.update'); Route::put('stage/edit/{id}', 'updateStage')->name('admin.leads.stage.update'); Route::get('search', 'search')->name('admin.leads.search'); Route::delete('{id}', 'destroy')->name('admin.leads.delete'); Route::post('mass-update', 'massUpdate')->name('admin.leads.mass_update'); Route::post('mass-destroy', 'massDestroy')->name('admin.leads.mass_delete'); Route::get('get/{pipeline_id?}', 'get')->name('admin.leads.get'); Route::delete('product/{lead_id}', 'removeProduct')->name('admin.leads.product.remove'); Route::put('product/{lead_id}', 'addProduct')->name('admin.leads.product.add'); Route::get('kanban/look-up', [LeadController::class, 'kanbanLookup'])->name('admin.leads.kanban.look_up'); Route::controller(ActivityController::class)->prefix('{id}/activities')->group(function () { Route::get('', 'index')->name('admin.leads.activities.index'); }); Route::controller(TagController::class)->prefix('{id}/tags')->group(function () { Route::post('', 'attach')->name('admin.leads.tags.attach'); Route::delete('', 'detach')->name('admin.leads.tags.detach'); }); Route::controller(EmailController::class)->prefix('{id}/emails')->group(function () { Route::post('', 'store')->name('admin.leads.emails.store'); Route::delete('', 'detach')->name('admin.leads.emails.detach'); }); Route::controller(QuoteController::class)->prefix('{id}/quotes')->group(function () { Route::delete('{quote_id?}', 'delete')->name('admin.leads.quotes.delete'); }); }); ================================================ FILE: packages/Webkul/Admin/src/Routes/Admin/mail-routes.php ================================================ middleware('sanitize_url')->group(function () { Route::controller(EmailController::class)->group(function () { Route::post('create', 'store')->name('admin.mail.store'); Route::put('edit/{id}', 'update')->name('admin.mail.update'); Route::get('attachment-download/{id?}', 'download')->name('admin.mail.attachment_download'); Route::get('{route?}', 'index')->name('admin.mail.index'); Route::get('{route}/{id}', 'view')->name('admin.mail.view'); Route::delete('{id}', 'destroy')->name('admin.mail.delete'); Route::post('mass-update', 'massUpdate')->name('admin.mail.mass_update'); Route::post('mass-destroy', 'massDestroy')->name('admin.mail.mass_delete'); Route::post('inbound-parse', 'inboundParse')->name('admin.mail.inbound_parse')->withoutMiddleware('user'); }); Route::controller(TagController::class)->prefix('{id}/tags')->group(function () { Route::post('', 'attach')->name('admin.mail.tags.attach'); Route::delete('', 'detach')->name('admin.mail.tags.detach'); }); }); ================================================ FILE: packages/Webkul/Admin/src/Routes/Admin/products-routes.php ================================================ ['user']], function () { Route::controller(ProductController::class)->prefix('products')->group(function () { Route::get('', 'index')->name('admin.products.index'); Route::get('create', 'create')->name('admin.products.create'); Route::post('create', 'store')->name('admin.products.store'); Route::get('view/{id}', 'view')->name('admin.products.view'); Route::get('edit/{id}', 'edit')->name('admin.products.edit'); Route::put('edit/{id}', 'update')->name('admin.products.update'); Route::get('search', 'search')->name('admin.products.search'); Route::get('{id}/warehouses', 'warehouses')->name('admin.products.warehouses'); Route::post('{id}/inventories/{warehouseId?}', 'storeInventories')->name('admin.products.inventories.store'); Route::delete('{id}', 'destroy')->name('admin.products.delete'); Route::post('mass-destroy', 'massDestroy')->name('admin.products.mass_delete'); Route::controller(ActivityController::class)->prefix('{id}/activities')->group(function () { Route::get('', 'index')->name('admin.products.activities.index'); }); Route::controller(TagController::class)->prefix('{id}/tags')->group(function () { Route::post('', 'attach')->name('admin.products.tags.attach'); Route::delete('', 'detach')->name('admin.products.tags.detach'); }); }); }); ================================================ FILE: packages/Webkul/Admin/src/Routes/Admin/quote-routes.php ================================================ prefix('quotes')->group(function () { Route::get('', 'index')->name('admin.quotes.index'); Route::get('create/{lead_id?}', 'create')->name('admin.quotes.create'); Route::post('create', 'store')->name('admin.quotes.store'); Route::get('edit/{id?}', 'edit')->name('admin.quotes.edit'); Route::put('edit/{id}', 'update')->name('admin.quotes.update'); Route::get('print/{id?}', 'print')->name('admin.quotes.print'); Route::delete('{id}', 'destroy')->name('admin.quotes.delete'); Route::get('search', 'search')->name('admin.quotes.search'); Route::post('mass-destroy', 'massDestroy')->name('admin.quotes.mass_delete'); }); ================================================ FILE: packages/Webkul/Admin/src/Routes/Admin/rest-routes.php ================================================ prefix('dashboard')->group(function () { Route::get('', 'index')->name('admin.dashboard.index'); Route::get('stats', 'stats')->name('admin.dashboard.stats'); }); /** * DataGrid routes. */ Route::prefix('datagrid')->group(function () { /** * Saved filter routes. */ Route::controller(SavedFilterController::class)->prefix('datagrid/saved-filters')->group(function () { Route::post('', 'store')->name('admin.datagrid.saved_filters.store'); Route::get('', 'get')->name('admin.datagrid.saved_filters.index'); Route::put('{id}', 'update')->name('admin.datagrid.saved_filters.update'); Route::delete('{id}', 'destroy')->name('admin.datagrid.saved_filters.destroy'); }); /** * Lookup routes. */ Route::get('datagrid/look-up', [DataGridController::class, 'lookUp'])->name('admin.datagrid.look_up'); }); /** * Tinymce file upload handler. */ Route::post('tinymce/upload', [TinyMCEController::class, 'upload'])->name('admin.tinymce.upload'); /** * User profile routes. */ Route::controller(AccountController::class)->prefix('account')->group(function () { Route::get('', 'edit')->name('admin.user.account.edit'); Route::put('update', 'update')->name('admin.user.account.update'); }); ================================================ FILE: packages/Webkul/Admin/src/Routes/Admin/settings-routes.php ================================================ group(function () { /** * Settings routes. */ Route::controller(SettingController::class)->prefix('settings')->group(function () { Route::get('', 'index')->name('admin.settings.index'); Route::get('search', 'search')->name('admin.settings.search'); }); /** * Groups routes. */ Route::controller(GroupController::class)->prefix('groups')->group(function () { Route::get('', 'index')->name('admin.settings.groups.index'); Route::post('create', 'store')->name('admin.settings.groups.store'); Route::get('edit/{id}', 'edit')->name('admin.settings.groups.edit'); Route::put('edit/{id}', 'update')->name('admin.settings.groups.update'); Route::delete('{id}', 'destroy')->name('admin.settings.groups.delete'); }); /** * Type routes. */ Route::controller(TypeController::class)->prefix('types')->group(function () { Route::get('', 'index')->name('admin.settings.types.index'); Route::post('create', 'store')->name('admin.settings.types.store'); Route::get('edit/{id?}', 'edit')->name('admin.settings.types.edit'); Route::put('edit/{id}', 'update')->name('admin.settings.types.update'); Route::delete('{id}', 'destroy')->name('admin.settings.types.delete'); }); /** * Roles routes. */ Route::controller(RoleController::class)->prefix('roles')->group(function () { Route::get('', 'index')->name('admin.settings.roles.index'); Route::get('create', 'create')->name('admin.settings.roles.create'); Route::post('create', 'store')->name('admin.settings.roles.store'); Route::get('edit/{id}', 'edit')->name('admin.settings.roles.edit'); Route::put('edit/{id}', 'update')->name('admin.settings.roles.update'); Route::delete('{id}', 'destroy')->name('admin.settings.roles.delete'); }); /** * WebForms Routes. */ Route::controller(WebFormController::class)->prefix('web-forms')->group(function () { Route::group(['middleware' => ['user']], function () { Route::get('', 'index')->name('admin.settings.web_forms.index'); Route::get('create', 'create')->name('admin.settings.web_forms.create'); Route::post('create', 'store')->name('admin.settings.web_forms.store'); Route::get('edit/{id?}', 'edit')->name('admin.settings.web_forms.edit'); Route::put('edit/{id}', 'update')->name('admin.settings.web_forms.update'); Route::delete('{id}', 'destroy')->name('admin.settings.web_forms.delete'); }); }); /** * Workflows Routes. */ Route::controller(WorkflowController::class)->prefix('workflows')->group(function () { Route::get('', 'index')->name('admin.settings.workflows.index'); Route::get('create', 'create')->name('admin.settings.workflows.create'); Route::post('create', 'store')->name('admin.settings.workflows.store'); Route::get('edit/{id?}', 'edit')->name('admin.settings.workflows.edit'); Route::put('edit/{id}', 'update')->name('admin.settings.workflows.update'); Route::delete('{id}', 'destroy')->name('admin.settings.workflows.delete'); }); /** * Webhook Routes. */ Route::controller(WebhookController::class)->prefix('webhooks')->group(function () { Route::get('', 'index')->name('admin.settings.webhooks.index'); Route::get('create', 'create')->name('admin.settings.webhooks.create'); Route::post('create', 'store')->name('admin.settings.webhooks.store'); Route::get('edit/{id?}', 'edit')->name('admin.settings.webhooks.edit'); Route::put('edit/{id}', 'update')->name('admin.settings.webhooks.update'); Route::delete('{id}', 'destroy')->name('admin.settings.webhooks.delete'); }); /** * Tags Routes. */ Route::controller(TagController::class)->prefix('tags')->group(function () { Route::get('', 'index')->name('admin.settings.tags.index'); Route::post('create', 'store')->name('admin.settings.tags.store'); Route::get('edit/{id}', 'edit')->name('admin.settings.tags.edit'); Route::put('edit/{id}', 'update')->name('admin.settings.tags.update'); Route::get('search', 'search')->name('admin.settings.tags.search'); Route::delete('{id}', 'destroy')->name('admin.settings.tags.delete'); Route::post('mass-destroy', 'massDestroy')->name('admin.settings.tags.mass_delete'); }); /** * Users Routes. */ Route::controller(UserController::class)->prefix('users')->group(function () { Route::get('', 'index')->name('admin.settings.users.index'); Route::post('create', 'store')->name('admin.settings.users.store'); Route::get('edit/{id?}', 'edit')->name('admin.settings.users.edit'); Route::put('edit/{id}', 'update')->name('admin.settings.users.update'); Route::get('search', 'search')->name('admin.settings.users.search'); Route::delete('{id}', 'destroy')->name('admin.settings.users.delete'); Route::post('mass-update', 'massUpdate')->name('admin.settings.users.mass_update'); Route::post('mass-destroy', 'massDestroy')->name('admin.settings.users.mass_delete'); }); /** * Pipelines Routes. */ Route::controller(PipelineController::class)->prefix('pipelines')->group(function () { Route::get('', 'index')->name('admin.settings.pipelines.index'); Route::get('create', 'create')->name('admin.settings.pipelines.create'); Route::post('create', 'store')->name('admin.settings.pipelines.store'); Route::get('edit/{id?}', 'edit')->name('admin.settings.pipelines.edit'); Route::post('edit/{id}', 'update')->name('admin.settings.pipelines.update'); Route::delete('{id}', 'destroy')->name('admin.settings.pipelines.delete'); }); /** * Sources Routes. */ Route::controller(SourceController::class)->prefix('sources')->group(function () { Route::get('', 'index')->name('admin.settings.sources.index'); Route::post('create', 'store')->name('admin.settings.sources.store'); Route::get('edit/{id?}', 'edit')->name('admin.settings.sources.edit'); Route::put('edit/{id}', 'update')->name('admin.settings.sources.update'); Route::delete('{id}', 'destroy')->name('admin.settings.sources.delete'); }); /** * Attributes Routes. */ Route::controller(AttributeController::class)->prefix('attributes')->group(function () { Route::get('', 'index')->name('admin.settings.attributes.index'); Route::get('check-unique-validation', 'checkUniqueValidation')->name('admin.settings.attributes.check_unique_validation'); Route::get('create', 'create')->name('admin.settings.attributes.create'); Route::post('create', 'store')->name('admin.settings.attributes.store'); Route::get('edit/{id}', 'edit')->name('admin.settings.attributes.edit'); Route::put('edit/{id}', 'update')->name('admin.settings.attributes.update'); Route::get('lookup/{lookup?}', 'lookup')->name('admin.settings.attributes.lookup'); Route::get('lookup-entity/{lookup?}', 'lookupEntity')->name('admin.settings.attributes.lookup_entity'); Route::delete('{id}', 'destroy')->name('admin.settings.attributes.delete'); Route::get('{id}/options', 'getAttributeOptions')->name('admin.settings.attributes.options'); Route::post('mass-update', 'massUpdate')->name('admin.settings.attributes.mass_update'); Route::post('mass-destroy', 'massDestroy')->name('admin.settings.attributes.mass_delete'); Route::get('download', 'download')->name('admin.settings.attributes.download'); }); /** * Warehouses Routes. */ Route::controller(WarehouseController::class)->prefix('warehouses')->group(function () { Route::put('edit/{id}', 'update')->name('admin.settings.warehouses.update'); Route::get('', 'index')->name('admin.settings.warehouses.index'); Route::get('search', 'search')->name('admin.settings.warehouses.search'); Route::get('{id}/products', 'products')->name('admin.settings.warehouses.products.index'); Route::get('create', 'create')->name('admin.settings.warehouses.create'); Route::post('create', 'store')->name('admin.settings.warehouses.store'); Route::get('view/{id}', 'view')->name('admin.settings.warehouses.view'); Route::get('edit/{id?}', 'edit')->name('admin.settings.warehouses.edit'); Route::delete('{id}', 'destroy')->name('admin.settings.warehouses.delete'); Route::controller(WarehouseTagController::class)->prefix('{id}/tags')->group(function () { Route::post('', 'attach')->name('admin.settings.warehouses.tags.attach'); Route::delete('', 'detach')->name('admin.settings.warehouses.tags.detach'); }); Route::controller(ActivityController::class)->prefix('{id}/activities')->group(function () { Route::get('', 'index')->name('admin.settings.warehouse.activities.index'); }); }); /** * Warehouses Location Routes. */ Route::controller(LocationController::class)->prefix('locations')->group(function () { Route::get('search', 'search')->name('admin.settings.locations.search'); Route::post('create', 'store')->name('admin.settings.locations.store'); Route::put('edit/{id}', 'update')->name('admin.settings.locations.update'); Route::delete('{id}', 'destroy')->name('admin.settings.locations.delete'); }); /** * Email Templates Routes. */ Route::controller(EmailTemplateController::class)->prefix('email-templates')->group(function () { Route::get('', 'index')->name('admin.settings.email_templates.index'); Route::get('create', 'create')->name('admin.settings.email_templates.create'); Route::post('create', 'store')->name('admin.settings.email_templates.store'); Route::get('edit/{id?}', 'edit')->name('admin.settings.email_templates.edit'); Route::put('edit/{id}', 'update')->name('admin.settings.email_templates.update'); Route::delete('{id}', 'destroy')->name('admin.settings.email_templates.delete'); }); /** * Events Routes. */ Route::group(['prefix' => 'marketing'], function () { Route::controller(EventController::class)->prefix('events')->group(function () { Route::get('', 'index')->name('admin.settings.marketing.events.index'); Route::post('create', 'store')->name('admin.settings.marketing.events.store'); Route::get('edit/{id?}', 'edit')->name('admin.settings.marketing.events.edit'); Route::put('edit/{id}', 'update')->name('admin.settings.marketing.events.update'); Route::delete('{id}', 'destroy')->name('admin.settings.marketing.events.delete'); Route::post('mass-destroy', 'massDestroy')->name('admin.settings.marketing.events.mass_delete'); }); Route::controller(CampaignsController::class)->prefix('campaigns')->group(function () { Route::get('', 'index')->name('admin.settings.marketing.campaigns.index'); Route::get('events', 'getEvents')->name('admin.settings.marketing.campaigns.events'); Route::get('email-templates', 'getEmailTemplates')->name('admin.settings.marketing.campaigns.email-templates'); Route::post('', 'store')->name('admin.settings.marketing.campaigns.store'); Route::get('{id}', 'show')->name('admin.settings.marketing.campaigns.edit'); Route::put('{id}', 'update')->name('admin.settings.marketing.campaigns.update'); Route::delete('{id}', 'destroy')->name('admin.settings.marketing.campaigns.delete'); Route::post('mass-destroy', 'massDestroy')->name('admin.settings.marketing.campaigns.mass_delete'); }); }); Route::prefix('data-transfer')->group(function () { /** * Import routes. */ Route::controller(ImportController::class)->prefix('imports')->group(function () { Route::get('', 'index')->name('admin.settings.data_transfer.imports.index'); Route::get('create', 'create')->name('admin.settings.data_transfer.imports.create'); Route::post('create', 'store')->name('admin.settings.data_transfer.imports.store'); Route::get('edit/{id}', 'edit')->name('admin.settings.data_transfer.imports.edit'); Route::put('update/{id}', 'update')->name('admin.settings.data_transfer.imports.update'); Route::delete('destroy/{id}', 'destroy')->name('admin.settings.data_transfer.imports.delete'); Route::get('import/{id}', 'import')->name('admin.settings.data_transfer.imports.import'); Route::get('validate/{id}', 'validateImport')->name('admin.settings.data_transfer.imports.validate'); Route::get('start/{id}', 'start')->name('admin.settings.data_transfer.imports.start'); Route::get('link/{id}', 'link')->name('admin.settings.data_transfer.imports.link'); Route::get('index/{id}', 'indexData')->name('admin.settings.data_transfer.imports.index_data'); Route::get('stats/{id}/{state?}', 'stats')->name('admin.settings.data_transfer.imports.stats'); Route::get('download-sample/{sample?}', 'downloadSample')->name('admin.settings.data_transfer.imports.download_sample'); Route::get('download/{id}', 'download')->name('admin.settings.data_transfer.imports.download'); Route::get('download-error-report/{id}', 'downloadErrorReport')->name('admin.settings.data_transfer.imports.download_error_report'); }); }); }); ================================================ FILE: packages/Webkul/Admin/src/Routes/Admin/web.php ================================================ name('krayin.home'); ================================================ FILE: packages/Webkul/Admin/src/Traits/ProvideDropdownOptions.php ================================================ booleanDropdownChoices); } /** * Get boolean dropdown options. * * @param string $choice */ public function getBooleanDropdownOptions($choice = 'active_inactive'): array { return $this->isBooleanDropdownChoiceExists($choice) && $choice == 'active_inactive' ? $this->getActiveInactiveDropdownOptions() : $this->getYesNoDropdownOptions(); } /** * Get active/inactive dropdown options. */ public function getActiveInactiveDropdownOptions(): array { return [ [ 'value' => '', 'label' => trans('admin::app.common.select-options'), 'disabled' => true, 'selected' => true, ], [ 'label' => trans('admin::app.datagrid.active'), 'value' => 1, 'disabled' => false, 'selected' => false, ], [ 'label' => trans('admin::app.datagrid.inactive'), 'value' => 0, 'disabled' => false, 'selected' => false, ], ]; } /** * Get yes/no dropdown options. */ public function getYesNoDropdownOptions(): array { return [ [ 'value' => '', 'label' => trans('admin::app.common.select-options'), 'disabled' => true, 'selected' => true, ], [ 'value' => 0, 'label' => trans('admin::app.common.no'), 'disabled' => false, 'selected' => false, ], [ 'value' => 1, 'label' => trans('admin::app.common.yes'), 'disabled' => false, 'selected' => false, ], ]; } /** * Get user dropdown options. */ public function getUserDropdownOptions(): array { $options = app(UserRepository::class) ->get(['id as value', 'name as label']) ->map(function ($item, $key) { $item->disabled = false; $item->selected = false; return $item; }) ->toArray(); return [ [ 'label' => trans('admin::app.common.select-users'), 'value' => '', 'disabled' => true, 'selected' => true, ], ...$options, ]; } /** * Get lead source options. */ public function getLeadSourcesOptions(): array { $options = app(SourceRepository::class) ->get(['id as value', 'name as label']) ->map(function ($item, $key) { $item->disabled = false; $item->selected = false; return $item; }) ->toArray(); return [ [ 'label' => trans('admin::app.common.select-users'), 'value' => '', 'disabled' => true, 'selected' => true, ], ...$options, ]; } /** * Get organization dropdown options. */ public function getOrganizationDropdownOptions(): array { $options = app(OrganizationRepository::class) ->get(['id as value', 'name as label']) ->map(function ($item, $key) { $item->disabled = false; $item->selected = false; return $item; }) ->toArray(); return [ [ 'label' => trans('admin::app.common.select-organization'), 'value' => '', 'disabled' => true, 'selected' => true, ], ...$options, ]; } /** * Get role dropdown options. */ public function getRoleDropdownOptions(): array { return [ [ 'label' => trans('admin::app.settings.roles.all'), 'value' => 'all', 'disabled' => false, 'selected' => false, ], [ 'label' => trans('admin::app.settings.roles.custom'), 'value' => 'custom', 'disabled' => false, 'selected' => false, ], ]; } /** * Get activity type dropdown options. */ public function getActivityTypeDropdownOptions(): array { return [ [ 'label' => trans('admin::app.common.select-type'), 'value' => '', 'disabled' => true, 'selected' => true, ], [ 'label' => trans('admin::app.common.select-call'), 'value' => 'call', 'disabled' => false, 'selected' => false, ], [ 'label' => trans('admin::app.common.select-meeting'), 'value' => 'meeting', 'disabled' => false, 'selected' => false, ], [ 'label' => trans('admin::app.common.select-lunch'), 'value' => 'lunch', 'disabled' => false, 'selected' => false, ], ]; } /** * Get attribute type dropdown options. */ public function getAttributeTypeDropdownOptions(): array { return [ [ 'label' => trans('admin::app.common.select-options'), 'value' => '', 'disabled' => true, 'selected' => true, ], [ 'label' => trans('admin::app.common.system_attribute'), 'value' => '0', 'disabled' => false, 'selected' => false, ], [ 'label' => trans('admin::app.common.custom_attribute'), 'value' => '1', 'disabled' => false, 'selected' => false, ], ]; } /** * Get organization dropdown options. */ public function getWarehouseDropdownOptions(): array { $options = app(WarehouseRepository::class) ->get(['id as value', 'name as label']) ->map(function ($item, $key) { $item->disabled = false; $item->selected = false; return $item; }) ->toArray(); return [ [ 'label' => trans('admin::app.common.select-warehouse'), 'value' => '', 'disabled' => true, 'selected' => true, ], ...$options, ]; } } ================================================ FILE: packages/Webkul/Admin/tailwind.config.js ================================================ /** @type {import('tailwindcss').Config} */ module.exports = { content: ["./src/Resources/**/*.blade.php", "./src/Resources/**/*.js"], theme: { container: { center: true, screens: { "4xl": "1920px", }, padding: { DEFAULT: "16px", }, }, screens: { sm: "525px", md: "768px", lg: "1024px", xl: "1240px", "2xl": "1440px", "3xl": "1680px", "4xl": "1920px", }, extend: { colors: { brandColor: "var(--brand-color)", }, fontFamily: { inter: ['Inter'], icon: ['icomoon'] } }, }, darkMode: 'class', plugins: [], safelist: [ { pattern: /icon-/, } ] }; ================================================ FILE: packages/Webkul/Admin/tests/e2e-pw/.gitignore ================================================ /playwright-report /test-results ================================================ FILE: packages/Webkul/Admin/tests/e2e-pw/playwright.config.ts ================================================ import { defineConfig, devices } from "@playwright/test"; import dotenv from "dotenv"; import path from "path"; import { fileURLToPath } from "url"; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); export const TESTS_ROOT_PATH = __dirname; export const STATE_DIR_PATH = `${ TESTS_ROOT_PATH }/.state/`; export const ADMIN_AUTH_STATE_PATH = `${ STATE_DIR_PATH }/admin-auth.json`; dotenv.config({ path: path.resolve(__dirname, "../../../../../.env") }); export default defineConfig({ testDir: "./tests", timeout: 30 * 1000, expect: { timeout: 20 * 1000 }, outputDir: "./test-results", fullyParallel: false, workers: 1, forbidOnly: !!process.env.CI, retries: 0, reportSlowTests: null, reporter: [ [ "html", { outputFolder: "./playwright-report", }, ], ], use: { baseURL: `${process.env.APP_URL}/`.replace(/\/+$/, "/"), screenshot: { mode: "only-on-failure", fullPage: true }, video: "retain-on-failure", trace: "retain-on-failure", }, projects: [ { name: "chromium", use: { ...devices["Desktop Chrome"] }, }, ], }); ================================================ FILE: packages/Webkul/Admin/tests/e2e-pw/setup.ts ================================================ import { test as base, expect, type Page } from "@playwright/test"; type AdminFixtures = { adminPage: Page; }; export const test = base.extend({ adminPage: async ({ page }, use) => { const adminCredentials = { email: "admin@example.com", password: "admin123", }; await page.goto("admin/login"); await page.fill('input[name="email"]', adminCredentials.email); await page.fill('input[name="password"]', adminCredentials.password); await page.press('input[name="password"]', "Enter"); await page.waitForURL("**/admin/dashboard"); await use(page); }, }); export { expect }; ================================================ FILE: packages/Webkul/Admin/tests/e2e-pw/tests/auth.spec.ts ================================================ import { test, expect } from "../setup"; const adminCredentials = { email: "admin@example.com", password: "admin123", }; test("should be able to login", async ({ page }) => { /** * Login as admin. */ await page.goto("admin/login"); await page.getByPlaceholder("Email Address").click(); await page.getByPlaceholder("Email Address").fill(adminCredentials.email); await page.getByPlaceholder("Password").click(); await page.getByPlaceholder("Password").fill(adminCredentials.password); await page.getByRole("button", { name: "Sign In" }).click(); await expect(page).toHaveURL(/\/admin\/dashboard/); await expect(page.getByPlaceholder("Mega Search").first()).toBeVisible(); }); test("should be able to logout", async ({ adminPage }) => { await expect(adminPage).toHaveURL(/\/admin\/dashboard/); const profileToggle = adminPage.getByRole("banner").getByRole("button").last(); await expect(profileToggle).toBeVisible(); await profileToggle.click(); const signOutLink = adminPage.getByRole("link", { name: "Sign Out" }); await expect(signOutLink).toBeVisible(); await signOutLink.click(); await expect(adminPage).toHaveURL(/\/admin\/login/); await expect(adminPage.locator('input[name="password"]')).toBeVisible(); }); ================================================ FILE: packages/Webkul/Admin/tests/e2e-pw/tests/contacts/organization.spec.ts ================================================ import { test, expect } from '../../setup'; import {generateCompanyName, createOrganization} from "../../utils/faker" test.describe("organization management", () => { test('should be able to create organization', async ({ adminPage }) => { /** * Create Organization. */ await createOrganization(adminPage); }); test('should be able to edit detail of organization', async ({ adminPage }) => { /** * Create Organization. */ const companyName = await createOrganization(adminPage); /** * Edit Organization detail. */ await adminPage.locator('div').filter({ hasText: companyName }).locator('span.icon-edit').first().click(); await adminPage.getByRole('textbox', { name: 'City' }).click(); await adminPage.getByRole('textbox', { name: 'City' }).fill('Gurugram'); await adminPage.getByRole('button', { name: 'Save Organization' }).click(); /** * Check detail are Edit */ await adminPage.locator('div').filter({ hasText: companyName }).locator('span.icon-edit').first().click(); await expect(adminPage.getByRole('textbox', { name: 'City' })).toHaveValue('Gurugram'); }); test('should be able to delete organization', async ({ adminPage }) => { await adminPage.goto('admin/contacts/organizations'); /** * Delete Organization. */ await adminPage.locator('div') .locator('span.icon-delete') .first() .click(); await adminPage.getByRole('button', { name: 'Agree', exact: true }).click(); await expect(adminPage.locator('#app')).toContainText('Success'); }); test('should be able to mass delete organization', async ({ adminPage }) => { /** * Create Multiple Organizations. */ await createOrganization(adminPage); await createOrganization(adminPage); await createOrganization(adminPage); await createOrganization(adminPage); await createOrganization(adminPage); /** * Check if any organization has value greater than 0. */ const organizationCount = await adminPage.locator('#app').getByText('1', { exact: true }).count(); /** * Mass Delete Organization. */ await adminPage.locator('.icon-checkbox-outline').first().click(); await adminPage.getByRole('button', { name: 'Delete' }).click(); await adminPage.getByRole('button', { name: 'Agree', exact: true }).click(); if (organizationCount > 0) { await expect(adminPage.getByText('Error')).toBeVisible(); } else{ await expect(adminPage.locator('#app')).toContainText('Success'); } }); test('should not be able to create same name organization', async ({ adminPage }) => { /** * Create Organization. */ const companyName = await createOrganization(adminPage); await adminPage.getByRole('link', { name: 'Create Organization' }).click(); /** * Fill in organization details */ await adminPage.getByRole('textbox', { name: 'Name *' }).fill(companyName); await adminPage.locator('textarea[name="address\\[address\\]"]').fill('ARV Park'); await adminPage.getByRole('combobox').selectOption('IN'); await adminPage.locator('select[name="address\\[state\\]"]').selectOption('DL'); await adminPage.getByRole('textbox', { name: 'City' }).fill('Delhi'); await adminPage.getByRole('textbox', { name: 'Postcode' }).fill('123456'); /** * Click to add extra details */ await adminPage.locator('div').filter({ hasText: /^Click to add$/ }).nth(2).click(); await adminPage.getByRole('textbox', { name: 'Search...' }).fill('exampl'); await adminPage.getByRole('listitem').filter({ hasText: 'Example' }).click(); /** * Click on "Save Organization" */ await adminPage.getByRole('button', { name: 'Save Organization' }).click(); /** * Expect Error message */ await expect(adminPage.locator('#app')).toContainText('The value has already been taken.'); }); }); ================================================ FILE: packages/Webkul/Admin/tests/e2e-pw/tests/contacts/person.spec.ts ================================================ import { test, expect } from "../../setup"; import { createPerson, generateCompanyName } from "../../utils/faker"; test("should be able to create person", async ({ adminPage }) => { /** * Create person. */ await adminPage.goto("admin/contacts/persons"); await createPerson(adminPage); }); test("should be able to assign a company to person", async ({ adminPage }) => { /** * Create person. */ await adminPage.goto("admin/contacts/persons"); await createPerson(adminPage); const editButton = adminPage.locator("span.icon-edit").first(); await expect(editButton).toBeVisible(); await editButton.click(); const addCompanyField = adminPage .locator("div") .filter({ hasText: /^Click to add$/ }) .nth(2); await expect(addCompanyField).toBeVisible(); await addCompanyField.click(); await adminPage.getByRole("textbox", { name: "Search..." }).click(); await adminPage .getByRole("textbox", { name: "Search..." }) .fill(generateCompanyName()); await adminPage.getByText("Add as New").click(); await adminPage.getByRole("button", { name: "Save Person" }).click(); }); test("should be able to delete person", async ({ adminPage }) => { /** * Delete person. */ await adminPage.goto("admin/contacts/persons"); await adminPage.locator("span.icon-delete").nth(1).click(); await adminPage.getByRole("button", { name: "Agree", exact: true }).click(); await expect(adminPage.locator("#app")).toContainText("Success", { timeout: 10000 }); }); ================================================ FILE: packages/Webkul/Admin/tests/e2e-pw/tests/lang/lang.spec.ts ================================================ import { expect, test } from '@playwright/test'; import { execSync } from 'child_process'; import fs from 'fs'; import path from 'path'; import { fileURLToPath } from 'url'; // Resolve file paths relative to this test file const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); // Absolute path to /lang directory const LANG_DIR = path.resolve( __dirname, '../../../../src/Resources/lang' ); const BASE_LANG = 'en'; // Helper to extract just the keys from app.php function getNormalizedKeys(filePath: string): string[] { // PHP one-liner to output the array as JSON const phpCode = `echo json_encode(include '${filePath}');`; const json = execSync(`php -r "${phpCode.replace(/"/g, '\\"')}"`).toString(); const obj = JSON.parse(json); function flattenKeys(obj: any, prefix = ''): string[] { return Object.keys(obj).flatMap(key => { const fullKey = prefix ? `${prefix}.${key}` : key; if (typeof obj[key] === 'object' && obj[key] !== null) { return flattenKeys(obj[key], fullKey); } return [fullKey]; }); } return flattenKeys(obj); } test('All language files must match number of keys and key names with English app.php', () => { const baseFile = path.join(LANG_DIR, BASE_LANG, 'app.php'); const baseKeys = getNormalizedKeys(baseFile); // All locales except the base one const locales = fs .readdirSync(LANG_DIR) .filter(locale => locale !== BASE_LANG && fs.existsSync(path.join(LANG_DIR, locale, 'app.php'))); // Array to collect any locales that have issues let failedLocales: { locale: string; missingKeys: string[]; extraKeys: string[] }[] = []; for (const locale of locales) { const filePath = path.join(LANG_DIR, locale, 'app.php'); const keys = getNormalizedKeys(filePath); const missingKeys = baseKeys.filter(key => !keys.includes(key)); const extraKeys = keys.filter(key => !baseKeys.includes(key)); if (missingKeys.length > 0 || extraKeys.length > 0) { failedLocales.push({ locale, missingKeys, extraKeys }); } } if (failedLocales.length > 0) { for (const { locale, missingKeys, extraKeys } of failedLocales) { console.error(` ${locale}/app.php has issues:`); if (missingKeys.length) { console.error(` Missing keys (${missingKeys.length}):`); for (const key of missingKeys) console.error(` - ${key}`); } if (extraKeys.length) { console.error(` Extra keys (${extraKeys.length}):`); for (const key of extraKeys) console.error(` + ${key}`); } } // Fail the test expect(failedLocales).toEqual([]); } else { console.log('All language files have matching keys and counts with en/app.php'); } }); ================================================ FILE: packages/Webkul/Admin/tests/e2e-pw/tests/lead.spec.ts ================================================ import { test, expect } from "../setup"; import fs from "fs"; import { generateName, getRandomDateTime, generateDescription, generateDate, generateEmail, generatePhoneNumber,generateEmailSubject } from '../utils/faker'; async function generateLead(adminPage) { /** * Go to the lead listing page. */ await adminPage.goto("admin/leads"); await adminPage.getByRole('link', { name: 'Create Lead' }).click(); /** * Fill the lead form. */ const leadTitle = `${generateName()}-${Date.now()}`; const leadDescription = generateDescription(); const leadDate = generateDate(); const leadEmail = generateEmail(); const leadPhoneNumber = generatePhoneNumber(); await adminPage.fill('input[name="title"]', leadTitle); await adminPage.fill('textarea[name="description"]', leadDescription); await adminPage.locator('select[name="lead_source_id"]').selectOption("1"); await adminPage.fill('input[name="expected_close_date"]', leadDate); await adminPage.locator('select[name="lead_type_id"]').selectOption("1"); await adminPage.locator('select[name="user_id"]').selectOption("1"); await adminPage.fill('input[name="lead_value"]', '1000'); /** * Add a new person. */ await adminPage.locator('div').filter({ hasText: /^Click to Add$/ }).nth(1).click(); await adminPage.getByRole('textbox', { name: 'Search...' }).fill(leadTitle); await adminPage.getByText('Add as New').click(); await adminPage.fill('input[name="person[emails][0][value]"]', leadEmail); await adminPage.fill('input[name="person[contact_numbers][0][value]"]', leadPhoneNumber); /** * Associate an organization. */ await adminPage.locator('div').filter({ hasText: /^Click to add$/ }).nth(2).click(); await adminPage.getByRole('textbox', { name: 'Search...' }).fill(leadTitle); await adminPage.getByText('Add as New').click(); /** * Save the lead. */ await adminPage.getByRole('button', { name: 'Save' }).click(); /** * Assertion to confirm lead creation. */ await expect(adminPage.getByText('Success', { exact: true })).toBeVisible(); return { leadTitle, leadDescription, leadDate, leadEmail, leadPhoneNumber }; } function generateFile(fileName, content) { fs.writeFileSync(fileName, content); return fileName; } async function openLeadByTitle(adminPage, leadTitle) { await adminPage.goto("admin/leads"); const searchInput = adminPage.getByRole("textbox", { name: "Search by Title" }); await searchInput.fill(leadTitle); await searchInput.press("Enter"); const leadLink = adminPage .locator('a[href*="/admin/leads/view/"]') .filter({ hasText: leadTitle }) .first(); await expect(leadLink).toBeVisible(); await leadLink.click(); } test.describe("lead management", () => { test("should create a new lead", async ({ adminPage }) => { /** * Create a new lead. */ await generateLead(adminPage); }); test("should able to update lead", async ({ adminPage }) => { /** * Create a new lead. */ const lead = await generateLead(adminPage); /** * Update the lead. */ await openLeadByTitle(adminPage, lead.leadTitle); const page1Promise = adminPage.waitForEvent('popup'); await adminPage.getByRole('link', { name: '' }).first().click(); const page1 = await page1Promise; await page1.fill('textarea[name="description"]', generateDescription()); await page1.fill('input[name="title"]', generateName()); await page1.getByLabel('Source').selectOption('3'); await page1.fill('input[name="lead_value"]', '30000'); await page1.getByRole('button', { name: 'Save' }).click(); await page1.getByText('Success', { exact: true }).click(); }); test("should able to delete lead", async ({ adminPage }) => { /** * Create a new lead. */ const lead = await generateLead(adminPage); /** * Delete the lead. */ await adminPage.getByRole('link', { name: '' }).click(); await adminPage.locator('div:nth-child(4) > .flex > span:nth-child(2)').click(); await adminPage.getByRole('button', { name: 'Agree', exact: true }).click(); await expect(adminPage.getByText('Success', { exact: true })).toBeVisible(); await expect(adminPage.locator('#app')).toContainText('Lead deleted successfully.'); }); test("should sent a mail", async ({ adminPage }) => { /** * Create a new lead. */ const lead = await generateLead(adminPage); /** * fill mail detail. */ await openLeadByTitle(adminPage, lead.leadTitle); await adminPage.getByRole('button', { name: ' Mail' }).click(); await adminPage.fill('input[name="temp-reply_to"]', generateEmail()); await adminPage.fill('input[name="subject"]', generateEmailSubject()); await adminPage.fill('textarea[name="reply"]', generateDescription()); /** * Sending mail and closing the modal. */ await adminPage.getByRole('button', { name: 'Send' }).click(); await expect(adminPage.getByText('Email sent successfully.')).toBeVisible(); }); test("should able to upload file in lead", async ({ adminPage }) => { /** * Create a new lead. */ const lead = await generateLead(adminPage); /** * fill the file detail or upload a file. */ await openLeadByTitle(adminPage, lead.leadTitle); await adminPage.getByRole('button', { name: ' File' }).click(); await adminPage.locator('input[name="title"]').fill(lead.leadTitle); await adminPage.locator('textarea[name="comment"]').fill(generateDescription()); await adminPage.locator('input[name="name"]').fill(generateName()); await adminPage.locator('#file').setInputFiles(generateFile('example.txt', 'Hello, this is a generated file!')); await adminPage.getByRole('button', { name: 'Save File' }).click(); }); test("should able to write a note in lead", async ({ adminPage }) => { /** * Create a new lead. */ const lead = await generateLead(adminPage); /** * write a notes */ await openLeadByTitle(adminPage, lead.leadTitle); await adminPage.getByRole('button', { name: ' Note' }).click(); await adminPage.locator('textarea[name="comment"]').fill(generateDescription()); await adminPage.getByRole('button', { name: 'Save Note' }).click(); }); test("should able to add call activity in lead", async ({ adminPage }) => { /** * Create a new lead. */ const lead = await generateLead(adminPage); /** * write a call activity detail */ await openLeadByTitle(adminPage, lead.leadTitle); await adminPage.getByRole('button', { name: ' Activity' }).click(); await adminPage.getByRole('heading', { name: 'Add Activity - Call ' }).locator('span').click(); await adminPage.getByText('Call', { exact: true }).click(); await adminPage.locator('input[name="title"]').fill(lead.leadTitle); await adminPage.locator('textarea[name="comment"]').fill(generateDescription()); await adminPage.locator('input[name="schedule_from"]').click(); await adminPage.locator('input[name="schedule_from"]').fill(getRandomDateTime()); await adminPage.locator('input[name="schedule_to"]').click(); await adminPage.locator('input[name="schedule_to"]').fill(getRandomDateTime()); await adminPage.locator('input[name="location"]').fill('call'); await adminPage.getByRole('button', { name: 'Save Activity' }).click(); }); test("should able to add meeting activity in lead", async ({ adminPage }) => { /** * Create a new lead. */ const lead = await generateLead(adminPage); /** * write a call activity detail */ await openLeadByTitle(adminPage, lead.leadTitle); await adminPage.getByRole('button', { name: ' Activity' }).click(); await adminPage.getByRole('heading', { name: 'Add Activity' }).locator('span').click(); await adminPage.getByText('Meeting', { exact: true }).click(); await adminPage.locator('input[name="title"]').fill(lead.leadTitle); await adminPage.locator('textarea[name="comment"]').fill(generateDescription()); await adminPage.locator('input[name="schedule_from"]').click(); await adminPage.locator('input[name="schedule_from"]').fill(getRandomDateTime()); await adminPage.locator('input[name="schedule_to"]').click(); await adminPage.locator('input[name="schedule_to"]').fill(getRandomDateTime()); await adminPage.locator('input[name="location"]').fill('Google meet'); await adminPage.getByRole('button', { name: 'Save Activity' }).click(); }); test("should able to add lunch activity in lead", async ({ adminPage }) => { /** * Create a new lead. */ const lead = await generateLead(adminPage); /** * write a call activity detail */ await openLeadByTitle(adminPage, lead.leadTitle); await adminPage.getByRole('button', { name: ' Activity' }).click(); await adminPage.getByRole('heading', { name: 'Add Activity' }).locator('span').click(); await adminPage.getByText('Lunch', { exact: true }).click(); await adminPage.locator('input[name="title"]').fill(lead.leadTitle); await adminPage.locator('textarea[name="comment"]').fill(generateDescription()); await adminPage.locator('input[name="schedule_from"]').click(); await adminPage.locator('input[name="schedule_from"]').fill(getRandomDateTime()); await adminPage.locator('input[name="schedule_to"]').click(); await adminPage.locator('input[name="schedule_to"]').fill(getRandomDateTime()); await adminPage.locator('input[name="location"]').fill('Restraunt'); await adminPage.getByRole('button', { name: 'Save Activity' }).click(); }); }); ================================================ FILE: packages/Webkul/Admin/tests/e2e-pw/tests/mail/draft.spec.ts ================================================ import { test, expect } from "../../setup"; ================================================ FILE: packages/Webkul/Admin/tests/e2e-pw/tests/mail/inbox.spec.ts ================================================ import { test, expect } from '../../setup'; import { generateEmail, generateEmailSubject, generateDescription } from '../../utils/faker'; async function composeMail(adminPage, ccMail = false, bccMail = false) { /** * Reaching to the mail listing page. */ await adminPage.goto("admin/mail/inbox"); /** * Opening compose mail in modal. */ await adminPage.getByRole('button', { name: 'Compose Mail' }).click(); await adminPage.fill('input[name="temp-reply_to"]', generateEmail()); await adminPage.fill('input[name="subject"]', generateEmailSubject()); const frameElementHandle = await adminPage.waitForSelector( "iframe.tox-edit-area__iframe" ); const frame = await frameElementHandle.contentFrame(); await frame.waitForSelector("body"); await frame.fill("body", generateDescription()); /** * Sending mail and closing the modal. */ await adminPage.getByRole("button", { name: "Send" }).click(); await expect(adminPage.getByText("Email sent successfully.")).toBeVisible(); } test.describe("mail management", () => { /** * Should be able to compose a mail. */ test("should compose a mail", async ({ adminPage }) => { await composeMail(adminPage); }); /** * Should be able to compose a mail with CC. */ test("should compose a mail with CC", async ({ adminPage }) => { const ccMail = true; await composeMail(adminPage, ccMail); }); /** * Should be able to compose a mail with BCC. */ test("should compose a mail with BCC", async ({ adminPage }) => { const bccMail = true; await composeMail(adminPage, bccMail); }); /** * Should be able to compose a mail with CC & BCC. */ test("should compose a mail with CC & BCC", async ({ adminPage }) => { const ccMail = true; const bccMail = true; await composeMail(adminPage, ccMail, bccMail); }); }); ================================================ FILE: packages/Webkul/Admin/tests/e2e-pw/tests/mail/outbox.spec.ts ================================================ import { test, expect } from "../../setup"; ================================================ FILE: packages/Webkul/Admin/tests/e2e-pw/tests/mail/sent.spec.ts ================================================ import { test, expect } from "../../setup"; ================================================ FILE: packages/Webkul/Admin/tests/e2e-pw/tests/mail/trash.spec.ts ================================================ import { test, expect } from "../../setup"; ================================================ FILE: packages/Webkul/Admin/tests/e2e-pw/tests/product.spec.ts ================================================ import { test, expect } from "../setup"; import { generateName, generateSKU, generateDescription } from "../utils/faker"; test.describe("product management", () => { test("should create a product", async ({ adminPage }) => { /** * Go to the product listing page. */ await adminPage.goto("admin/products"); /** * Create Product. */ await adminPage.getByRole("link", { name: "Create Product" }).click(); /** * Fill the product form. */ const name = generateName(); const description = generateDescription(); await adminPage.waitForSelector('input[name="name"]', { state: 'visible' }); await adminPage.getByRole('textbox', { name: 'Name *' }).click(); await adminPage.locator('#name').clear(); await adminPage.getByRole('textbox', { name: 'Name *' }).fill(name); await adminPage.locator('textarea[name="description"]').clear(); await adminPage .locator('textarea[name="description"]') .type(description); await adminPage.fill('input[name="sku"]', generateSKU()); await adminPage.waitForSelector('input[name="price"]', { state: "visible", }); await adminPage.fill('input[name="price"]', "100"); await adminPage.waitForSelector('input[name="quantity"]', { state: "visible", }); await adminPage.fill('input[name="quantity"]', "50"); /** * Save Product. */ await adminPage.getByRole("button", { name: "Save Products" }).click(); /** * sucess message appear. */ await expect(adminPage.locator("#app")).toContainText( "Product created successfully." ); }); test("should edit a product", async ({ adminPage }) => { /** * Go to the product listing page. */ await adminPage.goto("admin/products"); await adminPage.waitForSelector("a.primary-button", { state: "visible", }); /** * Clicking on the edit icon. */ await adminPage.locator("span.icon-edit").first().click(); await adminPage.waitForSelector('form[action*="/admin/products/edit"]'); /** * Edit the product Detail */ await adminPage.fill('input[name="name"]', generateName()); await adminPage.fill('input[name="price"]', "1000"); await adminPage.fill('input[name="quantity"]', "500"); await adminPage.click('button:has-text("Save Products")'); await expect(adminPage.locator("#app")).toContainText( "Product updated successfully." ); }); test("should delete a product", async ({ adminPage }) => { /** * Go to the product listing page. */ await adminPage.goto("admin/products"); await adminPage.waitForSelector("a.primary-button", { state: "visible", }); /** * Clicking on the delete icon. */ await adminPage.waitForSelector("span.cursor-pointer.icon-delete", { state: "visible", }); await adminPage.locator("span.icon-delete").first().click(); await adminPage.click( "button.transparent-button + button.primary-button:visible" ); await expect(adminPage.locator("#app")).toContainText( "Product deleted successfully." ); }); }); ================================================ FILE: packages/Webkul/Admin/tests/e2e-pw/tests/quotes.spec.ts ================================================ import { test, expect } from "../setup"; import { generateEmailSubject, createPerson, generateDate, generateName, createProduct } from "../utils/faker"; test.describe("quotes management", () => { test("should create a quotes", async ({ adminPage }) => { /** * Create person. */ await adminPage.goto("admin/contacts/persons"); const Person = await createPerson(adminPage); /** * Create Product. */ await adminPage.goto("admin/products"); const Product = await createProduct(adminPage); /** * Create quote. */ await adminPage.goto("admin/quotes"); await adminPage.getByRole("link", { name: "Create Quote" }).click(); await adminPage.getByRole("textbox", { name: "Subject *" }).click(); await adminPage .getByRole("textbox", { name: "Subject *" }) .fill(generateEmailSubject()); await adminPage.getByRole("textbox", { name: "Description" }).click(); await adminPage .getByRole("textbox", { name: "Description" }) .fill(generateEmailSubject()); await adminPage.getByLabel("Sales Owner").selectOption("1"); await adminPage.getByRole("textbox", { name: "Expired At *" }).click(); await adminPage .getByRole("textbox", { name: "Expired At *" }) .fill(generateDate()); await adminPage.locator(".relative > div > .relative").first().click(); await adminPage.getByRole("textbox", { name: "Search..." }).click(); await adminPage .getByRole("textbox", { name: "Search..." }) .fill(Person.Name); await adminPage .getByRole("listitem") .filter({ hasText: Person.Name }) .click(); /** * Fill billing address. */ await adminPage .locator('textarea[name="billing_address\\[address\\]"]') .click(); await adminPage .locator('textarea[name="billing_address\\[address\\]"]') .fill("ARV Park"); await adminPage .locator('select[name="billing_address\\[country\\]"]') .selectOption("IN"); await adminPage .locator('select[name="billing_address\\[state\\]"]') .selectOption("UP"); await adminPage .locator('input[name="billing_address\\[city\\]"]') .click(); await adminPage .locator('input[name="billing_address\\[city\\]"]') .fill("Noida"); await adminPage .locator('input[name="billing_address\\[postcode\\]"]') .click(); await adminPage .locator('input[name="billing_address\\[postcode\\]"]') .fill("201301"); /** * Fill shipping address. */ await adminPage .locator('textarea[name="shipping_address\\[address\\]"]') .click(); await adminPage .locator('textarea[name="shipping_address\\[address\\]"]') .fill("ARV Park"); await adminPage .locator('select[name="shipping_address\\[country\\]"]') .selectOption("IN"); await adminPage .locator('select[name="shipping_address\\[state\\]"]') .selectOption("UP"); await adminPage .locator('input[name="shipping_address\\[city\\]"]') .click(); await adminPage .locator('input[name="shipping_address\\[city\\]"]') .fill("Noida"); await adminPage .locator('input[name="shipping_address\\[postcode\\]"]') .click(); await adminPage .locator('input[name="shipping_address\\[postcode\\]"]') .fill("201301"); await adminPage.locator('.relative.flex.cursor-pointer.items-center.justify-between.rounded.border.p-2').first().click(); await adminPage.getByRole("textbox", { name: "Search..." }).click(); await adminPage.getByRole("textbox", { name: "Search..." }).fill(Product.name); await adminPage.getByRole("listitem").filter({ hasText: Product.name }).first().click(); await adminPage.getByRole("button", { name: "Save Quote" }).click(); }); }); ================================================ FILE: packages/Webkul/Admin/tests/e2e-pw/tests/settings/automation/events.spec.ts ================================================ import { test, expect } from "../../../setup"; import { generateFullName, generateDescription, generateDate } from "../../../utils/faker"; import { confirmModal } from "../../../utils/components"; test.describe("event management", () => { test("should create a event", async ({ adminPage }) => { /** * Reaching to the events listing page. */ await adminPage.goto("admin/settings/marketing/events"); /** * Opening create event form in modal. */ await adminPage.getByRole("button", { name: "Create Event" }).click(); /** * Filling the form with event details. */ await adminPage .locator('input[name="name"]') .fill(generateFullName()); await adminPage .locator('textarea[name="description"]') .fill(generateDescription()); await adminPage .locator('input[name="date"]') .fill(generateDate()); await adminPage.getByRole('textbox', { name: 'Date *' }).press('Enter'); /** * Save event and close the modal. */ await adminPage.getByRole('button', { name: 'Save Event' }).click(); await expect( adminPage.getByText("Event created successfully.") ).toBeVisible(); }); test("should edit a event", async ({ adminPage }) => { /** * Reaching to the events listing page. */ await adminPage.goto("admin/settings/marketing/events"); /** * Clicking on the edit button for the first event opens the modal. */ await adminPage.locator('.row > div:nth-child(6) > a').first().click(); /** * Fill the form with the event details. */ await adminPage .locator('input[name="name"]') .fill(generateFullName()); await adminPage .locator('textarea[name="description"]') .fill(generateDescription()); await adminPage .locator('input[name="date"]') .fill(generateDate()); /** * Saving event and closing the modal. */ await adminPage.getByRole('button', { name: 'Save Event' }).click(); await expect( adminPage.getByText("Event updated successfully.") ).toBeVisible(); }); test("should delete a event", async ({ adminPage }) => { /** * Reaching to the event listing page. */ await adminPage.goto("admin/settings/marketing/events"); /** * Delete the first event. */ await adminPage.locator('div:nth-child(6) > a:nth-child(2)').first().click(); /** * Delete confirmation modal. */ await adminPage.getByRole('button', { name: 'Agree', exact: true }).click(); await expect( adminPage.getByText("Event deleted successfully.") ).toBeVisible(); }); }); ================================================ FILE: packages/Webkul/Admin/tests/e2e-pw/tests/settings/lead/types.spec.ts ================================================ import { test, expect } from "../../../setup"; import { generateFullName } from "../../../utils/faker"; import { confirmModal } from "../../../utils/components"; test.describe("type management", () => { test("should create a type", async ({ adminPage }) => { /** * Reaching to the types listing page. */ await adminPage.goto("admin/settings/types"); /** * Opening create type form in modal. */ await adminPage.getByRole("button", { name: "Create Type" }).click(); /** * Filling the form with type details. */ await adminPage .locator('input[name="name"]') .fill(generateFullName()); /** * Save type and close the modal. */ await adminPage.getByRole("button", { name: "Save Type" }).click(); await expect( adminPage.getByText("Type created successfully.") ).toBeVisible(); }); test("should edit a type", async ({ adminPage }) => { /** * Generating new name and email for the type. */ const updatedName = generateFullName(); /** * Reaching to the types listing page. */ await adminPage.goto("admin/settings/types"); /** * Clicking on the edit button for the first type opens the modal. */ await adminPage.waitForSelector("span.cursor-pointer.icon-edit", { state: "visible", }); const iconEdit = await adminPage.$$("span.cursor-pointer.icon-edit"); await iconEdit[0].click(); await adminPage.locator('input[name="name"]').fill(updatedName); /** * Saving type and closing the modal. */ await adminPage.getByRole("button", { name: "Save Type" }).click(); await expect( adminPage.getByText("Type updated successfully.") ).toBeVisible(); }); test("should delete a type", async ({ adminPage }) => { /** * Reaching to the type listing page. */ await adminPage.goto("admin/settings/types"); /** * Delete the first type. */ await adminPage.waitForSelector("span.cursor-pointer.icon-delete"); const iconDelete = await adminPage.$$( "span.cursor-pointer.icon-delete" ); await iconDelete[0].click(); /** * Delete confirmation modal. */ await confirmModal("Are you sure", adminPage); await expect( adminPage.getByText("Type deleted successfully.") ).toBeVisible(); }); }); ================================================ FILE: packages/Webkul/Admin/tests/e2e-pw/tests/settings/user/groups.spec.ts ================================================ import { test, expect } from "../../../setup"; import { generateFullName, generateDescription } from "../../../utils/faker"; test.describe("group management", () => { test("should create a group", async ({ adminPage }) => { /** * Reaching to the group listing page. */ await adminPage.goto("admin/settings/groups"); /** * Opening create group form in modal. */ await adminPage.getByRole("button", { name: "Create group" }).click(); /** * Filling the form with group details. */ await adminPage .locator('input[name="name"]') .fill(generateFullName()); await adminPage .locator('textarea[name="description"]') .fill(generateDescription(240)); /** * Save group and close the modal. */ await adminPage.getByRole("button", { name: "Save Group" }).click(); await expect( adminPage.getByText("Group created successfully.") ).toBeVisible(); }); }); ================================================ FILE: packages/Webkul/Admin/tests/e2e-pw/tests/settings/user/users.spec.ts ================================================ import { test, expect } from "../../../setup"; import { generateFullName, generateEmail, generateDescription, } from "../../../utils/faker"; import { confirmModal } from "../../../utils/components"; async function createGroup(adminPage) { /** * Navigate to the group listing page. */ await adminPage.goto("admin/settings/groups"); /** * Open the create group form in modal. */ await adminPage.getByRole("button", { name: "Create group" }).click(); /** * Generate group name and fill in the form with group details. */ const groupName = generateFullName(); await adminPage.locator('input[name="name"]').fill(groupName); await adminPage .locator('textarea[name="description"]') .fill(generateDescription(240)); /** * Save the group and close the modal. */ await adminPage.getByRole("button", { name: "Save Group" }).click(); /** * Validate successful creation. */ await expect( adminPage.getByText("Group created successfully.") ).toBeVisible(); return { groupName }; } test.describe("user management", () => { test("should create a user with global permission", async ({ adminPage }) => { /** * Reaching to the user listing page. */ await adminPage.goto("admin/settings/users"); /** * Opening create user form in modal. */ await adminPage.getByRole("button", { name: "Create User" }).click(); /** * Filling the form with user details. */ await adminPage.locator('input[name="name"]').fill(generateFullName()); await adminPage.locator('input[name="email"]').fill(generateEmail()); await adminPage.locator('input[name="password"]').fill("admin123"); await adminPage .locator('input[name="confirm_password"]') .fill("admin123"); await adminPage.locator('select[name="role_id"]').selectOption("1"); await adminPage .locator('select[name="view_permission"]') .selectOption("global"); /** * Clicking on the status toggler to make the user active. */ await adminPage.click('label[for="status"]'); /** * Save user and close the modal. */ await adminPage.getByRole("button", { name: "Save User" }).click(); await expect( adminPage.getByText("User created successfully.") ).toBeVisible(); }); test("should create a user with group permission", async ({ adminPage }) => { /** * Creating a group to assign to the user. */ const name = await createGroup(adminPage); /** * Reaching to the user listing page. */ await adminPage.goto("admin/settings/users"); /** * Opening create user form in modal. */ await adminPage.getByRole("button", { name: "Create User" }).click(); /** * Filling the form with user details. */ await adminPage.locator('input[name="name"]').fill(generateFullName()); await adminPage.locator('input[name="email"]').fill(generateEmail()); await adminPage.locator('input[name="password"]').fill("admin123"); await adminPage .locator('input[name="confirm_password"]') .fill("admin123"); await adminPage.locator('select[name="role_id"]').selectOption("1"); await adminPage .locator('select[name="view_permission"]') .selectOption("group"); await adminPage.getByRole('listbox').selectOption({ label: name.groupName }); /** * Clicking on the status toggler to make the user active. */ await adminPage.click('label[for="status"]'); /** * Save user and close the modal. */ await adminPage.getByRole("button", { name: "Save User" }).click(); await expect( adminPage.getByText("User created successfully.") ).toBeVisible(); }); test("should create a user with individual permission", async ({ adminPage }) => { /** * Reaching to the user listing page. */ await adminPage.goto("admin/settings/users"); /** * Opening create user form in modal. */ await adminPage.getByRole("button", { name: "Create User" }).click(); /** * Filling the form with user details. */ await adminPage.locator('input[name="name"]').fill(generateFullName()); await adminPage.locator('input[name="email"]').fill(generateEmail()); await adminPage.locator('input[name="password"]').fill("admin123"); await adminPage .locator('input[name="confirm_password"]') .fill("admin123"); await adminPage.locator('select[name="role_id"]').selectOption("1"); await adminPage .locator('select[name="view_permission"]') .selectOption("individual"); /** * Clicking on the status toggler to make the user active. */ await adminPage.click('label[for="status"]'); /** * Save user and close the modal. */ await adminPage.getByRole("button", { name: "Save User" }).click(); await expect( adminPage.getByText("User created successfully.") ).toBeVisible(); }); test("should edit a users", async ({ adminPage }) => { /** * Generating new name and email for the user. */ const updatedName = generateFullName(); const updatedEmail = generateEmail(); /** * Reaching to the user listing page. */ await adminPage.goto("admin/settings/users"); /** * Clicking on the edit button for the first user opens the modal. */ await adminPage.waitForSelector("span.cursor-pointer.icon-edit", { state: "visible", }); const iconEdit = await adminPage.$$("span.cursor-pointer.icon-edit"); await iconEdit[0].click(); await adminPage.locator('input[name="name"]').fill(updatedName); await adminPage.locator('input[name="email"]').fill(updatedEmail); /** * Saving user and closing the modal. */ await adminPage.getByRole("button", { name: "Save User" }).click(); await expect(adminPage.locator('#app')).toContainText("User updated successfully."); await expect(adminPage.locator("#app")).toContainText(updatedName); await expect( adminPage.getByRole("paragraph").filter({ hasText: updatedEmail }) ).toBeVisible(); }); test("should delete a user", async ({ adminPage }) => { /** * Reaching to the user listing page. */ await adminPage.goto("admin/settings/users"); /** * Delete the first user. */ await adminPage.waitForSelector("span.cursor-pointer.icon-delete"); const iconDelete = await adminPage.$$( "span.cursor-pointer.icon-delete" ); await iconDelete[0].click(); /** * Delete confirmation modal. */ await confirmModal("Are you sure", adminPage); await expect(adminPage.locator('#app')).toContainText("User deleted successfully."); }); }); ================================================ FILE: packages/Webkul/Admin/tests/e2e-pw/tests/settings/warehouses/warehouse.spec.ts ================================================ import { test, expect } from "../../../setup"; import { confirmModal } from "../../../utils/components"; test.describe("warehouse management", () => { test("should create a warehouse", async ({ adminPage }) => { /** * Reaching to the warehouses listing page. */ await adminPage.goto("admin/settings/warehouses"); // Add code for creating a warehouse. }); test("should edit a warehouse", async ({ adminPage }) => { /** * Reaching to the warehouses listing page. */ await adminPage.goto("admin/settings/warehouses"); /** * Clicking on the edit button for the first warehouse opens the modal. */ // await adminPage.waitForSelector("span.cursor-pointer.icon-edit", { // state: "visible", // }); // const iconEdit = await adminPage.$$("span.cursor-pointer.icon-edit"); // await iconEdit[0].click(); // Add code to edit the warehouse. }); test("should delete a warehouse", async ({ adminPage }) => { /** * Reaching to the warehouse listing page. */ await adminPage.goto("admin/settings/warehouses"); /** * Delete the first warehouse. */ // await adminPage.waitForSelector("span.cursor-pointer.icon-delete"); // const iconDelete = await adminPage.$$( // "span.cursor-pointer.icon-delete" // ); // await iconDelete[0].click(); // /** // * Delete confirmation modal. // */ // await confirmModal("Are you sure", adminPage); // await expect( // adminPage.getByText("Warehouse deleted successfully.") // ).toBeVisible(); }); }); ================================================ FILE: packages/Webkul/Admin/tests/e2e-pw/utils/components.ts ================================================ /** * Confirm the modal dialog. */ export function confirmModal(message, page) { return new Promise(async (resolve, reject) => { await page.waitForSelector("text=" + message); const agreeButton = await page.locator( 'button.primary-button:has-text("Agree")' ); if (await agreeButton.isVisible()) { await agreeButton.click(); resolve(true); } else { reject("Agree button not found or not visible."); } }); } ================================================ FILE: packages/Webkul/Admin/tests/e2e-pw/utils/faker.ts ================================================ import fs from "fs"; import path from "path"; import { fileURLToPath } from "url"; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const usedNames = new Set(); const usedEmails = new Set(); const usedNumbers = new Set(); const usedSlugs = new Set(); /** * Generate a random name. */ function generateName() { const adjectives = [ "Cool", "Smart", "Fast", "Sleek", "Innovative", "Shiny", "Bold", "Elegant", "Epic", "Mystic", "Brilliant", "Luminous", "Radiant", "Majestic", "Vivid", "Glowing", "Dynamic", "Fearless", "Silent", "Electric", "Golden", "Blazing", "Timeless", "Noble", "Eternal" ]; const nouns = [ "Star", "Vision", "Echo", "Spark", "Horizon", "Nova", "Shadow", "Wave", "Pulse", "Vortex", "Zenith", "Element", "Flare", "Comet", "Galaxy", "Ember", "Crystal", "Sky", "Stone", "Blaze", "Eclipse", "Storm", "Orbit", "Phantom", "Mirage" ]; let name = ""; do { const adj = adjectives[Math.floor(Math.random() * adjectives.length)]; const noun = nouns[Math.floor(Math.random() * nouns.length)]; name = `${adj} ${noun}`; } while (usedNames.has(name)); usedNames.add(name); return name; } /** * Generate the first Name. */ function generateFirstName() { const firstNames = [ "James", "Emma", "Liam", "Olivia", "Noah", "Ava", "William", "Sophia", "Benjamin", "Isabella", "Lucas", "Mia", ]; return firstNames[Math.floor(Math.random() * firstNames.length)]; } /** * Generate the last name. */ function generateLastName() { const lastNames = [ "Smith", "Johnson", "Brown", "Williams", "Jones", "Garcia", "Miller", "Davis", "Rodriguez", "Martinez", "Hernandez", "Lopez", ]; return lastNames[Math.floor(Math.random() * lastNames.length)]; } /** * Generate the full name. */ function generateFullName() { return `${generateFirstName()} ${generateLastName()}`; } /** * Generate the email address. */ function generateEmail() { const adjectives = [ "Cool", "Smart", "Fast", "Sleek", "Innovative", "Shiny", "Bold", "Elegant", "Epic", "Mystic", "Brilliant", "Luminous", ]; const nouns = [ "Star", "Vision", "Echo", "Spark", "Horizon", "Nova", "Shadow", "Wave", "Pulse", "Vortex", "Zenith", "Element", ]; let email = ""; do { const adj = adjectives[Math.floor(Math.random() * adjectives.length)]; const noun = nouns[Math.floor(Math.random() * nouns.length)]; const number = Math.floor(1000 + Math.random() * 9000); email = `${adj}${noun}${number}@example.com`.toLowerCase(); } while (usedEmails.has(email)); usedEmails.add(email); return email; } /** * Generate the phone number. */ function generatePhoneNumber() { let phoneNumber; do { phoneNumber = Math.floor(6000000000 + Math.random() * 4000000000); } while (usedNumbers.has(phoneNumber)); usedNumbers.add(phoneNumber); return `${phoneNumber}`; } /** * Generate a random SKU. */ function generateSKU() { const letters = Array.from({ length: 3 }, () => String.fromCharCode(65 + Math.floor(Math.random() * 26)) ).join(""); const numbers = Math.floor(1000 + Math.random() * 9000); return `${letters}${numbers}`; } /** * Generate a random URL. */ function generateSlug(delimiter = "-") { let slug; do { const name = generateName(); const randomStr = Math.random().toString(36).substring(2, 8); slug = `${name .toLowerCase() .replace(/\s+/g, delimiter)}${delimiter}${randomStr}`; } while (usedSlugs.has(slug)); usedSlugs.add(slug); return slug; } /** * Generate a random email subject. */ function generateEmailSubject() { const subjects = [ "Exciting news just for you!", "Don't miss out on this opportunity!", "Limited time offer – act now!", "An exclusive deal awaits you!", "Your next big opportunity is here!", "Something special just for you!", "Unlock amazing benefits today!", "Surprise! A special gift inside!", "This could change everything for you!", "You're invited to something amazing!", "Get ready for a game-changing experience!", "Hurry! This offer won't last long!", "A deal you simply can’t resist!", "Exclusive access – only for you!", "We’ve got something exciting for you!", "Your perfect opportunity is here!", "Important update – check this out!", "Discover what’s waiting for you!", "A limited-time surprise for you!", "Special invitation – don’t miss out!", ]; return subjects[Math.floor(Math.random() * subjects.length)]; } /** * Generate the description. */ function generateDescription(length = 255) { const phrases = [ "An innovative and sleek design.", "Built for speed and efficiency.", "Experience the future today.", "A perfect blend of style and power.", "Engineered to perfection.", "Designed for those who dream big.", "Unleash creativity with this masterpiece.", "A game-changer in every way.", "Smart, fast, and reliable.", "The perfect companion for your journey.", "Crafted with precision and excellence.", "Innovation that redefines possibilities.", "Enhancing your experience like never before.", "Where technology meets elegance.", "Power, performance, and perfection combined.", "Redefining the way you experience the world.", "A masterpiece of engineering and design.", "Unmatched quality and exceptional performance.", "Designed to elevate your lifestyle.", "Beyond expectations, beyond limits.", ]; let description = ""; while (length > 0) { let phrase = phrases[Math.floor(Math.random() * phrases.length)]; if (phrase.length <= length) { description += (description ? " " : "") + phrase; length -= phrase.length; } else { description += " " + phrase.substring(0, length); break; } } return description.trim(); } /** * Generate the host name. */ function generateHostname() { const words = [ "tech", "cloud", "byte", "stream", "nexus", "core", "pulse", "data", "sync", "wave", "hub", "zone", ]; const domains = [".com", ".net", ".io", ".ai", ".xyz", ".co"]; const part1 = words[Math.floor(Math.random() * words.length)]; const part2 = words[Math.floor(Math.random() * words.length)]; const domain = domains[Math.floor(Math.random() * domains.length)]; return `https://${part1}${part2}${domain}`; } /** * Generate a random element from the array. */ function randomElement(array) { return array[Math.floor(Math.random() * array.length)]; } /** * Get a random image file from the directory. */ function getImageFile( directory = path.resolve(__dirname, "../data/images/") ) { if (!fs.existsSync(directory)) { throw new Error(`Directory does not exist: ${directory}`); } const files = fs.readdirSync(directory); const imageFiles = files.filter((file) => /\.(gif|jpeg|jpg|png|svg|webp)$/i.test(file) ); if (!imageFiles.length) { throw new Error("No image files found in the directory."); } const randomIndex = Math.floor(Math.random() * imageFiles.length); return path.join(directory, imageFiles[randomIndex]); } /** * Generate a random date from today onwards in YYYY-MM-DD format. */ function generateDate(): string { const today = new Date().getTime(); const futureEnd = new Date(2030, 11, 31).getTime(); const randomDate = new Date(today + Math.random() * (futureEnd - today)); return randomDate.toISOString().split('T')[0]; } /** * Function to generate a random company name */ function generateCompanyName() { const prefixes = [ "Tech", "Software", "Innovate", "NextGen", "Cloud", "AI", "Cyber", "Digital", "Technical", "Product", "Organization", "Vendor", "Rock-on", "Super", "Quantum", "Neural", "Hyper", "Ultra", "Smart", "Future", "Mega", "Omni", "Virtual", "Dynamic", "Secure", "Data", "Meta", "Nano", "Robo", "Infinity", "Vision", "Intelli", "Strato", "Blue", "Green", "Red", "White", "Black", "Deep", "Elite", "Prime", "Titan", "Nova", "Storm", "Lightning", "Vertex", "Pioneer", "Omnis", "Synergy", "Core", "Nexus" ]; const suffixes = [ "Solutions", "Systems", "Pvt Ltd", "Technologies", "Enterprises", "Labs", "Networks", "Corporation", "Group", "Ventures", "Holdings", "Consulting", "Industries", "Analytics", "Innovations", "Services", "Softwares", "Developers", "AI", "Cloud", "Security", "Dynamics", "Technica", "Data", "Infotech", "Research", "Automation", "Synergy", "Strategies", "Platform", "Operations", "Logistics", "Infrastructure", "Management", "Digital", "Interactive", "Vision", "Connect", "Smart", "Solutions Inc", "Partners", "Tech Ltd", "Info Systems", "Growth", "Intelligence", "RoboCorp", "Edge", "Enterprise", "Global", "Power", "NextGen", "Creative" ]; return `${prefixes[Math.floor(Math.random() * prefixes.length)]} ${suffixes[Math.floor(Math.random() * suffixes.length)]}`; } function generateProductName() { const adjectives = ['Awesome', 'Portable', 'Eco', 'Smart', 'Compact']; const items = ['Phone', 'Laptop', 'Bottle', 'Bag', 'Watch']; return ( adjectives[Math.floor(Math.random() * adjectives.length)] + ' ' + items[Math.floor(Math.random() * items.length)] ); } function generatePrice() { return (Math.random() * (999 - 10) + 10).toFixed(2); } function generateQuantity() { return Math.floor(Math.random() * 100 + 1).toString(); } /** * Function to automate organization creation */ async function createOrganization(page) { const companyName = generateCompanyName(); /** * Click on "Create Organization" button */ await page.goto('admin/contacts/organizations'); await page.getByRole('link', { name: 'Create Organization' }).click(); /** * Fill in organization details */ await page.getByRole('textbox', { name: 'Name *' }).fill(companyName); await page.locator('textarea[name="address\\[address\\]"]').fill('ARV Park'); await page.getByRole('combobox').selectOption('IN'); await page.locator('select[name="address\\[state\\]"]').selectOption('DL'); await page.getByRole('textbox', { name: 'City' }).fill('Delhi'); await page.getByRole('textbox', { name: 'Postcode' }).fill('123456'); /** * Click to add extra details */ await page.locator('div').filter({ hasText: /^Click to add$/ }).nth(2).click(); await page.getByRole('textbox', { name: 'Search...' }).fill('exampl'); await page.getByRole('listitem').filter({ hasText: 'Example' }).click(); /** * Click on "Save Organization" */ await page.getByRole('button', { name: 'Save Organization' }).click(); // await expect(page.getByText(companyName)).toBeVisible(); return companyName; } function generateJobProfile() { const jobProfiles = [ "Playwright Automation Tester", "Software Engineer", "Data Analyst", "Project Manager", "DevOps Engineer", "QA Engineer", "UI/UX Designer", "Product Manager", "Cybersecurity Analyst", "Cloud Architect" ]; const randomIndex = Math.floor(Math.random() * jobProfiles.length); return jobProfiles[randomIndex]; } async function createPerson(page) { const Name = generateFullName(); const email = generateEmail(); const phone = generatePhoneNumber(); const Job = generateJobProfile(); await page.getByRole('link', { name: 'Create Person' }).click(); await page.getByRole('textbox', { name: 'Name *' }).fill(Name); await page.getByRole('textbox', { name: 'Emails *' }).fill(email); await page.getByRole('textbox', { name: 'Contact Numbers' }).fill(phone); await page.getByRole('textbox', { name: 'Job Title' }).fill(Job); // Select an organization await page.locator('.relative > div > .relative').first().click(); await page.getByRole('textbox', { name: 'Search...' }).fill('examp'); await page.getByRole('listitem').filter({ hasText: 'Example' }).click(); // Save person await page.getByRole('button', { name: 'Save Person' }).click(); return { Name, email, phone }; } async function createProduct(page) { const name = generateProductName(); const description = generateDescription(); const sku = generateSKU(); const price = generatePrice(); const quantity = generateQuantity(); await page.getByRole('link', { name: 'Create Product' }).click(); await page.getByRole('textbox', { name: 'Name *' }).fill(name); await page.getByRole('textbox', { name: 'Description' }).fill(description); await page.getByRole('textbox', { name: 'SKU *' }).fill(sku); await page.getByRole('textbox', { name: /price/i }).fill(price); await page.getByRole('textbox', { name: 'Quantity *' }).fill(quantity); await page.getByRole('button', { name: 'Save Products' }).click(); return { name }; } function getRandomDateTime() { const year = Math.floor(Math.random() * (2030 - 2020 + 1)) + 2020; const month = String(Math.floor(Math.random() * 12) + 1).padStart(2, '0'); const day = String(Math.floor(Math.random() * 28) + 1).padStart(2, '0'); const hours = String(Math.floor(Math.random() * 24)).padStart(2, '0'); const minutes = String(Math.floor(Math.random() * 60)).padStart(2, '0'); const seconds = String(Math.floor(Math.random() * 60)).padStart(2, '0'); return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`; } export { generateName, generateFirstName, generateLastName, generateFullName, generateEmail, generatePhoneNumber, generateSKU, generateSlug, generateEmailSubject, generateDescription, generateHostname, randomElement, getImageFile, generateDate, createOrganization, generateCompanyName, createPerson, getRandomDateTime, createProduct }; ================================================ FILE: packages/Webkul/Admin/vite.config.js ================================================ import { defineConfig, loadEnv } from "vite"; import vue from "@vitejs/plugin-vue"; import laravel from "laravel-vite-plugin"; import path from "path"; export default defineConfig(({ mode }) => { const envDir = "../../../"; Object.assign(process.env, loadEnv(mode, envDir)); return { build: { emptyOutDir: true, }, envDir, server: { host: process.env.VITE_HOST || "localhost", port: process.env.VITE_PORT || 5173, cors: true, }, plugins: [ vue(), laravel({ hotFile: "../../../public/admin-vite.hot", publicDirectory: "../../../public", buildDirectory: "admin/build", input: [ "src/Resources/assets/css/app.css", "src/Resources/assets/js/app.js", "src/Resources/assets/js/chart.js", ], refresh: true, }), ], experimental: { renderBuiltUrl(filename, { hostId, hostType, type }) { if (hostType === "css") { return path.basename(filename); } }, }, }; }); ================================================ FILE: packages/Webkul/Attribute/composer.json ================================================ { "name": "krayin/laravel-attribute", "license": "MIT", "authors": [ { "name": "Jitendra Singh", "email": "jitendra@webkul.com" } ], "require": { "krayin/laravel-core": "^1.0" }, "autoload": { "psr-4": { "Webkul\\Attribute\\": "src/" } }, "extra": { "laravel": { "providers": [ "Webkul\\Attribute\\Providers\\AttributeServiceProvider" ], "aliases": {} } }, "minimum-stability": "dev" } ================================================ FILE: packages/Webkul/Attribute/src/Config/attribute_lookups.php ================================================ increments('id'); $table->string('code'); $table->string('name'); $table->string('type'); $table->string('lookup_type')->nullable(); $table->string('entity_type'); $table->integer('sort_order')->nullable(); $table->string('validation')->nullable(); $table->boolean('is_required')->default(0); $table->boolean('is_unique')->default(0); $table->boolean('quick_add')->default(0); $table->boolean('is_user_defined')->default(1); $table->unique(['code', 'entity_type']); $table->timestamps(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('attributes'); } }; ================================================ FILE: packages/Webkul/Attribute/src/Database/Migrations/2021_04_02_080837_create_attribute_options_table.php ================================================ increments('id'); $table->string('name')->nullable(); $table->integer('sort_order')->nullable(); $table->integer('attribute_id')->unsigned(); $table->foreign('attribute_id')->references('id')->on('attributes')->onDelete('cascade'); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('attribute_options'); } }; ================================================ FILE: packages/Webkul/Attribute/src/Database/Migrations/2021_04_06_122751_create_attribute_values_table.php ================================================ increments('id'); $table->string('entity_type')->default('leads'); $table->text('text_value')->nullable(); $table->boolean('boolean_value')->nullable(); $table->integer('integer_value')->nullable(); $table->double('float_value')->nullable(); $table->datetime('datetime_value')->nullable(); $table->date('date_value')->nullable(); $table->json('json_value')->nullable(); $table->integer('entity_id')->unsigned(); $table->integer('attribute_id')->unsigned(); $table->foreign('attribute_id')->references('id')->on('attributes')->onDelete('cascade'); $table->unique(['entity_type', 'entity_id', 'attribute_id'], 'entity_type_attribute_value_index_unique'); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('attribute_values'); } }; ================================================ FILE: packages/Webkul/Attribute/src/Database/Migrations/2025_07_02_191710_alter_attribute_values_table.php ================================================ string('unique_id')->nullable(); }); $tablePrefix = DB::getTablePrefix(); DB::statement('UPDATE '.$tablePrefix."attribute_values SET unique_id = CONCAT(entity_id, '|', attribute_id)"); Schema::table('attribute_values', function (Blueprint $table) { $table->unique('unique_id'); }); } /** * Reverse the migrations. */ public function down(): void { Schema::table('attribute_values', function (Blueprint $table) { $table->dropUnique(['unique_id']); $table->dropColumn('unique_id'); }); } }; ================================================ FILE: packages/Webkul/Attribute/src/Models/Attribute.php ================================================ hasMany(AttributeOptionProxy::modelClass()); } } ================================================ FILE: packages/Webkul/Attribute/src/Models/AttributeOption.php ================================================ belongsTo(AttributeProxy::modelClass()); } } ================================================ FILE: packages/Webkul/Attribute/src/Models/AttributeOptionProxy.php ================================================ 'array', ]; /** * The attributes that are fillable for the model. * * @var array */ protected $fillable = [ 'attribute_id', 'text_value', 'boolean_value', 'integer_value', 'float_value', 'datetime_value', 'date_value', 'json_value', 'entity_id', 'entity_type', ]; /** * The attributes that are used for logging activity. * * @var array */ public static $attributeTypeFields = [ 'text' => 'text_value', 'textarea' => 'text_value', 'price' => 'float_value', 'boolean' => 'boolean_value', 'select' => 'integer_value', 'multiselect' => 'text_value', 'checkbox' => 'text_value', 'email' => 'json_value', 'address' => 'json_value', 'phone' => 'json_value', 'lookup' => 'integer_value', 'datetime' => 'datetime_value', 'date' => 'date_value', 'file' => 'text_value', 'image' => 'text_value', ]; /** * Get the attribute that owns the attribute value. */ public function attribute() { return $this->belongsTo(AttributeProxy::modelClass()); } /** * Get the parent entity model (leads, products, persons or organizations). */ public function entity() { return $this->morphTo(); } } ================================================ FILE: packages/Webkul/Attribute/src/Models/AttributeValueProxy.php ================================================ loadMigrationsFrom(__DIR__.'/../Database/Migrations'); } /** * Register services. * * @return void */ public function register() { $this->registerConfig(); } /** * Register package config. * * @return void */ protected function registerConfig() { $this->mergeConfigFrom( dirname(__DIR__).'/Config/attribute_lookups.php', 'attribute_lookups' ); } } ================================================ FILE: packages/Webkul/Attribute/src/Providers/ModuleServiceProvider.php ================================================ , * 1: class-string, * 2: class-string * } */ protected $models = [ Attribute::class, AttributeOption::class, AttributeValue::class, ]; } ================================================ FILE: packages/Webkul/Attribute/src/Repositories/AttributeOptionRepository.php ================================================ model->create($data); if (in_array($attribute->type, ['select', 'multiselect', 'checkbox']) && count($options)) { $sortOrder = 1; foreach ($options as $optionInputs) { $this->attributeOptionRepository->create(array_merge([ 'attribute_id' => $attribute->id, 'sort_order' => $sortOrder++, ], $optionInputs)); } } return $attribute; } /** * @param int $id * @param string $attribute * @return Attribute */ public function update(array $data, $id, $attribute = 'id') { $attribute = $this->find($id); $attribute->update($data); if (! in_array($attribute->type, ['select', 'multiselect', 'checkbox'])) { return $attribute; } if (! isset($data['options'])) { return $attribute; } foreach ($data['options'] as $optionId => $optionInputs) { $isNew = $optionInputs['isNew'] == 'true'; if ($isNew) { $this->attributeOptionRepository->create(array_merge([ 'attribute_id' => $attribute->id, ], $optionInputs)); } else { $isDelete = $optionInputs['isDelete'] == 'true'; if ($isDelete) { $this->attributeOptionRepository->delete($optionId); } else { $this->attributeOptionRepository->update($optionInputs, $optionId); } } } return $attribute; } /** * @param string $code * @return Attribute */ public function getAttributeByCode($code) { static $attributes = []; if (array_key_exists($code, $attributes)) { return $attributes[$code]; } return $attributes[$code] = $this->findOneByField('code', $code); } /** * @param int $lookup * @param string $query * @param array $columns * @return array */ public function getLookUpOptions($lookup, $query = '', $columns = []) { $lookup = config('attribute_lookups.'.$lookup); if (! count($columns)) { $columns = [($lookup['value_column'] ?? 'id').' as id', ($lookup['label_column'] ?? 'name').' as name']; } if (Str::contains($lookup['repository'], 'UserRepository')) { $userRepository = app($lookup['repository'])->where('status', 1); $currentUser = auth()->guard('user')->user(); if ($currentUser?->view_permission === 'group') { $query = urldecode($query); $userIds = bouncer()->getAuthorizedUserIds(); return $userRepository ->when(! empty($userIds), fn ($queryBuilder) => $queryBuilder->whereIn('users.id', $userIds)) ->when(! empty($query), fn ($queryBuilder) => $queryBuilder->where('users.name', 'like', "%{$query}%")) ->get(); } elseif ($currentUser?->view_permission === 'individual') { return $userRepository->where('users.id', $currentUser->id)->get(); } return $userRepository->where('users.name', 'like', '%'.urldecode($query).'%')->get(); } return app($lookup['repository'])->findWhere([ [$lookup['label_column'] ?? 'name', 'like', '%'.urldecode($query).'%'], ], $columns); } /** * @param string $lookup * @param int|array $entityId * @param array $columns * @return mixed */ public function getLookUpEntity($lookup, $entityId = null, $columns = []) { if (! $entityId) { return; } $lookup = config('attribute_lookups.'.$lookup); if (! count($columns)) { $columns = [($lookup['value_column'] ?? 'id').' as id', ($lookup['label_column'] ?? 'name').' as name']; } if (is_array($entityId)) { return app($lookup['repository'])->findWhereIn( 'id', $entityId, $columns ); } else { return app($lookup['repository'])->find($entityId, $columns); } } } ================================================ FILE: packages/Webkul/Attribute/src/Repositories/AttributeValueRepository.php ================================================ $data['entity_type']]; if (isset($data['quick_add'])) { $conditions['quick_add'] = 1; } $attributes = $this->attributeRepository->where($conditions)->get(); } foreach ($attributes as $attribute) { $typeColumn = $this->model::$attributeTypeFields[$attribute->type]; if ($attribute->type === 'boolean') { $data[$attribute->code] = isset($data[$attribute->code]) && $data[$attribute->code] ? 1 : 0; } if (! array_key_exists($attribute->code, $data)) { continue; } if ($attribute->type === 'price' && isset($data[$attribute->code]) && $data[$attribute->code] === '' ) { $data[$attribute->code] = null; } if ($attribute->type === 'date' && $data[$attribute->code] === '') { $data[$attribute->code] = null; } if ($attribute->type === 'multiselect' || $attribute->type === 'checkbox') { $data[$attribute->code] = implode(',', $data[$attribute->code]); } if ($attribute->type === 'email' || $attribute->type === 'phone') { $data[$attribute->code] = $this->sanitizeEmailAndPhone($data[$attribute->code]); } if ($attribute->type === 'image' || $attribute->type === 'file') { $data[$attribute->code] = $data[$attribute->code] instanceof UploadedFile ? $data[$attribute->code]->store($data['entity_type'].'/'.$data['entity_id']) : null; } $attributeValue = $this->findOneWhere([ 'entity_type' => $data['entity_type'], 'entity_id' => $data['entity_id'], 'attribute_id' => $attribute->id, ]); if (! $attributeValue) { $this->create([ 'entity_type' => $data['entity_type'], 'entity_id' => $data['entity_id'], 'attribute_id' => $attribute->id, $typeColumn => $data[$attribute->code], ]); } else { $this->update([ $typeColumn => $data[$attribute->code], ], $attributeValue->id); if ($attribute->type == 'image' || $attribute->type == 'file') { if ($attributeValue->text_value) { Storage::delete($attributeValue->text_value); } } } } } /** * Is value unique. * * @param int $entityId * @param string $entityType * @param Attribute $attribute * @param string $value * @return bool */ public function isValueUnique($entityId, $entityType, $attribute, $value) { $query = $this->resetScope()->model ->where('attribute_id', $attribute->id) ->where('entity_type', $entityType) ->where('entity_id', '!=', $entityId); /** * If the attribute type is email or phone, check the JSON value. */ if (in_array($attribute->type, ['email', 'phone'])) { $query->whereJsonContains($this->model::$attributeTypeFields[$attribute->type], [['value' => $value]]); } else { $query->where($this->model::$attributeTypeFields[$attribute->type], $value); } return $query->get()->count() ? false : true; } /** * Removed null values from email and phone fields. * * @param array $data * @return array */ public function sanitizeEmailAndPhone($data) { foreach ($data as $key => $row) { if (is_null($row['value'])) { unset($data[$key]); } } return $data; } /** * Replace placeholders with values */ public function getAttributeLabel(mixed $value, mixed $attribute) { switch ($attribute?->type) { case 'price': $label = core()->formatBasePrice($value); break; case 'boolean': $label = $value ? 'Yes' : 'No'; break; case 'select': case 'radio': case 'lookup': if ($attribute->lookup_type) { $option = $this->attributeRepository->getLookUpEntity($attribute->lookup_type, $value); } else { $option = $attribute->options->where('id', $value)->first(); } $label = $option?->name; break; case 'multiselect': case 'checkbox': if ($attribute->lookup_type) { $options = $this->attributeRepository->getLookUpEntity($attribute->lookup_type, explode(',', $value)); } else { $options = $attribute->options->whereIn('id', explode(',', $value)); } $optionsLabels = []; foreach ($options as $key => $option) { $optionsLabels[] = $option->name; } $label = implode(', ', $optionsLabels); break; case 'email': case 'phone': if (! is_array($value)) { break; } $optionsLabels = []; foreach ($value as $item) { $optionsLabels[] = $item['value'].' ('.$item['label'].')'; } $label = implode(', ', $optionsLabels); break; case 'address': if ( ! $value || ! count(array_filter($value)) ) { break; } $label = $value['address'].'
    ' .$value['postcode'].' '.$value['city'].'
    ' .core()->state_name($value['state']).'
    ' .core()->country_name($value['country']).'
    '; break; case 'date': if ($value) { $label = Carbon::parse($value)->format('D M d, Y'); } else { $label = null; } break; case 'datetime': if ($value) { $label = Carbon::parse($value)->format('D M d, Y H:i A'); } else { $label = null; } break; default: if ($value instanceof Carbon) { $label = $value->format('D M d, Y H:i A'); } else { $label = $value; } break; } return $label ?? null; } } ================================================ FILE: packages/Webkul/Attribute/src/Traits/CustomAttribute.php ================================================ 'text_value', 'textarea' => 'text_value', 'price' => 'float_value', 'boolean' => 'boolean_value', 'select' => 'integer_value', 'multiselect' => 'text_value', 'checkbox' => 'text_value', 'email' => 'json_value', 'address' => 'json_value', 'phone' => 'json_value', 'lookup' => 'integer_value', 'datetime' => 'datetime_value', 'date' => 'date_value', 'file' => 'text_value', 'image' => 'text_value', ]; /** * Get the attribute values that owns the entity. */ public function attribute_values() { return $this->morphMany(AttributeValueProxy::modelClass(), 'entity'); } /** * Get an attribute from the model. * * @param string $key * @return mixed */ public function getAttribute($key) { if (! method_exists(static::class, $key) && ! isset($this->attributes[$key])) { if (isset($this->id)) { $this->attributes[$key] = ''; $attribute = app(AttributeRepository::class)->getAttributeByCode($key); $this->attributes[$key] = $this->getCustomAttributeValue($attribute); return $this->getAttributeValue($key); } } return parent::getAttribute($key); } /** * @return array */ public function attributesToArray() { $attributes = parent::attributesToArray(); $hiddenAttributes = $this->getHidden(); if (isset($this->id)) { $customAttributes = $this->getCustomAttributes(); foreach ($customAttributes as $attribute) { if (in_array($attribute->code, $hiddenAttributes) && isset($this->attributes[$attribute->code])) { continue; } $attributes[$attribute->code] = $this->getCustomAttributeValue($attribute); } } return $attributes; } /** * Check in loaded family attributes. * * @return object */ public function getCustomAttributes() { static $attributes; if ($attributes) { return $attributes; } return $attributes = app(AttributeRepository::class)->where('entity_type', $this->getTable())->get(); } /** * Get an product attribute value. * * @return mixed */ public function getCustomAttributeValue($attribute) { if (! $attribute) { return; } $attributeValue = $this->attribute_values->where('attribute_id', $attribute->id)->first(); return $attributeValue[self::$attributeTypeFields[$attribute->type]] ?? null; } /** * Create a new instance of the given model. * * @param array $attributes * @return Collection */ public function getLookUpAttributes($attributes) { $attributes = app(AttributeRepository::class)->scopeQuery(function ($query) use ($attributes) { return $query->distinct() ->where('type', 'lookup') ->where('entity_type', request('entity_type')) ->whereIn('code', array_keys($attributes, '', false)); })->get(); return $attributes; } /** * Create a new instance of the given model. * * @param array $attributes * @param bool $exists * @return static */ public function newInstance($attributes = [], $exists = false) { // $attributes = $this->getLookUpAttributes($attributes); // Play with data here return parent::newInstance($attributes, $exists); } /** * Fill the model with an array of attributes. * * @return $this * * @throws MassAssignmentException */ public function fill(array $attributes) { // Play with data here return parent::fill($attributes); } // Delete model's attribute values public static function boot() { parent::boot(); static::deleting(function ($entity) { $entity->attribute_values()->delete(); }); } } ================================================ FILE: packages/Webkul/Automation/src/Config/workflows.php ================================================ [ 'leads' => [ 'name' => 'Leads', 'class' => 'Webkul\Automation\Helpers\Entity\Lead', 'events' => [ [ 'event' => 'lead.create.after', 'name' => 'Created', ], [ 'event' => 'lead.update.after', 'name' => 'Updated', ], [ 'event' => 'lead.delete.before', 'name' => 'Deleted', ], ], ], 'activities' => [ 'name' => 'Activities', 'class' => 'Webkul\Automation\Helpers\Entity\Activity', 'events' => [ [ 'event' => 'activity.create.after', 'name' => 'Created', ], [ 'event' => 'activity.update.after', 'name' => 'Updated', ], [ 'event' => 'activity.delete.before', 'name' => 'Deleted', ], ], ], 'persons' => [ 'name' => 'Persons', 'class' => 'Webkul\Automation\Helpers\Entity\Person', 'events' => [ [ 'event' => 'contacts.person.create.after', 'name' => 'Created', ], [ 'event' => 'contacts.person.update.after', 'name' => 'Updated', ], [ 'event' => 'contacts.person.delete.before', 'name' => 'Deleted', ], ], ], 'quotes' => [ 'name' => 'Quotes', 'class' => 'Webkul\Automation\Helpers\Entity\Quote', 'events' => [ [ 'event' => 'quote.create.after', 'name' => 'Created', ], [ 'event' => 'quote.update.after', 'name' => 'Updated', ], [ 'event' => 'quote.delete.before', 'name' => 'Deleted', ], ], ], ], ]; ================================================ FILE: packages/Webkul/Automation/src/Contracts/Webhook.php ================================================ increments('id'); $table->string('name'); $table->string('description')->nullable(); $table->string('entity_type'); $table->string('event'); $table->string('condition_type')->default('and'); $table->json('conditions')->nullable(); $table->json('actions')->nullable(); $table->timestamps(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('workflows'); } }; ================================================ FILE: packages/Webkul/Automation/src/Database/Migrations/2024_07_24_150821_create_webhooks_table.php ================================================ id(); $table->string('name'); $table->string('entity_type'); $table->string('description')->nullable(); $table->string('method'); $table->string('end_point'); $table->json('query_params')->nullable(); $table->json('headers')->nullable(); $table->string('payload_type'); $table->string('raw_payload_type'); $table->json('payload')->nullable(); $table->timestamps(); }); } /** * Reverse the migrations. */ public function down(): void { Schema::dropIfExists('webhooks'); } }; ================================================ FILE: packages/Webkul/Automation/src/Helpers/Entity/AbstractEntity.php ================================================ getAttributes($this->entityType); } /** * Get attributes for entity. */ public function getAttributes(string $entityType, array $skipAttributes = ['textarea', 'image', 'file', 'address']): array { $attributes = []; foreach ($this->attributeRepository->findByField('entity_type', $entityType) as $attribute) { if (in_array($attribute->type, $skipAttributes)) { continue; } if ($attribute->lookup_type) { $options = []; } else { $options = $attribute->options; } $attributes[] = [ 'id' => $attribute->code, 'type' => $attribute->type, 'name' => $attribute->name, 'lookup_type' => $attribute->lookup_type, 'options' => $options, ]; } return $attributes; } /** * Returns placeholders for email templates. */ public function getEmailTemplatePlaceholders(array $entity): array { $menuItems = []; foreach ($this->getAttributes($this->entityType) as $attribute) { $menuItems[] = [ 'text' => $attribute['name'], 'value' => '{%'.$this->entityType.'.'.$attribute['id'].'%}', ]; } return [ 'text' => $entity['name'], 'menu' => $menuItems, ]; } /** * Replace placeholders with values. */ public function replacePlaceholders(mixed $entity, string $content): string { foreach ($this->getAttributes($this->entityType, []) as $attribute) { $value = ''; switch ($attribute['type']) { case 'price': $value = core()->formatBasePrice($entity->{$attribute['id']}); break; case 'boolean': $value = $entity->{$attribute['id']} ? trans('admin::app.common.yes') : trans('admin::app.common.no'); break; case 'select': case 'radio': case 'lookup': if ($attribute['lookup_type']) { $option = $this->attributeRepository->getLookUpEntity($attribute['lookup_type'], $entity->{$attribute['id']}); } else { $option = $attribute['options']->where('id', $entity->{$attribute['id']})->first(); } $value = $option ? $option->name : ''; break; case 'multiselect': case 'checkbox': if ($attribute['lookup_type']) { $options = $this->attributeRepository->getLookUpEntity($attribute['lookup_type'], explode(',', $entity->{$attribute['id']})); } else { $options = $attribute['options']->whereIn('id', explode(',', $entity->{$attribute['id']})); } $optionsLabels = []; foreach ($options as $key => $option) { $optionsLabels[] = $option->name; } $value = implode(', ', $optionsLabels); break; case 'email': case 'phone': if (! is_array($entity->{$attribute['id']})) { break; } $optionsLabels = []; foreach ($entity->{$attribute['id']} as $item) { $optionsLabels[] = $item['value'].' ('.$item['label'].')'; } $value = implode(', ', $optionsLabels); break; case 'address': if (! $entity->{$attribute['id']} || ! count(array_filter($entity->{$attribute['id']}))) { break; } $value = $entity->{$attribute['id']}['address'].'
    ' .$entity->{$attribute['id']}['postcode'].' '.$entity->{$attribute['id']}['city'].'
    ' .core()->state_name($entity->{$attribute['id']}['state']).'
    ' .core()->country_name($entity->{$attribute['id']}['country']).'
    '; break; case 'date': if ($entity->{$attribute['id']}) { $value = ! is_object($entity->{$attribute['id']}) ? Carbon::parse($entity->{$attribute['id']}) : $entity->{$attribute['id']}->format('D M d, Y'); } else { $value = 'N/A'; } break; case 'datetime': if ($entity->{$attribute['id']}) { $value = ! is_object($entity->{$attribute['id']}) ? Carbon::parse($entity->{$attribute['id']}) : $entity->{$attribute['id']}->format('D M d, Y H:i A'); } else { $value = 'N/A'; } break; default: $value = $entity->{$attribute['id']}; break; } $content = strtr($content, [ '{%'.$this->entityType.'.'.$attribute['id'].'%}' => $value, '{% '.$this->entityType.'.'.$attribute['id'].' %}' => $value, ]); } return $content; } /** * Trigger webhook. * * @return void */ public function triggerWebhook(int $webhookId, mixed $entity) { $webhook = $this->webhookRepository->findOrFail($webhookId); $payload = [ 'method' => $webhook->method, 'query_params' => $this->replacePlaceholders($entity, json_encode($webhook->query_params)), 'end_point' => $this->replacePlaceholders($entity, $webhook->end_point), 'payload' => $this->replacePlaceholders($entity, json_encode($webhook->payload)), 'headers' => $this->replacePlaceholders($entity, json_encode($webhook->headers)), ]; $this->webhookService->triggerWebhook($payload); } } ================================================ FILE: packages/Webkul/Automation/src/Helpers/Entity/Activity.php ================================================ 'title', 'type' => 'text', 'name' => 'Title', 'lookup_type' => null, 'options' => collect(), ], [ 'id' => 'type', 'type' => 'multiselect', 'name' => 'Type', 'lookup_type' => null, 'options' => collect([ (object) [ 'id' => 'note', 'name' => 'Note', ], (object) [ 'id' => 'call', 'name' => 'Call', ], (object) [ 'id' => 'meeting', 'name' => 'Meeting', ], (object) [ 'id' => 'lunch', 'name' => 'Lunch', ], (object) [ 'id' => 'file', 'name' => 'File', ], ]), ], [ 'id' => 'location', 'type' => 'text', 'name' => 'Location', 'lookup_type' => null, 'options' => collect(), ], [ 'id' => 'comment', 'type' => 'textarea', 'name' => 'Comment', 'lookup_type' => null, 'options' => collect(), ], [ 'id' => 'schedule_from', 'type' => 'datetime', 'name' => 'Schedule From', 'lookup_type' => null, 'options' => collect(), ], [ 'id' => 'schedule_to', 'type' => 'datetime', 'name' => 'Schedule To', 'lookup_type' => null, 'options' => collect(), ], [ 'id' => 'user_id', 'type' => 'select', 'name' => 'User', 'lookup_type' => 'users', 'options' => $this->attributeRepository->getLookUpOptions('users'), ], ]; return $attributes; } /** * Returns placeholders for email templates. */ public function getEmailTemplatePlaceholders(array $entity): array { $emailTemplates = parent::getEmailTemplatePlaceholders($entity); $emailTemplates['menu'][] = [ 'text' => 'Participants', 'value' => '{%activities.participants%}', ]; return $emailTemplates; } /** * Replace placeholders with values. */ public function replacePlaceholders(mixed $entity, string $content): string { $content = parent::replacePlaceholders($entity, $content); $value = '
      '; foreach ($entity->participants as $participant) { $value .= '
    • '.($participant->user ? $participant->user->name : $participant->person->name).'
    • '; } $value .= '
    '; return strtr($content, [ '{%'.$this->entityType.'.participants%}' => $value, '{% '.$this->entityType.'.participants %}' => $value, ]); } /** * Listing of the entities. */ public function getEntity(mixed $entity): mixed { if (! $entity instanceof ContractsActivity) { $entity = $this->activityRepository->find($entity); } return $entity; } /** * Returns workflow actions. */ public function getActions(): array { $emailTemplates = $this->emailTemplateRepository->all(['id', 'name']); $webhooksOptions = $this->webhookRepository->all(['id', 'name']); return [ [ 'id' => 'update_related_leads', 'name' => trans('admin::app.settings.workflows.helpers.update-related-leads'), 'attributes' => $this->getAttributes('leads'), ], [ 'id' => 'send_email_to_sales_owner', 'name' => trans('admin::app.settings.workflows.helpers.send-email-to-sales-owner'), 'options' => $emailTemplates, ], [ 'id' => 'send_email_to_participants', 'name' => trans('admin::app.settings.workflows.helpers.send-email-to-participants'), 'options' => $emailTemplates, ], [ 'id' => 'trigger_webhook', 'name' => trans('admin::app.settings.workflows.helpers.add-webhook'), 'options' => $webhooksOptions, ], ]; } /** * Execute workflow actions. */ public function executeActions(mixed $workflow, mixed $activity): void { foreach ($workflow->actions as $action) { switch ($action['id']) { case 'update_related_leads': $leadIds = $this->activityRepository->getModel() ->leftJoin('lead_activities', 'activities.id', 'lead_activities.activity_id') ->leftJoin('leads', 'lead_activities.lead_id', 'leads.id') ->addSelect('leads.id') ->where('activities.id', $activity->id) ->pluck('id'); foreach ($leadIds as $leadId) { $this->leadRepository->update( [ 'entity_type' => 'leads', $action['attribute'] => $action['value'], ], $leadId, [$action['attribute']] ); } break; case 'send_email_to_sales_owner': $emailTemplate = $this->emailTemplateRepository->find($action['value']); if (! $emailTemplate) { break; } try { Mail::queue(new Common([ 'to' => $activity->user->email, 'subject' => $this->replacePlaceholders($activity, $emailTemplate->subject), 'body' => $this->replacePlaceholders($activity, $emailTemplate->content), 'attachments' => [ [ 'name' => 'invite.ics', 'mime' => 'text/calendar', 'content' => $this->getICSContent($activity), ], ], ])); } catch (\Exception $e) { } break; case 'send_email_to_participants': $emailTemplate = $this->emailTemplateRepository->find($action['value']); if (! $emailTemplate) { break; } try { foreach ($activity->participants as $participant) { Mail::queue(new Common([ 'to' => $participant->user ? $participant->user->email : data_get($participant->person->emails, '*.value'), 'subject' => $this->replacePlaceholders($activity, $emailTemplate->subject), 'body' => $this->replacePlaceholders($activity, $emailTemplate->content), 'attachments' => [ [ 'name' => 'invite.ics', 'mime' => 'text/calendar', 'content' => $this->getICSContent($activity), ], ], ])); } } catch (\Exception $e) { } break; case 'trigger_webhook': try { $this->triggerWebhook($action['value'], $activity); } catch (\Exception $e) { report($e); } break; } } } /** * Returns .ics file for attachments. */ public function getICSContent(ContractsActivity $activity): string { $content = [ 'BEGIN:VCALENDAR', 'VERSION:2.0', 'PRODID:-//Krayincrm//Krayincrm//EN', 'BEGIN:VEVENT', 'UID:'.time().'-'.$activity->id, 'DTSTAMP:'.Carbon::now()->format('YmdTHis'), 'CREATED:'.$activity->created_at->format('YmdTHis'), 'SEQUENCE:1', 'ORGANIZER;CN='.$activity->user->name.':MAILTO:'.$activity->user->email, ]; foreach ($activity->participants as $participant) { if ($participant->user) { $content[] = 'ATTENDEE;ROLE=REQ-PARTICIPANT;CN='.$participant->user->name.';PARTSTAT=NEEDS-ACTION:MAILTO:'.$participant->user->email; } else { foreach (data_get($participant->person->emails, '*.value') as $email) { $content[] = 'ATTENDEE;ROLE=REQ-PARTICIPANT;CN='.$participant->person->name.';PARTSTAT=NEEDS-ACTION:MAILTO:'.$email; } } } $content = array_merge($content, [ 'DTSTART:'.$activity->schedule_from->format('YmdTHis'), 'DTEND:'.$activity->schedule_to->format('YmdTHis'), 'SUMMARY:'.$activity->title, 'LOCATION:'.$activity->location, 'DESCRIPTION:'.$activity->comment, 'END:VEVENT', 'END:VCALENDAR', ]); return implode("\r\n", $content); } } ================================================ FILE: packages/Webkul/Automation/src/Helpers/Entity/Lead.php ================================================ leadRepository->find($entity); } return $entity; } /** * Returns attributes. */ public function getAttributes(string $entityType, array $skipAttributes = ['textarea', 'image', 'file', 'address']): array { return parent::getAttributes($entityType, $skipAttributes); } /** * Returns workflow actions. */ public function getActions(): array { $emailTemplates = $this->emailTemplateRepository->all(['id', 'name']); $webhooksOptions = $this->webhookRepository->all(['id', 'name']); return [ [ 'id' => 'update_lead', 'name' => trans('admin::app.settings.workflows.helpers.update-lead'), 'attributes' => $this->getAttributes('leads'), ], [ 'id' => 'update_person', 'name' => trans('admin::app.settings.workflows.helpers.update-person'), 'attributes' => $this->getAttributes('persons'), ], [ 'id' => 'send_email_to_person', 'name' => trans('admin::app.settings.workflows.helpers.send-email-to-person'), 'options' => $emailTemplates, ], [ 'id' => 'send_email_to_sales_owner', 'name' => trans('admin::app.settings.workflows.helpers.send-email-to-sales-owner'), 'options' => $emailTemplates, ], [ 'id' => 'add_tag', 'name' => trans('admin::app.settings.workflows.helpers.add-tag'), ], [ 'id' => 'add_note_as_activity', 'name' => trans('admin::app.settings.workflows.helpers.add-note-as-activity'), ], [ 'id' => 'trigger_webhook', 'name' => trans('admin::app.settings.workflows.helpers.add-webhook'), 'options' => $webhooksOptions, ], ]; } /** * Execute workflow actions. */ public function executeActions(mixed $workflow, mixed $lead): void { foreach ($workflow->actions as $action) { switch ($action['id']) { case 'update_lead': $this->leadRepository->update( [ 'entity_type' => 'leads', $action['attribute'] => $action['value'], ], $lead->id, [$action['attribute']] ); break; case 'update_person': $this->personRepository->update([ 'entity_type' => 'persons', $action['attribute'] => $action['value'], ], $lead->person_id); break; case 'send_email_to_person': $emailTemplate = $this->emailTemplateRepository->find($action['value']); if (! $emailTemplate) { break; } try { Mail::queue(new Common([ 'to' => data_get($lead->person->emails, '*.value'), 'subject' => $this->replacePlaceholders($lead, $emailTemplate->subject), 'body' => $this->replacePlaceholders($lead, $emailTemplate->content), ])); } catch (\Exception $e) { } break; case 'send_email_to_sales_owner': $emailTemplate = $this->emailTemplateRepository->find($action['value']); if (! $emailTemplate) { break; } try { Mail::queue(new Common([ 'to' => $lead->user->email, 'subject' => $this->replacePlaceholders($lead, $emailTemplate->subject), 'body' => $this->replacePlaceholders($lead, $emailTemplate->content), ])); } catch (\Exception $e) { } break; case 'add_tag': $colors = [ '#337CFF', '#FEBF00', '#E5549F', '#27B6BB', '#FB8A3F', '#43AF52', ]; if (! $tag = $this->tagRepository->findOneByField('name', $action['value'])) { $tag = $this->tagRepository->create([ 'name' => $action['value'], 'color' => $colors[rand(0, 5)], 'user_id' => auth()->guard('user')->user()->id, ]); } if (! $lead->tags->contains($tag->id)) { $lead->tags()->attach($tag->id); } break; case 'add_note_as_activity': $activity = $this->activityRepository->create([ 'type' => 'note', 'comment' => $action['value'], 'is_done' => 1, 'user_id' => auth()->guard('user')->user()->id, ]); $lead->activities()->attach($activity->id); break; case 'trigger_webhook': try { $this->triggerWebhook($action['value'], $lead); } catch (\Exception $e) { report($e); } break; } } } } ================================================ FILE: packages/Webkul/Automation/src/Helpers/Entity/Person.php ================================================ personRepository->find($entity); } return $entity; } /** * Returns workflow actions. */ public function getActions(): array { $emailTemplates = $this->emailTemplateRepository->all(['id', 'name']); $webhooksOptions = $this->webhookRepository->all(['id', 'name']); return [ [ 'id' => 'update_person', 'name' => trans('admin::app.settings.workflows.helpers.update-person'), 'attributes' => $this->getAttributes('persons'), ], [ 'id' => 'update_related_leads', 'name' => trans('admin::app.settings.workflows.helpers.update-related-leads'), 'attributes' => $this->getAttributes('leads'), ], [ 'id' => 'send_email_to_person', 'name' => trans('admin::app.settings.workflows.helpers.send-email-to-person'), 'options' => $emailTemplates, ], [ 'id' => 'trigger_webhook', 'name' => trans('admin::app.settings.workflows.helpers.add-webhook'), 'options' => $webhooksOptions, ], ]; } /** * Execute workflow actions. */ public function executeActions(mixed $workflow, mixed $person): void { foreach ($workflow->actions as $action) { switch ($action['id']) { case 'update_person': $this->personRepository->update([ 'entity_type' => 'persons', $action['attribute'] => $action['value'], ], $person->id); break; case 'update_related_leads': $leads = $this->leadRepository->findByField('person_id', $person->id); foreach ($leads as $lead) { $this->leadRepository->update( [ 'entity_type' => 'leads', $action['attribute'] => $action['value'], ], $lead->id, [$action['attribute']] ); } break; case 'send_email_to_person': $emailTemplate = $this->emailTemplateRepository->find($action['value']); if (! $emailTemplate) { break; } try { Mail::queue(new Common([ 'to' => data_get($person->emails, '*.value'), 'subject' => $this->replacePlaceholders($person, $emailTemplate->subject), 'body' => $this->replacePlaceholders($person, $emailTemplate->content), ])); } catch (\Exception $e) { report($e); } break; case 'trigger_webhook': try { $this->triggerWebhook($action['value'], $person); } catch (\Exception $e) { report($e); } break; } } } } ================================================ FILE: packages/Webkul/Automation/src/Helpers/Entity/Quote.php ================================================ quoteRepository->find($entity); } return $entity; } /** * Returns workflow actions. */ public function getActions(): array { $emailTemplates = $this->emailTemplateRepository->all(['id', 'name']); $webhookOptions = $this->webhookRepository->all(['id', 'name']); return [ [ 'id' => 'update_quote', 'name' => trans('admin::app.settings.workflows.helpers.update-quote'), 'attributes' => $this->getAttributes('quotes'), ], [ 'id' => 'update_person', 'name' => trans('admin::app.settings.workflows.helpers.update-person'), 'attributes' => $this->getAttributes('persons'), ], [ 'id' => 'update_related_leads', 'name' => trans('admin::app.settings.workflows.helpers.update-related-leads'), 'attributes' => $this->getAttributes('leads'), ], [ 'id' => 'send_email_to_person', 'name' => trans('admin::app.settings.workflows.helpers.send-email-to-person'), 'options' => $emailTemplates, ], [ 'id' => 'send_email_to_sales_owner', 'name' => trans('admin::app.settings.workflows.helpers.send-email-to-sales-owner'), 'options' => $emailTemplates, ], [ 'id' => 'trigger_webhook', 'name' => trans('admin::app.settings.workflows.helpers.add-webhook'), 'options' => $webhookOptions, ], ]; } /** * Execute workflow actions. */ public function executeActions(mixed $workflow, mixed $quote): void { foreach ($workflow->actions as $action) { switch ($action['id']) { case 'update_quote': $this->quoteRepository->update([ 'entity_type' => 'quotes', $action['attribute'] => $action['value'], ], $quote->id); break; case 'update_person': $this->personRepository->update([ 'entity_type' => 'persons', $action['attribute'] => $action['value'], ], $quote->person_id); break; case 'update_related_leads': foreach ($quote->leads as $lead) { $this->leadRepository->update( [ 'entity_type' => 'leads', $action['attribute'] => $action['value'], ], $lead->id, [$action['attribute']] ); } break; case 'send_email_to_person': $emailTemplate = $this->emailTemplateRepository->find($action['value']); if (! $emailTemplate) { break; } try { Mail::queue(new Common([ 'to' => data_get($quote->person->emails, '*.value'), 'subject' => $this->replacePlaceholders($quote, $emailTemplate->subject), 'body' => $this->replacePlaceholders($quote, $emailTemplate->content), ])); } catch (\Exception $e) { } break; case 'send_email_to_sales_owner': $emailTemplate = $this->emailTemplateRepository->find($action['value']); if (! $emailTemplate) { break; } try { Mail::queue(new Common([ 'to' => $quote->user->email, 'subject' => $this->replacePlaceholders($quote, $emailTemplate->subject), 'body' => $this->replacePlaceholders($quote, $emailTemplate->content), ])); } catch (\Exception $e) { } break; case 'trigger_webhook': try { $this->triggerWebhook($action['value'], $quote); } catch (\Exception $e) { report($e); } break; } } } } ================================================ FILE: packages/Webkul/Automation/src/Helpers/Entity.php ================================================ $entity) { $object = app($entity['class']); $events[$key] = [ 'id' => $key, 'name' => $entity['name'], 'events' => $entity['events'], ]; } return $events; } /** * Returns conditions to match for the entity * * @return array */ public function getConditions() { $entities = config('workflows.trigger_entities'); $conditions = []; foreach ($entities as $key => $entity) { $object = app($entity['class']); $conditions[$key] = $object->getConditions(); } return $conditions; } /** * Returns workflow actions * * @return array */ public function getActions() { $entities = config('workflows.trigger_entities'); $conditions = []; foreach ($entities as $key => $entity) { $object = app($entity['class']); $conditions[$key] = $object->getActions(); } return $conditions; } /** * Returns placeholders for email templates * * @return array */ public function getEmailTemplatePlaceholders() { $entities = config('workflows.trigger_entities'); $placeholders = []; foreach ($entities as $key => $entity) { $object = app($entity['class']); $placeholders[] = $object->getEmailTemplatePlaceholders($entity); } return $placeholders; } } ================================================ FILE: packages/Webkul/Automation/src/Helpers/Validator.php ================================================ conditions) { return true; } $validConditionCount = $totalConditionCount = 0; foreach ($workflow->conditions as $condition) { if (! $condition['attribute'] || ! isset($condition['value']) || is_null($condition['value']) || $condition['value'] == '' ) { continue; } $totalConditionCount++; if ($workflow->condition_type == 'and') { if (! $this->validateEntity($condition, $entity)) { return false; } else { $validConditionCount++; } } else { if ($this->validateEntity($condition, $entity)) { return true; } } } return $validConditionCount == $totalConditionCount ? true : false; } /** * Validate object * * @param array $condition * @param mixed $entity * @return bool */ private function validateEntity($condition, $entity) { return $this->validateAttribute( $condition, $this->getAttributeValue($condition, $entity) ); } /** * Return value for the attribute * * @param array $condition * @param mixed $entity * @return bool */ public function getAttributeValue($condition, $entity) { $value = $entity->{$condition['attribute']}; if (! in_array($condition['attribute_type'], ['multiselect', 'checkbox'])) { return $value; } return $value ? explode(',', $value) : []; } /** * Validate attribute value for condition * * @param array $condition * @param mixed $attributeValue * @return bool */ public function validateAttribute($condition, $attributeValue) { switch ($condition['operator']) { case '==': case '!=': if (is_array($condition['value'])) { if (! is_array($attributeValue)) { return false; } $result = ! empty(array_intersect($condition['value'], $attributeValue)); } elseif (is_object($attributeValue)) { $result = $attributeValue->value == $condition['value']; } else { if (is_array($attributeValue)) { $result = count($attributeValue) == 1 && array_shift($attributeValue) == $condition['value']; } else { $result = $attributeValue == $condition['value']; } } break; case '<=': case '>': if (! is_scalar($attributeValue)) { return false; } $result = $attributeValue <= $condition['value']; break; case '>=': case '<': if (! is_scalar($attributeValue)) { return false; } $result = $attributeValue >= $condition['value']; break; case '{}': case '!{}': if (is_scalar($attributeValue) && is_array($condition['value'])) { foreach ($condition['value'] as $item) { if (stripos($attributeValue, $item) !== false) { $result = true; break; } } } elseif (is_array($condition['value'])) { if (! is_array($attributeValue)) { return false; } $result = ! empty(array_intersect($condition['value'], $attributeValue)); } else { if (is_array($attributeValue)) { $result = self::validateArrayValues($attributeValue, $condition['value']); } else { $result = strpos($attributeValue, $condition['value']) !== false; } } break; } if (in_array($condition['operator'], ['!=', '>', '<', '!{}'])) { $result = ! $result; } return $result; } /** * Validate the condition value against a multi dimensional array recursively */ private static function validateArrayValues(array $attributeValue, string $conditionValue): bool { if (in_array($conditionValue, $attributeValue, true) === true) { return true; } foreach ($attributeValue as $subValue) { if (! is_array($subValue)) { continue; } if (self::validateArrayValues($subValue, $conditionValue) === true) { return true; } } return false; } } ================================================ FILE: packages/Webkul/Automation/src/Listeners/Entity.php ================================================ workflowRepository->findByField('event', $eventName); foreach ($workflows as $workflow) { $workflowEntity = app(config('workflows.trigger_entities.'.$workflow->entity_type.'.class')); $entity = $workflowEntity->getEntity($entity); if (! $this->validator->validate($workflow, $entity)) { continue; } try { $workflowEntity->executeActions($workflow, $entity); } catch (\Exception $e) { logger()->error($e->getMessage()); } } } } ================================================ FILE: packages/Webkul/Automation/src/Models/Webhook.php ================================================ */ protected $fillable = [ 'name', 'entity_type', 'description', 'method', 'end_point', 'query_params', 'headers', 'payload_type', 'raw_payload_type', 'payload', ]; /** * The attributes that should be cast to native types. * * @var array */ protected $casts = [ 'query_params' => 'array', 'headers' => 'array', 'payload' => 'array', ]; } ================================================ FILE: packages/Webkul/Automation/src/Models/WebhookProxy.php ================================================ 'array', 'actions' => 'array', ]; protected $fillable = [ 'name', 'description', 'entity_type', 'event', 'condition_type', 'conditions', 'actions', ]; } ================================================ FILE: packages/Webkul/Automation/src/Models/WorkflowProxy.php ================================================ loadMigrationsFrom(__DIR__.'/../Database/Migrations'); Event::listen('*', function ($eventName, array $data) { if (! in_array($eventName, data_get(config('workflows.trigger_entities'), '*.events.*.event'))) { return; } app(Entity::class)->process($eventName, current($data)); }); } /** * Register services. * * @return void */ public function register() { $this->registerConfig(); } /** * Register package config. * * @return void */ protected function registerConfig() { $this->mergeConfigFrom(dirname(__DIR__).'/Config/workflows.php', 'workflows'); } } ================================================ FILE: packages/Webkul/Automation/src/Repositories/WebhookRepository.php ================================================ client = new Client([ 'timeout' => 30, 'connect_timeout' => 10, 'verify' => true, 'http_errors' => false, ]); } /** * Trigger the webhook. */ public function triggerWebhook(mixed $data): array { if ( ! isset($data['method']) || ! isset($data['end_point']) ) { return [ 'status' => 'error', 'response' => 'Missing required fields: method or end_point', ]; } $headers = isset($data['headers']) ? $this->parseJsonField($data['headers']) : []; $payload = isset($data['payload']) ? $data['payload'] : null; $data['end_point'] = $this->appendQueryParams($data['end_point'], $data['query_params'] ?? ''); $formattedHeaders = $this->formatHeaders($headers); $options = $this->buildRequestOptions($data['method'], $formattedHeaders, $payload); try { $response = $this->client->request( strtoupper($data['method']), $data['end_point'], $options, ); return [ 'status' => 'success', 'response' => $response->getBody()->getContents(), 'status_code' => $response->getStatusCode(), 'headers' => $response->getHeaders(), ]; } catch (RequestException $e) { return [ 'status' => 'error', 'response' => $e->hasResponse() ? Message::toString($e->getResponse()) : $e->getMessage(), 'status_code' => $e->hasResponse() ? $e->getResponse()->getStatusCode() : null, ]; } } /** * Parse JSON field safely. */ protected function parseJsonField(mixed $field): array { if (is_array($field)) { return $field; } if (is_string($field)) { $decoded = json_decode($field, true); if ( json_last_error() === JSON_ERROR_NONE && is_array($decoded) ) { return $decoded; } } return []; } /** * Build request options based on method and content type. */ protected function buildRequestOptions(string $method, array $headers, mixed $payload): array { $options = []; if (! empty($headers)) { $options['headers'] = $headers; } if ( $payload !== null && ! in_array(strtoupper($method), ['GET', 'HEAD']) ) { $contentType = $this->getContentType($headers); switch ($contentType) { case 'application/json': $options['json'] = $this->prepareJsonPayload($payload); break; case 'application/x-www-form-urlencoded': $options['form_params'] = $this->prepareFormPayload($payload); break; case 'multipart/form-data': $options['multipart'] = $this->prepareMultipartPayload($payload); break; case 'text/plain': case 'text/xml': case 'application/xml': $options['body'] = $this->prepareRawPayload($payload); break; default: $options = array_merge($options, $this->autoDetectPayloadFormat($payload)); break; } } return $options; } /** * Prepare JSON payload. */ protected function prepareJsonPayload(mixed $payload): mixed { if (is_string($payload)) { $decoded = json_decode($payload, true); if (json_last_error() === JSON_ERROR_NONE) { return $decoded; } return $payload; } if (is_array($payload)) { return $this->formatPayload($payload); } return $payload; } /** * Prepare form payload. */ protected function prepareFormPayload(mixed $payload): array { if (is_string($payload)) { $decoded = json_decode($payload, true); if ( json_last_error() === JSON_ERROR_NONE && is_array($decoded) ) { return $this->formatPayload($decoded); } parse_str($payload, $parsed); return $parsed ?: []; } if (is_array($payload)) { return $this->formatPayload($payload); } return []; } /** * Prepare multipart payload. */ protected function prepareMultipartPayload(mixed $payload): array { $formattedPayload = $this->prepareFormPayload($payload); return $this->buildMultipartData($formattedPayload); } /** * Prepare raw payload. */ protected function prepareRawPayload(mixed $payload): string { if (is_string($payload)) { return $payload; } if (is_array($payload)) { return json_encode($payload); } return (string) $payload; } /** * Auto-detect payload format when no content-type is specified. */ protected function autoDetectPayloadFormat(mixed $payload): array { if (is_string($payload)) { $decoded = json_decode($payload, true); if (json_last_error() === JSON_ERROR_NONE) { return ['json' => $decoded]; } if ( strpos($payload, '=') !== false && strpos($payload, '&') !== false ) { parse_str($payload, $parsed); return ['form_params' => $parsed]; } return ['body' => $payload]; } if (is_array($payload)) { $formatted = $this->formatPayload($payload); return ['json' => $formatted]; } return ['body' => (string) $payload]; } /** * Get content type from headers. */ protected function getContentType(array $headers): string { foreach ($headers as $key => $value) { if (strtolower($key) === 'content-type') { $contentType = strtolower(trim(explode(';', $value)[0])); return $contentType; } } return ''; } /** * Build multipart data array. */ protected function buildMultipartData(array $payload): array { $multipart = []; foreach ($payload as $key => $value) { $multipart[] = [ 'name' => $key, 'contents' => is_array($value) ? json_encode($value) : (string) $value, ]; } return $multipart; } /** * Format headers array. */ protected function formatHeaders(array $headers): array { if (empty($headers)) { return []; } $formattedHeaders = []; if ($this->isKeyValuePairArray($headers)) { foreach ($headers as $header) { if ( isset($header['key']) && array_key_exists('value', $header) ) { if ( isset($header['disabled']) && $header['disabled'] ) { continue; } if ( isset($header['enabled']) && ! $header['enabled'] ) { continue; } $formattedHeaders[$header['key']] = $header['value']; } } } else { $formattedHeaders = $headers; } return $formattedHeaders; } /** * Format any incoming payload into a clean associative array. */ protected function formatPayload(mixed $payload): array { if (empty($payload)) { return []; } if ( is_array($payload) && isset($payload['key']) && array_key_exists('value', $payload) ) { return [$payload['key'] => $payload['value']]; } if ( is_array($payload) && array_is_list($payload) && $this->isKeyValuePairArray($payload) ) { $formatted = []; foreach ($payload as $item) { if ( isset($item['key']) && array_key_exists('value', $item) ) { if ( isset($item['disabled']) && $item['disabled'] ) { continue; } if ( isset($item['enabled']) && ! $item['enabled'] ) { continue; } $formatted[$item['key']] = $item['value']; } } return $formatted; } return is_array($payload) ? $payload : []; } /** * Check if array is a key-value pair array. */ protected function isKeyValuePairArray(array $array): bool { if (empty($array)) { return false; } if ( isset($array['key']) && array_key_exists('value', $array) ) { return true; } if (array_is_list($array)) { return collect($array)->every(fn ($item) => is_array($item) && isset($item['key']) && array_key_exists('value', $item) ); } return false; } /** * Append query parameters to the endpoint URL. */ protected function appendQueryParams(string $endPoint, string $queryParamsJson): string { $queryParams = json_decode($queryParamsJson, true); if ( json_last_error() !== JSON_ERROR_NONE || ! is_array($queryParams) ) { return $endPoint; } $queryArray = []; foreach ($queryParams as $param) { if ( isset($param['key']) && array_key_exists('value', $param) ) { $queryArray[$param['key']] = $param['value']; } } $queryString = http_build_query($queryArray); $glue = str_contains($endPoint, '?') ? '&' : '?'; return $endPoint.($queryString ? $glue.$queryString : ''); } } ================================================ FILE: packages/Webkul/Contact/composer.json ================================================ { "name": "krayin/laravel-contact", "license": "MIT", "authors": [ { "name": "Jitendra Singh", "email": "jitendra@webkul.com" } ], "require": { "krayin/laravel-attribute": "^1.0", "krayin/laravel-core": "^1.0" }, "autoload": { "psr-4": { "Webkul\\Contact\\": "src/" } }, "extra": { "laravel": { "providers": [ "Webkul\\Contact\\Providers\\ContactServiceProvider" ], "aliases": {} } }, "minimum-stability": "dev" } ================================================ FILE: packages/Webkul/Contact/src/Contracts/Organization.php ================================================ $this->faker->name(), 'emails' => [$this->faker->unique()->safeEmail()], 'contact_numbers' => [$this->faker->randomNumber(9)], ]; } } ================================================ FILE: packages/Webkul/Contact/src/Database/Migrations/2021_04_09_051326_create_organizations_table.php ================================================ increments('id'); $table->string('name'); $table->json('address')->nullable(); $table->timestamps(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('organizations'); } }; ================================================ FILE: packages/Webkul/Contact/src/Database/Migrations/2021_04_09_065617_create_persons_table.php ================================================ increments('id'); $table->string('name'); $table->json('emails'); $table->json('contact_numbers')->nullable(); $table->integer('organization_id')->unsigned()->nullable(); $table->foreign('organization_id')->references('id')->on('organizations')->onDelete('cascade'); $table->timestamps(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('persons'); } }; ================================================ FILE: packages/Webkul/Contact/src/Database/Migrations/2021_09_22_194103_add_unique_index_to_name_in_organizations_table.php ================================================ unique('name'); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::table('organizations', function (Blueprint $table) { $table->dropUnique('organizations_name_unique'); }); } }; ================================================ FILE: packages/Webkul/Contact/src/Database/Migrations/2024_07_31_092951_add_job_title_in_persons_table.php ================================================ string('job_title')->nullable(); }); } /** * Reverse the migrations. */ public function down(): void { Schema::table('persons', function (Blueprint $table) { $table->dropColumn('job_title'); }); } }; ================================================ FILE: packages/Webkul/Contact/src/Database/Migrations/2024_08_06_145943_create_person_tags_table.php ================================================ integer('tag_id')->unsigned(); $table->foreign('tag_id')->references('id')->on('tags')->onDelete('cascade'); $table->integer('person_id')->unsigned(); $table->foreign('person_id')->references('id')->on('persons')->onDelete('cascade'); }); } /** * Reverse the migrations. */ public function down(): void { Schema::dropIfExists('person_tags'); } }; ================================================ FILE: packages/Webkul/Contact/src/Database/Migrations/2024_08_06_161212_create_person_activities_table.php ================================================ integer('activity_id')->unsigned(); $table->foreign('activity_id')->references('id')->on('activities')->onDelete('cascade'); $table->integer('person_id')->unsigned(); $table->foreign('person_id')->references('id')->on('persons')->onDelete('cascade'); }); } /** * Reverse the migrations. */ public function down(): void { Schema::dropIfExists('lead_activities'); } }; ================================================ FILE: packages/Webkul/Contact/src/Database/Migrations/2024_08_14_102116_add_user_id_column_in_persons_table.php ================================================ integer('user_id')->unsigned()->nullable(); $table->foreign('user_id')->references('id')->on('users')->onDelete('set null'); }); } /** * Reverse the migrations. */ public function down(): void { Schema::table('persons', function (Blueprint $table) { $table->dropForeign(['user_id']); $table->dropColumn('user_id'); }); } }; ================================================ FILE: packages/Webkul/Contact/src/Database/Migrations/2024_08_14_102136_add_user_id_column_in_organizations_table.php ================================================ integer('user_id')->unsigned()->nullable(); $table->foreign('user_id')->references('id')->on('users')->onDelete('set null'); }); } /** * Reverse the migrations. */ public function down(): void { Schema::table('organizations', function (Blueprint $table) { $table->dropForeign(['user_id']); $table->dropColumn('user_id'); }); } }; ================================================ FILE: packages/Webkul/Contact/src/Database/Migrations/2024_09_09_112201_add_unique_id_to_person_table.php ================================================ string('unique_id')->nullable()->unique(); }); $tableName = DB::getTablePrefix().'persons'; DB::statement(" UPDATE {$tableName} SET unique_id = CONCAT( user_id, '|', organization_id, '|', JSON_UNQUOTE(JSON_EXTRACT(emails, '$[0].value')), '|', JSON_UNQUOTE(JSON_EXTRACT(contact_numbers, '$[0].value')) ) "); } /** * Reverse the migrations. */ public function down(): void { Schema::table('persons', function (Blueprint $table) { $table->dropColumn('unique_id'); }); } }; ================================================ FILE: packages/Webkul/Contact/src/Database/Migrations/2025_03_19_132236_update_organization_id_column_in_persons_table.php ================================================ dropForeign(['organization_id']); $table->foreign('organization_id')->references('id')->on('organizations')->onDelete('set null'); }); } /** * Reverse the migrations. */ public function down(): void { Schema::table('persons', function (Blueprint $table) { $table->dropForeign(['organization_id']); $table->foreign('organization_id')->references('id')->on('organizations')->onDelete('cascade'); }); } }; ================================================ FILE: packages/Webkul/Contact/src/Models/Organization.php ================================================ 'array', ]; /** * The attributes that are mass assignable. * * @var array */ protected $fillable = [ 'name', 'address', 'user_id', ]; /** * Get persons. * * @return HasMany */ public function persons() { return $this->hasMany(PersonProxy::modelClass()); } /** * Get the user that owns the lead. */ public function user() { return $this->belongsTo(UserProxy::modelClass()); } } ================================================ FILE: packages/Webkul/Contact/src/Models/OrganizationProxy.php ================================================ 'array', 'contact_numbers' => 'array', ]; /** * The attributes that are mass assignable. * * @var array */ protected $fillable = [ 'name', 'emails', 'contact_numbers', 'job_title', 'user_id', 'organization_id', 'unique_id', ]; /** * Get the user that owns the lead. */ public function user(): BelongsTo { return $this->belongsTo(UserProxy::modelClass()); } /** * Get the organization that owns the person. */ public function organization(): BelongsTo { return $this->belongsTo(OrganizationProxy::modelClass()); } /** * Get the activities. */ public function activities(): BelongsToMany { return $this->belongsToMany(ActivityProxy::modelClass(), 'person_activities'); } /** * The tags that belong to the person. */ public function tags(): BelongsToMany { return $this->belongsToMany(TagProxy::modelClass(), 'person_tags'); } /** * Get the leads for the person. */ public function leads(): HasMany { return $this->hasMany(LeadProxy::modelClass(), 'person_id'); } /** * Create a new factory instance for the model. */ protected static function newFactory(): PersonFactory { return PersonFactory::new(); } } ================================================ FILE: packages/Webkul/Contact/src/Models/PersonProxy.php ================================================ loadMigrationsFrom(__DIR__.'/../Database/Migrations'); } /** * Register services. * * @return void */ public function register() {} } ================================================ FILE: packages/Webkul/Contact/src/Providers/ModuleServiceProvider.php ================================================ attributeValueRepository->save(array_merge($data, [ 'entity_id' => $organization->id, ])); return $organization; } /** * Update. * * @param int $id * @param array $attribute * @return Organization */ public function update(array $data, $id, $attributes = []) { if (isset($data['user_id'])) { $data['user_id'] = $data['user_id'] ?: null; } $organization = parent::update($data, $id); /** * If attributes are provided then only save the provided attributes and return. */ if (! empty($attributes)) { $conditions = ['entity_type' => $data['entity_type']]; if (isset($data['quick_add'])) { $conditions['quick_add'] = 1; } $attributes = $this->attributeRepository->where($conditions) ->whereIn('code', $attributes) ->get(); $this->attributeValueRepository->save(array_merge($data, [ 'entity_id' => $organization->id, ]), $attributes); return $organization; } $this->attributeValueRepository->save(array_merge($data, [ 'entity_id' => $organization->id, ])); return $organization; } /** * Delete organization and it's persons. * * @param int $id * @return @void */ public function delete($id) { $organization = $this->findOrFail($id); DB::transaction(function () use ($organization, $id) { $this->attributeValueRepository->deleteWhere([ 'entity_id' => $id, 'entity_type' => 'organizations', ]); $organization->delete(); }); } } ================================================ FILE: packages/Webkul/Contact/src/Repositories/PersonRepository.php ================================================ sanitizeRequestedPersonData($data); if (! empty($data['organization_name'])) { $organization = $this->fetchOrCreateOrganizationByName($data['organization_name']); $data['organization_id'] = $organization->id; } if (isset($data['user_id'])) { $data['user_id'] = $data['user_id'] ?: null; } $person = parent::create($data); $this->attributeValueRepository->save(array_merge($data, [ 'entity_id' => $person->id, ])); return $person; } /** * Update. * * @return Person */ public function update(array $data, $id, $attributes = []) { $data = $this->sanitizeRequestedPersonData($data); $data['user_id'] = empty($data['user_id']) ? null : $data['user_id']; if (! empty($data['organization_name'])) { $organization = $this->fetchOrCreateOrganizationByName($data['organization_name']); $data['organization_id'] = $organization->id; unset($data['organization_name']); } $person = parent::update($data, $id); /** * If attributes are provided then only save the provided attributes and return. */ if (! empty($attributes)) { $conditions = ['entity_type' => $data['entity_type']]; if (isset($data['quick_add'])) { $conditions['quick_add'] = 1; } $attributes = $this->attributeRepository->where($conditions) ->whereIn('code', $attributes) ->get(); $this->attributeValueRepository->save(array_merge($data, [ 'entity_id' => $person->id, ]), $attributes); return $person; } $this->attributeValueRepository->save(array_merge($data, [ 'entity_id' => $person->id, ])); return $person; } /** * Retrieves customers count based on date. * * @return int */ public function getCustomerCount($startDate, $endDate) { return $this ->whereBetween('created_at', [$startDate, $endDate]) ->get() ->count(); } /** * Fetch or create an organization. */ public function fetchOrCreateOrganizationByName(string $organizationName) { $organization = $this->organizationRepository->findOneWhere([ 'name' => $organizationName, ]); return $organization ?: $this->organizationRepository->create([ 'entity_type' => 'organizations', 'name' => $organizationName, ]); } /** * Sanitize requested person data and return the clean array. */ private function sanitizeRequestedPersonData(array $data): array { if ( array_key_exists('organization_id', $data) && empty($data['organization_id']) ) { $data['organization_id'] = null; } $uniqueIdParts = array_filter([ $data['user_id'] ?? null, $data['organization_id'] ?? null, $data['emails'][0]['value'] ?? null, ]); $data['unique_id'] = implode('|', $uniqueIdParts); if (isset($data['contact_numbers'])) { $data['contact_numbers'] = collect($data['contact_numbers'])->filter(fn ($number) => ! is_null($number['value']))->toArray(); $data['unique_id'] .= '|'.$data['contact_numbers'][0]['value']; } return $data; } } ================================================ FILE: packages/Webkul/Core/composer.json ================================================ { "name": "krayin/laravel-core", "license": "MIT", "authors": [ { "name": "Jitendra Singh", "email": "jitendra@webkul.com" } ], "require": { }, "autoload": { "psr-4": { "Webkul\\Core\\": "src/" } }, "extra": { "laravel": { "providers": [ "Webkul\\Core\\Providers\\CoreServiceProvider" ], "aliases": {} } }, "minimum-stability": "dev" } ================================================ FILE: packages/Webkul/Core/src/Acl/AclItem.php ================================================ items[] = $aclItem; } /** * Get all acl items. */ public function getItems(): Collection { if (! $this->items) { $this->prepareAclItems(); } return collect($this->items) ->sortBy('sort') ->values(); } /** * Acl Config. */ private function getAclConfig(): array { static $aclConfig; if ($aclConfig) { return $aclConfig; } $aclConfig = config('acl'); return $aclConfig; } /** * Get all roles. */ public function getRoles(): Collection { static $roles; if ($roles) { return $roles; } $roles = collect($this->getAclConfig()) ->mapWithKeys(function ($role) { if (is_array($role['route'])) { return collect($role['route'])->mapWithKeys(function ($route) use ($role) { return [$route => $role['key']]; }); } else { return [$role['route'] => $role['key']]; } }); return $roles; } /** * Prepare acl items. */ private function prepareAclItems(): void { $aclWithDotNotation = []; foreach ($this->getAclConfig() as $item) { $aclWithDotNotation[$item['key']] = $item; } $acl = Arr::undot(Arr::dot($aclWithDotNotation)); foreach ($acl as $aclItemKey => $aclItem) { $subAclItems = $this->processSubAclItems($aclItem); $this->addItem(new AclItem( key: $aclItemKey, name: trans($aclItem['name']), route: $aclItem['route'], sort: $aclItem['sort'], children: $subAclItems, )); } } /** * Process sub acl items. */ private function processSubAclItems($aclItem): Collection { return collect($aclItem) ->sortBy('sort') ->filter(fn ($value, $key) => is_array($value) && $key !== 'route') ->map(function ($subAclItem) { $subSubAclItems = $this->processSubAclItems($subAclItem); return new AclItem( key: $subAclItem['key'], name: trans($subAclItem['name']), route: $subAclItem['route'], sort: $subAclItem['sort'], children: $subSubAclItems, ); }); } } ================================================ FILE: packages/Webkul/Core/src/Config/concord.php ================================================ [ DataTransferModuleServiceProvider::class, AdminModuleServiceProvider::class, AttributeModuleServiceProvider::class, AutomationModuleServiceProvider::class, ContactModuleServiceProvider::class, CoreModuleServiceProvider::class, DataGridModuleServiceProvider::class, EmailTemplateModuleServiceProvider::class, EmailModuleServiceProvider::class, LeadModuleServiceProvider::class, ProductModuleServiceProvider::class, QuoteModuleServiceProvider::class, TagModuleServiceProvider::class, UserModuleServiceProvider::class, WarehouseModuleServiceProvider::class, WebFormModuleServiceProvider::class, DataTransferModuleServiceProvider::class, ], 'register_route_models' => true, ]; ================================================ FILE: packages/Webkul/Core/src/Config/cors.php ================================================ [ 'admin/web-forms/forms/*', ], /* * Matches the request method. `['*']` allows all methods. */ 'allowed_methods' => ['*'], /* * Matches the request origin. `['*']` allows all origins. Wildcards can be used, eg `*.mydomain.com` */ 'allowed_origins' => ['*'], /* * Patterns that can be used with `preg_match` to match the origin. */ 'allowed_origins_patterns' => [], /* * Sets the Access-Control-Allow-Headers response header. `['*']` allows all headers. */ 'allowed_headers' => ['*'], /* * Sets the Access-Control-Expose-Headers response header with these headers. */ 'exposed_headers' => [], /* * Sets the Access-Control-Max-Age response header when > 0. */ 'max_age' => 0, /* * Sets the Access-Control-Allow-Credentials header. */ 'supports_credentials' => false, ]; ================================================ FILE: packages/Webkul/Core/src/Config/sanctum.php ================================================ explode(',', env('SANCTUM_STATEFUL_DOMAINS', sprintf( '%s%s', 'localhost,localhost:3000,127.0.0.1,127.0.0.1:8000,::1', env('APP_URL') ? ','.parse_url(env('APP_URL'), PHP_URL_HOST) : '' ))), /* |-------------------------------------------------------------------------- | Sanctum Guards |-------------------------------------------------------------------------- | | This array contains the authentication guards that will be checked when | Sanctum is trying to authenticate a request. If none of these guards | are able to authenticate the request, Sanctum will use the bearer | token that's present on an incoming request for authentication. | */ 'guard' => ['user'], /* |-------------------------------------------------------------------------- | Expiration Minutes |-------------------------------------------------------------------------- | | This value controls the number of minutes until an issued token will be | considered expired. If this value is null, personal access tokens do | not expire. This won't tweak the lifetime of first-party sessions. | */ 'expiration' => null, /* |-------------------------------------------------------------------------- | Sanctum Middleware |-------------------------------------------------------------------------- | | When authenticating your first-party SPA with Sanctum you may need to | customize some of the middleware Sanctum uses while processing the | request. You may change the middleware listed below as required. | */ 'middleware' => [ 'verify_csrf_token' => VerifyCsrfToken::class, 'encrypt_cookies' => EncryptCookies::class, ], ]; ================================================ FILE: packages/Webkul/Core/src/Console/Commands/Version.php ================================================ comment('v'.core()->version()); } } ================================================ FILE: packages/Webkul/Core/src/Contracts/CoreConfig.php ================================================ $attribute])); } } } ================================================ FILE: packages/Webkul/Core/src/Core.php ================================================ $title) { $options[] = [ 'title' => $title, 'value' => $key, ]; } return $options; } /** * Retrieve all countries. * * @return Collection */ public function countries() { return $this->countryRepository->all(); } /** * Returns country name by code. */ public function country_name(string $code): string { $country = $this->countryRepository->findOneByField('code', $code); return $country ? $country->name : ''; } /** * Returns state name by code. */ public function state_name(string $code): string { $state = $this->countryStateRepository->findOneByField('code', $code); return $state ? $state->name : $code; } /** * Retrieve all country states. * * @return Collection */ public function states(string $countryCode) { return $this->countryStateRepository->findByField('country_code', $countryCode); } /** * Retrieve all grouped states by country code. * * @return Collection */ public function groupedStatesByCountries() { $collection = []; foreach ($this->countryStateRepository->all() as $state) { $collection[$state->country_code][] = $state->toArray(); } return $collection; } /** * Retrieve all grouped states by country code. * * @return Collection */ public function findStateByCountryCode($countryCode = null, $stateCode = null) { $collection = []; $collection = $this->countryStateRepository->findByField([ 'country_code' => $countryCode, 'code' => $stateCode, ]); if (count($collection)) { return $collection->first(); } else { return false; } } /** * Create singleton object through single facade. * * @param string $className * @return mixed */ public function getSingletonInstance($className) { static $instances = []; if (array_key_exists($className, $instances)) { return $instances[$className]; } return $instances[$className] = app($className); } /** * Format date * * @return string */ public function formatDate($date, $format = 'd M Y h:iA') { return Carbon::parse($date)->format($format); } /** * Week range. * * @param string $date * @param int $day * @return string */ public function xWeekRange($date, $day) { $ts = strtotime($date); if (! $day) { $start = (date('D', $ts) == 'Sun') ? $ts : strtotime('last sunday', $ts); return date('Y-m-d', $start); } else { $end = (date('D', $ts) == 'Sat') ? $ts : strtotime('next saturday', $ts); return date('Y-m-d', $end); } } /** * Return currency symbol from currency code. * * @param float $price * @return string */ public function currencySymbol($code) { $formatter = new \NumberFormatter(app()->getLocale().'@currency='.$code, \NumberFormatter::CURRENCY); return $formatter->getSymbol(\NumberFormatter::CURRENCY_SYMBOL); } /** * Format price with base currency symbol. This method also give ability to encode * the base currency symbol and its optional. * * @param float $price * @return string */ public function formatBasePrice($price) { if (is_null($price)) { $price = 0; } $formatter = new \NumberFormatter(app()->getLocale(), \NumberFormatter::CURRENCY); return $formatter->formatCurrency($price, config('app.currency')); } /** * Get the config field. */ public function getConfigField(string $fieldName): ?array { return system_config()->getConfigField($fieldName); } /** * Retrieve information for configuration. */ public function getConfigData(string $field): mixed { return system_config()->getConfigData($field); } } ================================================ FILE: packages/Webkul/Core/src/Database/Migrations/2021_03_12_060658_create_core_config_table.php ================================================ increments('id'); $table->string('code'); $table->string('value'); $table->timestamps(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('core_config'); } }; ================================================ FILE: packages/Webkul/Core/src/Database/Migrations/2021_04_12_173232_create_countries_table.php ================================================ increments('id'); $table->string('code'); $table->string('name'); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('countries'); } }; ================================================ FILE: packages/Webkul/Core/src/Database/Migrations/2021_04_12_173344_create_country_states_table.php ================================================ increments('id'); $table->string('country_code'); $table->string('code'); $table->string('name'); $table->integer('country_id')->unsigned(); $table->foreign('country_id')->references('id')->on('countries')->onDelete('cascade'); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('country_states'); } }; ================================================ FILE: packages/Webkul/Core/src/Database/Migrations/2025_01_29_133500_update_text_column_type_in_core_config_table.php ================================================ text('value')->change(); }); } /** * Reverse the migrations. */ public function down(): void { Schema::table('core_config', function (Blueprint $table) { $table->string('value')->change(); }); } }; ================================================ FILE: packages/Webkul/Core/src/Eloquent/Repository.php ================================================ findByField($field, $value, $columns = ['*']); return $model->first(); } /** * Find data by field and value * * @param string $field * @param string $value * @param array $columns * @return mixed */ public function findOneWhere(array $where, $columns = ['*']) { $model = $this->findWhere($where, $columns); return $model->first(); } /** * Find data by id * * @param int $id * @param array $columns * @return mixed */ public function find($id, $columns = ['*']) { $this->applyCriteria(); $this->applyScope(); $model = $this->model->find($id, $columns); $this->resetModel(); return $this->parserResult($model); } /** * Find data by id * * @param int $id * @param array $columns * @return mixed */ public function findOrFail($id, $columns = ['*']) { $this->applyCriteria(); $this->applyScope(); $model = $this->model->findOrFail($id, $columns); $this->resetModel(); return $this->parserResult($model); } /** * Count results of repository * * @param string $columns * @return int */ public function count(array $where = [], $columns = '*') { $this->applyCriteria(); $this->applyScope(); if ($where) { $this->applyConditions($where); } $result = $this->model->count($columns); $this->resetModel(); $this->resetScope(); return $result; } /** * @param string $columns * @return mixed */ public function sum($columns) { $this->applyCriteria(); $this->applyScope(); $sum = $this->model->sum($columns); $this->resetModel(); return $sum; } /** * @param string $columns * @return mixed */ public function avg($columns) { $this->applyCriteria(); $this->applyScope(); $avg = $this->model->avg($columns); $this->resetModel(); return $avg; } /** * @return mixed */ public function getModel($data = []) { return $this->model; } /** * @throws RepositoryException */ public function resetModel() { $this->makeModel(); return $this; } } ================================================ FILE: packages/Webkul/Core/src/Eloquent/TranslatableModel.php ================================================ defaultLocale) { return $this->defaultLocale; } return config('translatable.locale') ?: app()->make('translator')->getLocale(); } } ================================================ FILE: packages/Webkul/Core/src/Exceptions/ViterNotFound.php ================================================ getLocale(); $path = __DIR__."/../../../$packageName/src/Resources/lang/$currentLocale/app.php"; if (is_string($path) && is_readable($path)) { return include $path; } else { $currentLocale = 'en'; $path = __DIR__."/../../../$packageName/src/Resources/lang/$currentLocale/app.php"; return include $path; } } } ================================================ FILE: packages/Webkul/Core/src/Http/helpers.php ================================================ singleton(ViewRenderEventManager::class); $viewEventManager = app()->make(ViewRenderEventManager::class); $viewEventManager->handleRenderEvent($eventName, $params); return $viewEventManager->render(); } } if (! function_exists('vite')) { /** * Vite helper. */ function vite(): Vite { return app(Vite::class); } } ================================================ FILE: packages/Webkul/Core/src/Menu/MenuItem.php ================================================ name = $name; return $this; } /** * Get name of menu item. */ public function getName(): string { return $this->name; } /** * Set position of menu item. */ public function setPosition(int $sort): self { $this->sort = $sort; return $this; } /** * Get position of menu item. */ public function getPosition() { return $this->sort; } /** * Set icon of menu item. */ public function setIcon(string $icon): self { $this->icon = $icon; return $this; } /** * Get the icon of menu item. */ public function getIcon(): string { return $this->icon; } /** * Set info of menu item. */ public function setInfo(string $info): self { $this->info = $info; return $this; } /** * Get info of menu item. */ public function getInfo(): string { return $this->info; } /** * Set route of menu item. */ public function setRoute(string $route): self { $this->route = $route; return $this; } /** * Get current route. */ public function getRoute(): string { return $this->route; } /** * Set url of menu item. */ public function setUrl(string $url): self { $this->url = $url; return $this; } /** * Get the url of the menu item. */ public function getUrl(): string { return $this->url; } /** * Set the key of the menu item. */ public function setKey(string $key): self { $this->key = $key; return $this; } /** * Get the key of the menu item. */ public function getKey(): string { return $this->key; } /** * Set children of menu item. */ public function setChildren(Collection $children): self { $this->children = $children; return $this; } /** * Check weather menu item have children or not. */ public function haveChildren(): bool { return $this->children->isNotEmpty(); } /** * Get children of menu item. */ public function getChildren(): Collection { if (! $this->haveChildren()) { return collect(); } return $this->children; } /** * Check weather menu item is active or not. */ public function isActive(): bool { if (request()->fullUrlIs($this->getUrl().'*')) { return true; } if ($this->haveChildren()) { foreach ($this->getChildren() as $child) { if ($child->isActive()) { return true; } } } return false; } } ================================================ FILE: packages/Webkul/Core/src/Menu.php ================================================ items[] = $menuItem; } /** * Get all menu items. */ public function getItems(?string $area = null, string $key = ''): Collection { if (! $area) { throw new \Exception('Area must be provided to get menu items.'); } static $items; if ($items) { return $items; } $configMenu = collect(config("menu.$area"))->map(function ($item) { return Arr::except([ ...$item, 'url' => route($item['route'], $item['params'] ?? []), ], ['params']); }); switch ($area) { case self::ADMIN: $this->configMenu = $configMenu ->filter(fn ($item) => bouncer()->hasPermission($item['key'])) ->toArray(); break; default: $this->configMenu = $configMenu->toArray(); break; } if (! $this->items) { $this->prepareMenuItems(); } $items = collect($this->items)->sortBy(fn ($item) => $item->getPosition()); return $items; } /** * Get admin menu by key or keys. */ public function getAdminMenuByKey(array|string $keys): mixed { $items = $this->getItems('admin'); $keysArray = (array) $keys; $filteredItems = $items->filter(fn ($item) => in_array($item->getKey(), $keysArray)); return is_array($keys) ? $filteredItems : $filteredItems->first(); } /** * Prepare menu items. */ private function prepareMenuItems(): void { $menuWithDotNotation = []; foreach ($this->configMenu as $item) { if (strpos(request()->url(), route($item['route'])) !== false) { $this->currentKey = $item['key']; } $menuWithDotNotation[$item['key']] = $item; } $menu = Arr::undot(Arr::dot($menuWithDotNotation)); foreach ($menu as $menuItemKey => $menuItem) { $this->addItem(new MenuItem( key: $menuItemKey, name: trans($menuItem['name']), route: $menuItem['route'], url: $menuItem['url'], sort: $menuItem['sort'], icon: $menuItem['icon-class'], info: trans($menuItem['info'] ?? ''), children: $this->processSubMenuItems($menuItem), )); } } /** * Process sub menu items. */ private function processSubMenuItems($menuItem): Collection { return collect($menuItem) ->sortBy('sort') ->filter(fn ($value) => is_array($value)) ->map(function ($subMenuItem) { $subSubMenuItems = $this->processSubMenuItems($subMenuItem); return new MenuItem( key: $subMenuItem['key'], name: trans($subMenuItem['name']), route: $subMenuItem['route'], url: $subMenuItem['url'], sort: $subMenuItem['sort'], icon: $subMenuItem['icon-class'], info: trans($subMenuItem['info'] ?? ''), children: $subSubMenuItems, ); }); } /** * Get current active menu. */ public function getCurrentActiveMenu(?string $area = null): ?MenuItem { $currentKey = implode('.', array_slice(explode('.', $this->currentKey), 0, 2)); return $this->findMatchingItem($this->getItems($area), $currentKey); } /** * Finding the matching item. */ private function findMatchingItem($items, $currentKey): ?MenuItem { foreach ($items as $item) { if ($item->key == $currentKey) { return $item; } if ($item->haveChildren()) { $matchingChild = $this->findMatchingItem($item->getChildren(), $currentKey); if ($matchingChild) { return $matchingChild; } } } return null; } } ================================================ FILE: packages/Webkul/Core/src/Models/CoreConfig.php ================================================ areMigrationsEnabled()) { $this->registerMigrations(); } if ($this->areModelsEnabled()) { $this->registerModels(); $this->registerEnums(); $this->registerRequestTypes(); } if ($this->areViewsEnabled()) { $this->registerViews(); } if ($routes = $this->config('routes', true)) { $this->registerRoutes($routes); } } } ================================================ FILE: packages/Webkul/Core/src/Providers/CoreServiceProvider.php ================================================ loadMigrationsFrom(__DIR__.'/../Database/Migrations'); $this->loadTranslationsFrom(__DIR__.'/../Resources/lang', 'core'); $this->publishes([ dirname(__DIR__).'/Config/concord.php' => config_path('concord.php'), dirname(__DIR__).'/Config/cors.php' => config_path('cors.php'), dirname(__DIR__).'/Config/sanctum.php' => config_path('sanctum.php'), ]); } /** * Register services. * * @return void */ public function register() { $this->registerCommands(); $this->registerFacades(); } /** * Register Bouncer as a singleton. * * @return void */ protected function registerFacades() { $loader = AliasLoader::getInstance(); $loader->alias('acl', AclFacade::class); $loader->alias('core', CoreFacade::class); $loader->alias('system_config', SystemConfigFacade::class); $loader->alias('menu', MenuFacade::class); $this->app->singleton('acl', fn () => app(Acl::class)); $this->app->singleton('core', fn () => app(Core::class)); $this->app->singleton('system_config', fn () => app()->make(SystemConfig::class)); $this->app->singleton('menu', fn () => app()->make(Menu::class)); } /** * Register the console commands of this package. */ protected function registerCommands(): void { if ($this->app->runningInConsole()) { $this->commands([ Version::class, ]); } } } ================================================ FILE: packages/Webkul/Core/src/Providers/ModuleServiceProvider.php ================================================ getTitle()) ) { return trans($configuration->getTitle()); } if ( method_exists($configuration, 'getName') && ! is_null($configuration->getName()) ) { return trans($configuration->getName()); } return ''; } /** * Get children and fields. */ protected function getChildrenAndFields(mixed $configuration, string $searchTerm, array $path, array &$results): void { if ( method_exists($configuration, 'getChildren') || method_exists($configuration, 'getFields') ) { $children = $configuration->haveChildren() ? $configuration->getChildren() : $configuration->getFields(); $tempPath = array_merge($path, [[ 'key' => $configuration->getKey() ?? null, 'title' => $this->getTranslatedTitle($configuration), ]]); $results = array_merge($results, $this->search($children, $searchTerm, $tempPath)); } } /** * Search configuration. * * @param array $items */ public function search(Collection $items, string $searchTerm, array $path = []): array { $results = []; foreach ($items as $configuration) { $title = $this->getTranslatedTitle($configuration); if ( stripos($title, $searchTerm) !== false && count($path) ) { $queryParam = $path[1]['key'] ?? $configuration->getKey(); $results[] = [ 'title' => implode(' > ', [...Arr::pluck($path, 'title'), $title]), 'url' => route('admin.configuration.index', Str::replace('.', '/', $queryParam)), ]; } $this->getChildrenAndFields($configuration, $searchTerm, $path, $results); } return $results; } /** * Create core configuration. */ public function create(array $data): void { unset($data['_token']); $preparedData = []; foreach ($data as $method => $fieldData) { $recursiveData = $this->recursiveArray($fieldData, $method); foreach ($recursiveData as $fieldName => $value) { if ( is_array($value) && isset($value['delete']) ) { $coreConfigValues = $this->model->where('code', $fieldName)->get(); if ($coreConfigValues->isNotEmpty()) { foreach ($coreConfigValues as $coreConfig) { if (! empty($coreConfig['value'])) { Storage::delete($coreConfig['value']); } parent::delete($coreConfig['id']); } } continue; } } foreach ($recursiveData as $fieldName => $value) { if (is_array($value)) { foreach ($value as $key => $val) { $fieldNameWithKey = $fieldName.'.'.$key; $coreConfigValues = $this->model->where('code', $fieldNameWithKey)->get(); if (request()->hasFile($fieldNameWithKey)) { $val = request()->file($fieldNameWithKey)->store('configuration'); } if ($coreConfigValues->isNotEmpty()) { foreach ($coreConfigValues as $coreConfig) { if (request()->hasFile($fieldNameWithKey)) { Storage::delete($coreConfig['value']); } parent::update(['code' => $fieldNameWithKey, 'value' => $val], $coreConfig->id); } } else { parent::create(['code' => $fieldNameWithKey, 'value' => $val]); } } } else { if (request()->hasFile($fieldName)) { $value = request()->file($fieldName)->store('configuration'); } $preparedData[] = [ 'code' => $fieldName, 'value' => $value, ]; } } } if (! empty($preparedData)) { foreach ($preparedData as $dataItem) { $coreConfigValues = $this->model->where('code', $dataItem['code'])->get(); if ($coreConfigValues->isNotEmpty()) { foreach ($coreConfigValues as $coreConfig) { parent::update($dataItem, $coreConfig->id); } } else { parent::create($dataItem); } } } Event::dispatch('core.configuration.save.after'); } /** * Recursive array. */ public function recursiveArray(array $formData, string $method): array { static $data = []; static $recursiveArrayData = []; foreach ($formData as $form => $formValue) { $value = $method.'.'.$form; if (is_array($formValue)) { $dim = $this->countDim($formValue); if ($dim > 1) { $this->recursiveArray($formValue, $value); } elseif ($dim == 1) { $data[$value] = $formValue; } } } foreach ($data as $key => $value) { $field = core()->getConfigField($key); if ($field) { $recursiveArrayData[$key] = $value; } else { foreach ($value as $key1 => $val) { $recursiveArrayData[$key.'.'.$key1] = $val; } } } return $recursiveArrayData; } /** * Return dimension of the array. */ public function countDim(array|string $array): int { if (is_array(reset($array))) { $return = $this->countDim(reset($array)) + 1; } else { $return = 1; } return $return; } } ================================================ FILE: packages/Webkul/Core/src/Repositories/CountryRepository.php ================================================ [ 'code' => 'يجب أن يكون الحقل رمزًا صالحًا.', 'decimal' => 'يجب أن يكون الحقل رقمًا عشريًا.', ], ]; ================================================ FILE: packages/Webkul/Core/src/Resources/lang/en/app.php ================================================ [ 'code' => 'The field must be a valid code.', 'decimal' => 'The field must be a decimal number.', ], ]; ================================================ FILE: packages/Webkul/Core/src/Resources/lang/es/app.php ================================================ [ 'code' => 'El campo debe ser un código válido.', 'decimal' => 'El campo debe ser un número decimal.', ], ]; ================================================ FILE: packages/Webkul/Core/src/Resources/lang/fa/app.php ================================================ [ 'code' => 'این فیلد باید یک کد معتبر باشد.', 'decimal' => 'این فیلد باید یک عدد اعشاری باشد.', ], ]; ================================================ FILE: packages/Webkul/Core/src/Resources/lang/pt_BR/app.php ================================================ [ 'code' => 'O campo deve ser um código válido.', 'decimal' => 'O campo deve ser um número decimal.', ], ]; ================================================ FILE: packages/Webkul/Core/src/Resources/lang/tr/app.php ================================================ [ 'code' => 'Alan geçerli bir kod olmalıdır.', 'decimal' => 'Alan ondalık bir sayı olmalıdır.', ], ]; ================================================ FILE: packages/Webkul/Core/src/Resources/lang/vi/app.php ================================================ [ 'code' => 'Trường phải là một mã hợp lệ.', 'decimal' => 'Trường phải là một số thập phân.', ], ]; ================================================ FILE: packages/Webkul/Core/src/SystemConfig/Item.php ================================================ name ?? ''; } /** * Format options. */ private function formatOptions($options) { return is_array($options) ? $options : (is_string($options) ? $options : []); } /** * Get fields of config item. */ public function getFields(): Collection { return collect($this->fields)->map(function ($field) { return new ItemField( item_key: $this->key, name: $field['name'], title: $field['title'], info: $field['info'] ?? null, type: $field['type'], depends: $field['depends'] ?? null, path: $field['path'] ?? null, validation: $field['validation'] ?? null, default: $field['default'] ?? null, channel_based: $field['channel_based'] ?? null, locale_based: $field['locale_based'] ?? null, options: $this->formatOptions($field['options'] ?? null), tinymce: $field['tinymce'] ?? false, is_visible: true, ); }); } /** * Get name of config item. */ public function getInfo(): ?string { return $this->info; } /** * Get current route. */ public function getRoute(): string { return $this->route; } /** * Get the url of the config item. */ public function getUrl(): string { return route($this->getRoute()); } /** * Get the key of the config item. */ public function getKey(): string { return $this->key; } /** * Get Icon. */ public function getIcon(): ?string { return $this->icon; } /** * Check weather config item have children or not. */ public function haveChildren(): bool { return $this->children->isNotEmpty(); } /** * Get children of config item. */ public function getChildren(): Collection { if (! $this->haveChildren()) { return collect(); } return $this->children; } } ================================================ FILE: packages/Webkul/Core/src/SystemConfig/ItemField.php ================================================ 'min_value', ]; /** * Create a new ItemField instance. */ public function __construct( public string $item_key, public string $name, public string $title, public ?string $info, public string $type, public ?string $path, public ?string $validation, public ?string $depends, public ?string $default, public ?bool $channel_based, public ?bool $locale_based, public array|string $options, public bool $is_visible = true, public bool $tinymce = false, ) { $this->options = $this->getOptions(); } /** * Get name of config item. */ public function getName(): ?string { return $this->name; } /** * Get info of config item. */ public function getInfo(): ?string { return $this->info ?? ''; } /** * Get title of config item. */ public function getTitle(): ?string { return $this->title ?? ''; } /** * Determine if the field should use TinyMCE. */ public function getTinymce(): bool { return $this->tinymce; } /** * Get type of config item. */ public function getType(): string { return $this->type; } /** * Get path of config item. */ public function getPath(): ?string { return $this->path; } /** * Get item key of config item. */ public function getItemKey(): string { return $this->item_key; } /** * Get validation of config item. */ public function getValidations(): ?string { if (empty($this->validation)) { return ''; } foreach ($this->veeValidateMappings as $laravelRule => $veeValidateRule) { $this->validation = str_replace($laravelRule, $veeValidateRule, $this->validation); } return $this->validation; } /** * Get depends of config item. */ public function getDepends(): ?string { return $this->depends; } /** * Get default value of config item. */ public function getDefault(): ?string { return $this->default; } /** * Get channel based of config item. */ public function getChannelBased(): ?bool { return $this->channel_based; } /** * Get locale based of config item. */ public function getLocaleBased(): ?bool { return $this->locale_based; } /** * Get name field for forms in configuration page. */ public function getNameKey(): string { return $this->item_key.'.'.$this->name; } /** * Check if the field is required. */ public function isRequired(): string { return Str::contains($this->getValidations(), 'required') ? 'required' : ''; } /** * Get options of config item. */ public function getOptions(): array { if (is_array($this->options)) { return collect($this->options)->map(fn ($option) => [ 'title' => trans($option['title']), 'value' => $option['value'], ])->toArray(); } return collect($this->getFieldOptions($this->options))->map(fn ($option) => [ 'title' => trans($option['title']), 'value' => $option['value'], ])->toArray(); } /** * Convert the field to an array. */ public function toArray() { return [ 'name' => $this->getName(), 'title' => $this->getTitle(), 'info' => $this->getInfo(), 'type' => $this->getType(), 'path' => $this->getPath(), 'depends' => $this->getDepends(), 'validation' => $this->getValidations(), 'default' => $this->getDefault(), 'channel_based' => $this->getChannelBased(), 'locale_based' => $this->getLocaleBased(), 'options' => $this->getOptions(), 'item_key' => $this->getItemKey(), 'tinymce' => $this->getTinymce(), ]; } /** * Get name field for forms in configuration page. * * @param string $key * @return string */ public function getNameField($key = null) { if (! $key) { $key = $this->item_key.'.'.$this->name; } $nameField = ''; foreach (explode('.', $key) as $key => $field) { $nameField .= $key === 0 ? $field : '['.$field.']'; } return $nameField; } /** * Get depend the field name. */ public function getDependFieldName(): string { if (empty($depends = $this->getDepends())) { return ''; } $dependNameKey = $this->getItemKey().'.'.collect(explode(':', $depends))->first(); return $this->getNameField($dependNameKey); } /** * Returns the select options for the field. */ protected function getFieldOptions(string $options): array { [$class, $method] = Str::parseCallback($options); return app($class)->$method(); } } ================================================ FILE: packages/Webkul/Core/src/SystemConfig.php ================================================ items[] = $item; } /** * Get all configuration items. */ public function getItems(): Collection { if (! $this->items) { $this->prepareConfigurationItems(); } return collect($this->items) ->sortBy('sort'); } /** * Retrieve Core Config */ private function retrieveCoreConfig(): array { static $items; if ($items) { return $items; } return $items = config('core_config'); } /** * Prepare configuration items. */ public function prepareConfigurationItems() { $configWithDotNotation = []; foreach ($this->retrieveCoreConfig() as $item) { $configWithDotNotation[$item['key']] = $item; } $configs = Arr::undot(Arr::dot($configWithDotNotation)); foreach ($configs as $configItem) { $subConfigItems = $this->processSubConfigItems($configItem); $this->addItem(new Item( children: $subConfigItems, fields: $configItem['fields'] ?? null, icon: $configItem['icon'] ?? null, key: $configItem['key'], name: trans($configItem['name']), route: $configItem['route'] ?? null, info: trans($configItem['info']) ?? null, sort: $configItem['sort'], )); } } /** * Process sub config items. */ private function processSubConfigItems($configItem): Collection { return collect($configItem) ->sortBy('sort') ->filter(fn ($value) => is_array($value) && isset($value['name'])) ->map(function ($subConfigItem) { $configItemChildren = $this->processSubConfigItems($subConfigItem); return new Item( children: $configItemChildren, fields: $subConfigItem['fields'] ?? null, icon: $subConfigItem['icon'] ?? null, key: $subConfigItem['key'], name: trans($subConfigItem['name']), info: trans($subConfigItem['info']) ?? null, route: $subConfigItem['route'] ?? null, sort: $subConfigItem['sort'] ?? null, ); }); } /** * Get active configuration item. */ public function getActiveConfigurationItem(): ?Item { if (! $slug = request()->route('slug')) { return null; } $activeItem = $this->getItems()->where('key', $slug)->first() ?? null; if (! $activeItem) { return null; } if ($slug2 = request()->route('slug2')) { $activeItem = $activeItem->getChildren()[$slug2]; } return $activeItem; } /** * Get config field. */ public function getConfigField(string $fieldName): ?array { foreach ($this->retrieveCoreConfig() as $coreData) { if (! isset($coreData['fields'])) { continue; } foreach ($coreData['fields'] as $field) { $name = $coreData['key'].'.'.$field['name']; if ($name == $fieldName) { return $field; } } } return null; } /** * Get default config. */ private function getDefaultConfig(string $field): mixed { $configFieldInfo = $this->getConfigField($field); $fields = explode('.', $field); array_shift($fields); $field = implode('.', $fields); return Config::get($field, $configFieldInfo['default'] ?? null); } /** * Retrieve information for configuration */ public function getConfigData(string $field): mixed { $coreConfigValue = $this->coreConfigRepository->findOneWhere([ 'code' => $field, ]); if (! $coreConfigValue) { return $this->getDefaultConfig($field); } return $coreConfigValue->value; } } ================================================ FILE: packages/Webkul/Core/src/Traits/PDFHandler.php ================================================ getLocale(), ['ar', 'he'])) { $mPDF = new Mpdf([ 'margin_left' => 0, 'margin_right' => 0, 'margin_top' => 0, 'margin_bottom' => 0, ]); $mPDF->SetDirectionality($direction); $mPDF->SetDisplayMode('fullpage'); $mPDF->WriteHTML($this->adjustArabicAndPersianContent($html)); return response()->streamDownload(fn () => print ($mPDF->Output('', 'S')), $fileName.'.pdf'); } return Pdf::loadHTML($this->adjustArabicAndPersianContent($html)) ->setPaper('A4', 'portrait') ->set_option('defaultFont', 'Courier') ->download($fileName.'.pdf'); } /** * Adjust arabic and persian content. * * @return string */ protected function adjustArabicAndPersianContent(string $html) { $arabic = new Arabic; $p = $arabic->arIdentify($html); for ($i = count($p) - 1; $i >= 0; $i -= 2) { $utf8ar = $arabic->utf8Glyphs(substr($html, $p[$i - 1], $p[$i] - $p[$i - 1])); $html = substr_replace($html, $utf8ar, $p[$i - 1], $p[$i] - $p[$i - 1]); } return $html; } } ================================================ FILE: packages/Webkul/Core/src/Traits/Sanitizer.php ================================================ isSvgFile($file)) { return; } try { $svgContent = Storage::get($path); if (! $svgContent) { return; } $sanitizer = new MainSanitizer; $sanitizer->setAllowedAttrs(new AllowedAttributes); $sanitizer->setAllowedTags(new AllowedTags); $sanitizer->minify(true); $sanitizer->removeRemoteReferences(true); $sanitizer->removeXMLTag(true); $sanitizer->setXMLOptions(LIBXML_NONET | LIBXML_NOBLANKS); $sanitizedContent = $sanitizer->sanitize($svgContent); if ($sanitizedContent === false) { $patterns = [ '/]*>(.*?)<\/script>/is', '/\bon\w+\s*=\s*["\'][^"\']*["\']/i', '/javascript\s*:/i', '/data\s*:[^,]*base64/i', ]; $sanitizedContent = $svgContent; foreach ($patterns as $pattern) { $sanitizedContent = preg_replace($pattern, '', $sanitizedContent); } Storage::put($path, $sanitizedContent); return; } $sanitizedContent = preg_replace('/(.*?<\/script>)|(\son\w+\s*=\s*["\'][^"\']*["\'])/is', '', $sanitizedContent); Storage::put($path, $sanitizedContent); } catch (Exception $e) { report($e->getMessage()); Storage::delete($path); } } /** * Check if the uploaded file is an SVG based on both extension and mime type. */ public function isSvgFile(UploadedFile $file): bool { return str_contains(strtolower($file->getClientOriginalExtension()), 'svg'); } } ================================================ FILE: packages/Webkul/Core/src/ViewRenderEventManager.php ================================================ params = $params ?? []; Event::dispatch($eventName, $this); return $this->templates; } /** * get params * * @return array */ public function getParams() { return $this->params; } /** * get param * * @return mixed */ public function getParam($name) { return optional($this->params)[$name]; } /** * Add templates for render * * @param string $template * @return void */ public function addTemplate($template) { array_push($this->templates, $template); } /** * Renders templates * * @return string */ public function render() { $string = ''; foreach ($this->templates as $template) { if (view()->exists($template)) { $string .= view($template, $this->params)->render(); } elseif (is_string($template)) { $string .= $template; } } return $string; } } ================================================ FILE: packages/Webkul/Core/src/Vite.php ================================================ useBuildDirectory($viters[$namespace]['build_directory']) ->asset($viteUrl); } /** * Set krayin vite. * * @return mixed */ public function set(mixed $entryPoints, string $namespace = 'admin') { $viters = config('krayin-vite.viters'); if (empty($viters[$namespace])) { throw new ViterNotFound($namespace); } return BaseVite::useHotFile($viters[$namespace]['hot_file']) ->useBuildDirectory($viters[$namespace]['build_directory']) ->withEntryPoints($entryPoints); } } ================================================ FILE: packages/Webkul/DataGrid/src/Action.php ================================================ $this->index, 'icon' => $this->icon, 'title' => $this->title, 'method' => $this->method, 'url' => $this->url, ]; } } ================================================ FILE: packages/Webkul/DataGrid/src/Column.php ================================================ init($column); } /** * Initialize all necessary settings for the columns. */ public function init(array $column): void { $this->setIndex($column['index']); $this->setLabel($column['label']); $this->setType($column['type']); $this->setSearchable($column['searchable'] ?? $this->searchable); $this->setFilterable($column['filterable'] ?? $this->filterable); $this->setFilterableType($column['filterable_type'] ?? $this->filterableType); $this->setFilterableOptions($column['filterable_options'] ?? $this->filterableOptions); $this->setAllowMultipleValues($column['allow_multiple_values'] ?? $this->allowMultipleValues); $this->setSortable($column['sortable'] ?? $this->sortable); $this->setVisibility($column['visibility'] ?? $this->visibility); $this->setClosure($column['closure'] ?? $this->closure); $this->setColumnName($this->index); } /** * Set index. */ public function setIndex(string $index): void { $this->index = $index; } /** * Get index. */ public function getIndex(): string { return $this->index; } /** * Set label. */ public function setLabel(string $label): void { $this->label = $label; } /** * Get label. */ public function getLabel(): string { return $this->label; } /** * Set type. */ public function setType(string $type): void { $this->type = $type; } /** * Get type. */ public function getType(): string { return $this->type; } /** * Set searchable. */ public function setSearchable(bool $searchable): void { $this->searchable = $searchable; } /** * Get searchable. */ public function getSearchable(): bool { return $this->searchable; } /** * Set filterable. */ public function setFilterable(bool $filterable): void { $this->filterable = $filterable; } /** * Get filterable. */ public function getFilterable(): bool { return $this->filterable; } /** * Set filterable type. */ public function setFilterableType(?string $filterableType): void { $this->filterableType = $filterableType; } /** * Get filterable type. */ public function getFilterableType(): ?string { return $this->filterableType; } /** * Set filterable options. */ public function setFilterableOptions(mixed $filterableOptions): void { if ($filterableOptions instanceof \Closure) { $filterableOptions = $filterableOptions(); } $this->filterableOptions = $filterableOptions; } /** * Get filterable options. */ public function getFilterableOptions(): array { return $this->filterableOptions; } /** * Set allow multiple values. */ public function setAllowMultipleValues(bool $allowMultipleValues): void { $this->allowMultipleValues = $allowMultipleValues; } /** * Get allow multiple values. */ public function getAllowMultipleValues(): bool { return $this->allowMultipleValues; } /** * Set sortable. */ public function setSortable(?bool $sortable = null): void { $this->sortable = $sortable; } /** * Get sortable. */ public function getSortable(): bool { return $this->sortable; } /** * Set exportable. */ public function setExportable(bool $exportable): void { $this->exportable = $exportable; } /** * Get exportable. */ public function getExportable(): bool { return $this->exportable; } /** * Set visibility. */ public function setVisibility(bool $visibility): void { $this->visibility = $visibility; } /** * Get visibility. */ public function getVisibility(): bool { return $this->visibility; } /** * Set closure. */ public function setClosure(mixed $closure): void { $this->closure = $closure; } /** * Get closure. */ public function getClosure(): mixed { return $this->closure; } /** * Define the table's column name. Initially, it will match the index. However, after adding an alias, * the column name may change. */ public function setColumnName(mixed $columnName): void { $this->columnName = $columnName; } /** * Get the table's column name. */ public function getColumnName(): mixed { return $this->columnName; } /** * To array. */ public function toArray(): array { return [ 'index' => $this->index, 'label' => $this->label, 'type' => $this->type, 'searchable' => $this->searchable, 'filterable' => $this->filterable, 'filterable_type' => $this->filterableType, 'filterable_options' => $this->filterableOptions, 'allow_multiple_values' => $this->allowMultipleValues, 'sortable' => $this->sortable, 'visibility' => $this->visibility, ]; } /** * Validate the column. */ public static function validate(array $column): void { if (empty($column['index'])) { throw new InvalidColumnException('The `index` key is required. Ensure that the `index` key is present in all calls to the `addColumn` method.'); } if (empty($column['label'])) { throw new InvalidColumnException('The `label` key is required. Ensure that the `label` key is present in all calls to the `addColumn` method.'); } if (empty($column['type'])) { throw new InvalidColumnException('The `type` key is required. Ensure that the `type` key is present in all calls to the `addColumn` method.'); } } /** * Resolve the column type class. */ public static function resolveType(array $column): self { self::validate($column); $columnTypeClass = ColumnTypeEnum::getClassName($column['type']); return new $columnTypeClass($column); } } ================================================ FILE: packages/Webkul/DataGrid/src/ColumnTypes/Aggregate.php ================================================ filterableType === FilterTypeEnum::DROPDOWN->value) { return $queryBuilder->having(function ($scopeQueryBuilder) use ($requestedValues) { if (is_string($requestedValues)) { $scopeQueryBuilder->orHaving($this->columnName, $requestedValues); return; } foreach ($requestedValues as $value) { $scopeQueryBuilder->orHaving($this->columnName, $value); } }); } return $queryBuilder->having(function ($scopeQueryBuilder) use ($requestedValues) { if (is_string($requestedValues)) { $scopeQueryBuilder->orHaving($this->columnName, 'LIKE', '%'.$requestedValues.'%'); return; } foreach ($requestedValues as $value) { $scopeQueryBuilder->orHaving($this->columnName, 'LIKE', '%'.$value.'%'); } }); } } ================================================ FILE: packages/Webkul/DataGrid/src/ColumnTypes/Boolean.php ================================================ value) ) { throw new InvalidColumnException('Boolean filters will only work with `dropdown` type. Either remove the `filterable_type` or set it to `dropdown`.'); } if (! $filterableType) { $filterableType = FilterTypeEnum::DROPDOWN->value; } parent::setFilterableType($filterableType); } /** * Set filterable options. */ public function setFilterableOptions(mixed $filterableOptions): void { if (empty($filterableOptions)) { $filterableOptions = [ [ 'label' => trans('admin::app.components.datagrid.filters.boolean-options.true'), 'value' => 1, ], [ 'label' => trans('admin::app.components.datagrid.filters.boolean-options.false'), 'value' => 0, ], ]; } parent::setFilterableOptions($filterableOptions); } /** * Process filter. */ public function processFilter($queryBuilder, $requestedValues): mixed { return $queryBuilder->where(function ($scopeQueryBuilder) use ($requestedValues) { if (is_string($requestedValues)) { $scopeQueryBuilder->orWhere($this->columnName, $requestedValues); return; } foreach ($requestedValues as $value) { $scopeQueryBuilder->orWhere($this->columnName, $value); } }); } } ================================================ FILE: packages/Webkul/DataGrid/src/ColumnTypes/Date.php ================================================ value) ) { throw new InvalidColumnException('Date filters will only work with `date_range` type. Either remove the `filterable_type` or set it to `date_range`.'); } parent::setFilterableType($filterableType); } /** * Set filterable options. */ public function setFilterableOptions(mixed $filterableOptions): void { if (empty($filterableOptions)) { $filterableOptions = DateRangeOptionEnum::options(); } parent::setFilterableOptions($filterableOptions); } /** * Process filter. */ public function processFilter($queryBuilder, $requestedDates) { return $queryBuilder->where(function ($scopeQueryBuilder) use ($requestedDates) { if (is_string($requestedDates)) { $rangeOption = collect($this->filterableOptions)->firstWhere('name', $requestedDates); $requestedDates = ! $rangeOption ? [[$requestedDates, $requestedDates]] : [[$rangeOption['from'], $rangeOption['to']]]; } foreach ($requestedDates as $value) { $scopeQueryBuilder->whereBetween($this->columnName, [ ($value[0] ?? '').' 00:00:01', ($value[1] ?? '').' 23:59:59', ]); } }); } } ================================================ FILE: packages/Webkul/DataGrid/src/ColumnTypes/Datetime.php ================================================ value) ) { throw new InvalidColumnException('Datetime filters will only work with `datetime_range` type. Either remove the `filterable_type` or set it to `datetime_range`.'); } parent::setFilterableType($filterableType); } /** * Set filterable options. */ public function setFilterableOptions(mixed $filterableOptions): void { if (empty($filterableOptions)) { $filterableOptions = DateRangeOptionEnum::options(); } parent::setFilterableOptions($filterableOptions); } /** * Process filter. */ public function processFilter($queryBuilder, $requestedDates) { return $queryBuilder->where(function ($scopeQueryBuilder) use ($requestedDates) { if (is_string($requestedDates)) { $rangeOption = collect($this->filterableOptions)->firstWhere('name', $requestedDates); $requestedDates = ! $rangeOption ? [[$requestedDates, $requestedDates]] : [[$rangeOption['from'], $rangeOption['to']]]; } foreach ($requestedDates as $value) { $scopeQueryBuilder->whereBetween($this->columnName, [$value[0] ?? '', $value[1] ?? '']); } }); } } ================================================ FILE: packages/Webkul/DataGrid/src/ColumnTypes/Decimal.php ================================================ where(function ($scopeQueryBuilder) use ($requestedValues) { if (is_string($requestedValues)) { $scopeQueryBuilder->orWhere($this->columnName, $requestedValues); return; } foreach ($requestedValues as $value) { $scopeQueryBuilder->orWhere($this->columnName, $value); } }); } } ================================================ FILE: packages/Webkul/DataGrid/src/ColumnTypes/Integer.php ================================================ where(function ($scopeQueryBuilder) use ($requestedValues) { if (is_string($requestedValues)) { $scopeQueryBuilder->orWhere($this->columnName, $requestedValues); return; } foreach ($requestedValues as $value) { $scopeQueryBuilder->orWhere($this->columnName, $value); } }); } } ================================================ FILE: packages/Webkul/DataGrid/src/ColumnTypes/Text.php ================================================ filterableType === FilterTypeEnum::DROPDOWN->value) { return $queryBuilder->where(function ($scopeQueryBuilder) use ($requestedValues) { if (is_string($requestedValues)) { $scopeQueryBuilder->orWhere($this->columnName, $requestedValues); return; } foreach ($requestedValues as $value) { $scopeQueryBuilder->orWhere($this->columnName, $value); } }); } return $queryBuilder->where(function ($scopeQueryBuilder) use ($requestedValues) { if (is_string($requestedValues)) { $scopeQueryBuilder->orWhere($this->columnName, 'LIKE', '%'.$requestedValues.'%'); return; } foreach ($requestedValues as $value) { $scopeQueryBuilder->orWhere($this->columnName, 'LIKE', '%'.$value.'%'); } }); } } ================================================ FILE: packages/Webkul/DataGrid/src/Contracts/SavedFilter.php ================================================ columns; } /** * Get actions. */ public function getActions(): array { return $this->actions; } /** * Get mass actions. */ public function getMassActions(): array { return $this->massActions; } /** * Add column. */ public function addColumn(array $column): void { $this->dispatchEvent('columns.add.before', [$this, $column]); $this->columns[] = Column::resolveType($column); $this->dispatchEvent('columns.add.after', [$this, $this->columns[count($this->columns) - 1]]); } /** * Add action. */ public function addAction(array $action): void { $this->dispatchEvent('actions.add.before', [$this, $action]); $this->actions[] = new Action( index: $action['index'] ?? '', icon: $action['icon'] ?? '', title: $action['title'], method: $action['method'], url: $action['url'], ); $this->dispatchEvent('actions.add.after', [$this, $this->actions[count($this->actions) - 1]]); } /** * Add mass action. */ public function addMassAction(array $massAction): void { $this->dispatchEvent('mass_actions.add.before', [$this, $massAction]); $this->massActions[] = new MassAction( icon: $massAction['icon'] ?? '', title: $massAction['title'], method: $massAction['method'], url: $massAction['url'], options: $massAction['options'] ?? [], ); $this->dispatchEvent('mass_actions.add.after', [$this, $this->massActions[count($this->massActions) - 1]]); } /** * Set query builder. * * @param mixed $queryBuilder */ public function setQueryBuilder($queryBuilder = null): void { $this->dispatchEvent('query_builder.set.before', [$this, $queryBuilder]); $this->queryBuilder = $queryBuilder ?: $this->prepareQueryBuilder(); $this->dispatchEvent('query_builder.set.after', $this); } /** * Get query builder. */ public function getQueryBuilder(): mixed { return $this->queryBuilder; } /** * Map your filter. */ public function addFilter(string $datagridColumn, mixed $queryColumn): void { $this->dispatchEvent('filters.add.before', [$this, $datagridColumn, $queryColumn]); foreach ($this->columns as $column) { if ($column->getIndex() === $datagridColumn) { $column->setColumnName($queryColumn); break; } } $this->dispatchEvent('filters.add.after', [$this, $datagridColumn, $queryColumn]); } /** * Set exportable. */ public function setExportable(bool $exportable): void { $this->dispatchEvent('exportable.set.before', [$this, $exportable]); $this->exportable = $exportable; $this->dispatchEvent('exportable.set.after', $this); } /** * Get exportable. */ public function getExportable(): bool { return $this->exportable; } /** * Set export file. * * @param string $format * @return void */ public function setExportFile($format = 'csv') { $this->dispatchEvent('export_file.set.before', [$this, $format]); $this->setExportable(true); $this->exportFile = Excel::download(new DataGridExport($this), Str::random(36).'.'.$format); $this->dispatchEvent('export_file.set.after', $this); } /** * Download export file. * * @return BinaryFileResponse */ public function downloadExportFile() { return $this->exportFile; } /** * Process the datagrid. * * @return BinaryFileResponse|JsonResponse */ public function process() { $this->prepare(); if ($this->getExportable()) { return $this->downloadExportFile(); } return response()->json($this->formatData()); } /** * To json. The reason for deprecation is that it is not an action returning JSON; instead, * it is a process method which returns a download as well as a JSON response. * * @deprecated * * @return BinaryFileResponse|JsonResponse */ public function toJson() { $this->prepare(); if ($this->getExportable()) { return $this->downloadExportFile(); } return response()->json($this->formatData()); } /** * Validated request. */ protected function validatedRequest(): array { request()->validate([ 'filters' => ['sometimes', 'required', 'array'], 'sort' => ['sometimes', 'required', 'array'], 'pagination' => ['sometimes', 'required', 'array'], 'export' => ['sometimes', 'required', 'boolean'], 'format' => ['sometimes', 'required', 'in:csv,xls,xlsx'], ]); return request()->only(['filters', 'sort', 'pagination', 'export', 'format']); } /** * Process all requested filters. * * @return Builder */ protected function processRequestedFilters(array $requestedFilters) { foreach ($requestedFilters as $requestedColumn => $requestedValues) { if ($requestedColumn === 'all') { $this->queryBuilder->where(function ($scopeQueryBuilder) use ($requestedValues) { foreach ($requestedValues as $value) { collect($this->columns) ->filter(fn ($column) => $column->getSearchable() && ! in_array($column->getType(), [ ColumnTypeEnum::BOOLEAN->value, ColumnTypeEnum::AGGREGATE->value, ])) ->each(fn ($column) => $scopeQueryBuilder->orWhere($column->getColumnName(), 'LIKE', '%'.$value.'%')); } }); } else { collect($this->columns) ->first(fn ($column) => $column->getIndex() === $requestedColumn) ->processFilter($this->queryBuilder, $requestedValues); } } return $this->queryBuilder; } /** * Process requested sorting. * * @return Builder */ protected function processRequestedSorting($requestedSort) { if (! $this->sortColumn) { $this->sortColumn = $this->primaryColumn; } return $this->queryBuilder->orderBy($requestedSort['column'] ?? $this->sortColumn, $requestedSort['order'] ?? $this->sortOrder); } /** * Process requested pagination. */ protected function processRequestedPagination($requestedPagination): LengthAwarePaginator { return $this->queryBuilder->paginate( $requestedPagination['per_page'] ?? $this->itemsPerPage, ['*'], 'page', $requestedPagination['page'] ?? 1 ); } /** * Process paginated request. */ protected function processPaginatedRequest(array $requestedParams): void { $this->dispatchEvent('process_request.paginated.before', $this); $this->paginator = $this->processRequestedPagination($requestedParams['pagination'] ?? []); $this->dispatchEvent('process_request.paginated.after', $this); } /** * Process export request. */ protected function processExportRequest(array $requestedParams): void { $this->dispatchEvent('process_request.export.before', $this); $this->setExportFile($requestedParams['format']); $this->dispatchEvent('process_request.export.after', $this); } /** * Process request. */ protected function processRequest(): void { $this->dispatchEvent('process_request.before', $this); /** * Store all request parameters in this variable; avoid using direct request helpers afterward. */ $requestedParams = $this->validatedRequest(); $this->queryBuilder = $this->processRequestedFilters($requestedParams['filters'] ?? []); $this->queryBuilder = $this->processRequestedSorting($requestedParams['sort'] ?? []); /** * The `export` parameter is validated as a boolean in the `validatedRequest`. An `empty` function will not work, * as it will always be treated as true because of "0" and "1". */ isset($requestedParams['export']) && (bool) $requestedParams['export'] ? $this->processExportRequest($requestedParams) : $this->processPaginatedRequest($requestedParams); $this->dispatchEvent('process_request.after', $this); } /** * Prepare all the setup for datagrid. */ protected function sanitizeRow($row): \stdClass { /** * Convert stdClass to array. */ $tempRow = json_decode(json_encode($row), true); foreach ($tempRow as $column => $value) { if (! is_string($tempRow[$column])) { continue; } if (is_array($value)) { return $this->sanitizeRow($tempRow[$column]); } else { $row->{$column} = strip_tags($value); } } return $row; } /** * Format columns. */ protected function formatColumns(): array { return collect($this->columns) ->map(fn ($column) => $column->toArray()) ->toArray(); } /** * Format actions. */ protected function formatActions(): array { return collect($this->actions) ->map(fn ($action) => $action->toArray()) ->toArray(); } /** * Format mass actions. */ protected function formatMassActions(): array { return collect($this->massActions) ->map(fn ($massAction) => $massAction->toArray()) ->toArray(); } /** * Format records. */ protected function formatRecords($records): mixed { foreach ($records as $record) { $record = $this->sanitizeRow($record); foreach ($this->columns as $column) { if ($closure = $column->getClosure()) { $record->{$column->getIndex()} = $closure($record); } } $record->actions = []; foreach ($this->actions as $index => $action) { $getUrl = $action->url; $record->actions[] = [ 'index' => ! empty($action->index) ? $action->index : 'action_'.$index + 1, 'icon' => $action->icon, 'title' => $action->title, 'method' => $action->method, 'url' => $getUrl($record), ]; } } return $records; } /** * Format data. */ protected function formatData(): array { $paginator = $this->paginator->toArray(); return [ 'id' => Crypt::encryptString(get_called_class()), 'columns' => $this->formatColumns(), 'actions' => $this->formatActions(), 'mass_actions' => $this->formatMassActions(), 'records' => $this->formatRecords($paginator['data']), 'meta' => [ 'primary_column' => $this->primaryColumn, 'from' => $paginator['from'], 'to' => $paginator['to'], 'total' => $paginator['total'], 'per_page_options' => $this->perPageOptions, 'per_page' => $paginator['per_page'], 'current_page' => $paginator['current_page'], 'last_page' => $paginator['last_page'], ], ]; } /** * Dispatch event. */ protected function dispatchEvent(string $eventName, mixed $payload): void { $reflection = new \ReflectionClass($this); $datagridName = Str::snake($reflection->getShortName()); Event::dispatch("datagrid.{$datagridName}.{$eventName}", $payload); } /** * Prepare all the setup for datagrid. */ protected function prepare(): void { $this->dispatchEvent('prepare.before', $this); $this->prepareColumns(); $this->dispatchEvent('columns.prepare.after', $this); $this->prepareActions(); $this->dispatchEvent('actions.prepare.after', $this); $this->prepareMassActions(); $this->dispatchEvent('mass_actions.prepare.after', $this); $this->setQueryBuilder(); $this->dispatchEvent('query_builder.prepare.after', $this); $this->processRequest(); $this->dispatchEvent('prepare.after', $this); } } ================================================ FILE: packages/Webkul/DataGrid/src/Database/Migrations/2024_05_10_152848_create_saved_filters_table.php ================================================ id(); $table->integer('user_id')->unsigned(); $table->string('name'); $table->string('src'); $table->json('applied'); $table->timestamps(); $table->unique(['user_id', 'name', 'src']); }); } /** * Reverse the migrations. */ public function down(): void { Schema::dropIfExists('datagrid_saved_filters'); } }; ================================================ FILE: packages/Webkul/DataGrid/src/Enums/ColumnTypeEnum.php ================================================ value => Text::class, self::INTEGER->value => Integer::class, self::FLOAT->value => Decimal::class, self::BOOLEAN->value => Boolean::class, self::DATE->value => Date::class, self::DATETIME->value => Datetime::class, self::AGGREGATE->value => Aggregate::class, default => throw new InvalidColumnTypeException("Invalid column type: {$type}"), }; } } ================================================ FILE: packages/Webkul/DataGrid/src/Enums/DateRangeOptionEnum.php ================================================ self::TODAY->value, 'label' => trans('admin::app.components.datagrid.filters.date-options.today'), 'from' => now()->today()->format($format), 'to' => now()->endOfDay()->format($format), ], [ 'name' => self::YESTERDAY->value, 'label' => trans('admin::app.components.datagrid.filters.date-options.yesterday'), 'from' => now()->yesterday()->format($format), 'to' => now()->today()->subSecond(1)->format($format), ], [ 'name' => self::THIS_WEEK->value, 'label' => trans('admin::app.components.datagrid.filters.date-options.this-week'), 'from' => now()->startOfWeek()->format($format), 'to' => now()->endOfWeek()->format($format), ], [ 'name' => self::THIS_MONTH->value, 'label' => trans('admin::app.components.datagrid.filters.date-options.this-month'), 'from' => now()->startOfMonth()->format($format), 'to' => now()->endOfMonth()->format($format), ], [ 'name' => self::LAST_MONTH->value, 'label' => trans('admin::app.components.datagrid.filters.date-options.last-month'), 'from' => now()->subMonth(1)->startOfMonth()->format($format), 'to' => now()->subMonth(1)->endOfMonth()->format($format), ], [ 'name' => self::LAST_THREE_MONTHS->value, 'label' => trans('admin::app.components.datagrid.filters.date-options.last-three-months'), 'from' => now()->subMonth(3)->startOfMonth()->format($format), 'to' => now()->subMonth(1)->endOfMonth()->format($format), ], [ 'name' => self::LAST_SIX_MONTHS->value, 'label' => trans('admin::app.components.datagrid.filters.date-options.last-six-months'), 'from' => now()->subMonth(6)->startOfMonth()->format($format), 'to' => now()->subMonth(1)->endOfMonth()->format($format), ], [ 'name' => self::THIS_YEAR->value, 'label' => trans('admin::app.components.datagrid.filters.date-options.this-year'), 'from' => now()->startOfYear()->format($format), 'to' => now()->endOfYear()->format($format), ], ]; } } ================================================ FILE: packages/Webkul/DataGrid/src/Enums/FilterTypeEnum.php ================================================ datagrid->getQueryBuilder(); } /** * Headings. */ public function headings(): array { return collect($this->datagrid->getColumns()) ->filter(fn ($column) => $column->getExportable()) ->map(fn ($column) => $column->getLabel()) ->toArray(); } /** * Map each row for export. */ public function map(mixed $record): array { return collect($this->datagrid->getColumns()) ->filter(fn ($column) => $column->getExportable()) ->map(function ($column) use ($record) { $index = $column->getIndex(); $value = $record->{$index}; if ( in_array($index, ['emails', 'contact_numbers']) && is_string($value) ) { return $this->extractValuesFromJson($value); } return $value; }) ->toArray(); } /** * Extract 'value' fields from a JSON string. */ protected function extractValuesFromJson(string $json): string { $items = json_decode($json, true); if ( json_last_error() === JSON_ERROR_NONE && is_array($items) ) { return collect($items)->map(fn ($item) => "{$item['value']} ({$item['label']})")->implode(', '); } return $json; } } ================================================ FILE: packages/Webkul/DataGrid/src/Http/helpers.php ================================================ $this->icon, 'title' => $this->title, 'method' => $this->method, 'url' => $this->url, 'options' => $this->options, ]; } } ================================================ FILE: packages/Webkul/DataGrid/src/Models/SavedFilter.php ================================================ 'json', ]; } ================================================ FILE: packages/Webkul/DataGrid/src/Models/SavedFilterProxy.php ================================================ loadMigrationsFrom(__DIR__.'/../Database/Migrations'); } /** * Register any application services. */ public function register(): void {} } ================================================ FILE: packages/Webkul/DataGrid/src/Providers/ModuleServiceProvider.php ================================================ [ 'title' => 'data_transfer::app.importers.persons.title', 'importer' => 'Webkul\DataTransfer\Helpers\Importers\Persons\Importer', 'sample_path' => 'data-transfer/samples/persons.csv', ], 'products' => [ 'title' => 'data_transfer::app.importers.products.title', 'importer' => 'Webkul\DataTransfer\Helpers\Importers\Products\Importer', 'sample_path' => 'data-transfer/samples/products.csv', ], 'leads' => [ 'title' => 'data_transfer::app.importers.leads.title', 'importer' => 'Webkul\DataTransfer\Helpers\Importers\Leads\Importer', 'sample_path' => 'data-transfer/samples/leads.csv', ], ]; ================================================ FILE: packages/Webkul/DataTransfer/src/Contracts/Import.php ================================================ increments('id'); $table->string('state')->default('pending'); $table->boolean('process_in_queue')->default(1); $table->string('type'); $table->string('action'); $table->string('validation_strategy'); $table->integer('allowed_errors')->default(0); $table->integer('processed_rows_count')->default(0); $table->integer('invalid_rows_count')->default(0); $table->integer('errors_count')->default(0); $table->json('errors')->nullable(); $table->string('field_separator'); $table->string('file_path'); $table->string('error_file_path')->nullable(); $table->json('summary')->nullable(); $table->datetime('started_at')->nullable(); $table->datetime('completed_at')->nullable(); $table->timestamps(); }); } /** * Reverse the migrations. */ public function down(): void { Schema::dropIfExists('imports'); } }; ================================================ FILE: packages/Webkul/DataTransfer/src/Database/Migrations/2024_01_11_154741_create_import_batches_table.php ================================================ increments('id'); $table->string('state')->default('pending'); $table->json('data'); $table->json('summary')->nullable(); $table->integer('import_id')->unsigned(); $table->foreign('import_id')->references('id')->on('imports')->onDelete('cascade'); }); } /** * Reverse the migrations. */ public function down(): void { Schema::dropIfExists('import_batches'); } }; ================================================ FILE: packages/Webkul/DataTransfer/src/Helpers/Error.php ================================================ messageTemplate[$code] = $template; return $this; } /** * Add error message. */ public function addError(string $code, ?int $rowNumber = null, ?string $columnName = null, ?string $message = null): self { if ($this->isErrorAlreadyAdded($rowNumber, $code, $columnName)) { return $this; } $this->addRowToInvalid($rowNumber); $message = $this->getErrorMessage($code, $message, $columnName); $this->items[$rowNumber][] = [ 'code' => $code, 'column' => $columnName, 'message' => $message, ]; $this->errorsCount++; return $this; } /** * Check if error is already added for the row, code and column. */ public function isErrorAlreadyAdded(?int $rowNumber, string $code, ?string $columnName): bool { return collect($this->items[$rowNumber] ?? []) ->where('code', $code) ->where('column', $columnName) ->isNotEmpty(); } /** * Add specific row to invalid list via row number. */ protected function addRowToInvalid(?int $rowNumber): self { if (is_null($rowNumber)) { return $this; } if (! in_array($rowNumber, $this->invalidRows)) { $this->invalidRows[] = $rowNumber; } return $this; } /** * Add specific row to invalid list via row number. */ public function addRowToSkip(?int $rowNumber): self { if (is_null($rowNumber)) { return $this; } if (! in_array($rowNumber, $this->skippedRows)) { $this->skippedRows[] = $rowNumber; } return $this; } /** * Check if row is invalid by row number. */ public function isRowInvalid(int $rowNumber): bool { return in_array($rowNumber, array_merge($this->invalidRows, $this->skippedRows)); } /** * Build an error message via code, message and column name. */ protected function getErrorMessage(?string $code, ?string $message, ?string $columnName): string { if ( empty($message) && isset($this->messageTemplate[$code]) ) { $message = (string) $this->messageTemplate[$code]; } if ( $columnName && $message ) { $message = sprintf($message, $columnName); } if (! $message) { $message = $code; } return $message; } /** * Get number of invalid rows. */ public function getInvalidRowsCount(): int { return count($this->invalidRows); } /** * Get current error count. */ public function getErrorsCount(): int { return $this->errorsCount; } /** * Get all errors from an import process. */ public function getAllErrors(): array { return $this->items; } /** * Return all errors grouped by code. */ public function getAllErrorsGroupedByCode(): array { $errors = []; foreach ($this->items as $rowNumber => $rowErrors) { foreach ($rowErrors as $error) { if ($rowNumber === '') { $errors[$error['code']][$error['message']] = null; } else { $errors[$error['code']][$error['message']][] = $rowNumber; } } } return $errors; } } ================================================ FILE: packages/Webkul/DataTransfer/src/Helpers/Import.php ================================================ import = $import; return $this; } /** * Returns import instance. */ public function getImport(): ImportContract { return $this->import; } /** * Returns error helper instance. * * @return Error */ public function getErrorHelper() { return $this->errorHelper; } /** * Returns source helper instance. */ public function getSource(): AbstractSource { if (Str::contains($this->import->file_path, '.csv')) { $source = new CSVSource( $this->import->file_path, $this->import->field_separator, ); } else { $source = new ExcelSource( $this->import->file_path, $this->import->field_separator, ); } return $source; } /** * Validates import and returns validation result. */ public function validate(): bool { try { $source = $this->getSource(); $typeImporter = $this->getTypeImporter()->setSource($source); $typeImporter->validateData(); } catch (\Exception $e) { $this->errorHelper->addError( AbstractImporter::ERROR_CODE_SYSTEM_EXCEPTION, null, null, $e->getMessage() ); } $import = $this->importRepository->update([ 'state' => self::STATE_VALIDATED, 'processed_rows_count' => $this->getProcessedRowsCount(), 'invalid_rows_count' => $this->errorHelper->getInvalidRowsCount(), 'errors_count' => $this->errorHelper->getErrorsCount(), 'errors' => $this->getFormattedErrors(), 'error_file_path' => $this->uploadErrorReport(), ], $this->import->id); $this->setImport($import); return $this->isValid(); } /** * Starts import process. */ public function isValid(): bool { if ($this->isErrorLimitExceeded()) { return false; } if ($this->import->processed_rows_count <= $this->import->invalid_rows_count) { return false; } return true; } /** * Check if error limit has been exceeded. */ public function isErrorLimitExceeded(): bool { if ( $this->import->validation_strategy == self::VALIDATION_STRATEGY_STOP_ON_ERROR && $this->import->errors_count > $this->import->allowed_errors ) { return true; } return false; } /** * Starts import process. */ public function start(?ImportBatchContract $importBatch = null): bool { DB::beginTransaction(); try { $typeImporter = $this->getTypeImporter(); $typeImporter->importData($importBatch); } catch (\Exception $e) { /** * Rollback transaction. */ DB::rollBack(); throw $e; } finally { /** * Commit transaction. */ DB::commit(); } return true; } /** * Link import resources. */ public function link(ImportBatchContract $importBatch): bool { DB::beginTransaction(); try { $typeImporter = $this->getTypeImporter(); $typeImporter->linkData($importBatch); } catch (\Exception $e) { /** * Rollback transaction. */ DB::rollBack(); throw $e; } finally { /** * Commit transaction. */ DB::commit(); } return true; } /** * Index import resources. */ public function index(ImportBatchContract $importBatch): bool { DB::beginTransaction(); try { $typeImporter = $this->getTypeImporter(); $typeImporter->indexData($importBatch); } catch (\Exception $e) { /** * Rollback transaction. */ DB::rollBack(); throw $e; } finally { /** * Commit transaction. */ DB::commit(); } return true; } /** * Started the import process. */ public function started(): void { $import = $this->importRepository->update([ 'state' => self::STATE_PROCESSING, 'started_at' => now(), 'summary' => [], ], $this->import->id); $this->setImport($import); Event::dispatch('data_transfer.imports.started', $import); } /** * Started the import linking process. */ public function linking(): void { $import = $this->importRepository->update([ 'state' => self::STATE_LINKING, ], $this->import->id); $this->setImport($import); Event::dispatch('data_transfer.imports.linking', $import); } /** * Started the import indexing process. */ public function indexing(): void { $import = $this->importRepository->update([ 'state' => self::STATE_INDEXING, ], $this->import->id); $this->setImport($import); Event::dispatch('data_transfer.imports.indexing', $import); } /** * Start the import process. */ public function completed(): void { $summary = $this->importBatchRepository ->select( DB::raw('SUM(json_unquote(json_extract(summary, \'$."created"\'))) AS created'), DB::raw('SUM(json_unquote(json_extract(summary, \'$."updated"\'))) AS updated'), DB::raw('SUM(json_unquote(json_extract(summary, \'$."deleted"\'))) AS deleted'), ) ->where('import_id', $this->import->id) ->groupBy('import_id') ->first() ->toArray(); $import = $this->importRepository->update([ 'state' => self::STATE_COMPLETED, 'summary' => $summary, 'completed_at' => now(), ], $this->import->id); $this->setImport($import); Event::dispatch('data_transfer.imports.completed', $import); } /** * Returns import stats. */ public function stats(string $state): array { $total = $this->import->batches->count(); $completed = $this->import->batches->where('state', $state)->count(); $progress = $total ? round($completed / $total * 100) : 0; $summary = $this->importBatchRepository ->select( DB::raw('SUM(json_unquote(json_extract(summary, \'$."created"\'))) AS created'), DB::raw('SUM(json_unquote(json_extract(summary, \'$."updated"\'))) AS updated'), DB::raw('SUM(json_unquote(json_extract(summary, \'$."deleted"\'))) AS deleted'), ) ->where('import_id', $this->import->id) ->where('state', $state) ->groupBy('import_id') ->first() ?->toArray(); return [ 'batches' => [ 'total' => $total, 'completed' => $completed, 'remaining' => $total - $completed, ], 'progress' => $progress, 'summary' => $summary ?? [ 'created' => 0, 'updated' => 0, 'deleted' => 0, ], ]; } /** * Return all error grouped by error code. */ public function getFormattedErrors(): array { $errors = []; foreach ($this->errorHelper->getAllErrorsGroupedByCode() as $groupedErrors) { foreach ($groupedErrors as $errorMessage => $rowNumbers) { if (! empty($rowNumbers)) { $errors[] = 'Row(s) '.implode(', ', $rowNumbers).': '.$errorMessage; } else { $errors[] = $errorMessage; } } } return $errors; } /** * Uploads error report and save the path to the database. */ public function uploadErrorReport(): ?string { /** * Return null if there are no errors. */ if (! $this->errorHelper->getErrorsCount()) { return null; } /** * Return null if there are no invalid rows. */ if (! $this->errorHelper->getInvalidRowsCount()) { return null; } $errors = $this->errorHelper->getAllErrors(); $source = $this->getTypeImporter()->getSource(); $source->rewind(); $spreadsheet = new Spreadsheet; $sheet = $spreadsheet->getActiveSheet(); /** * Add headers with extra error column. */ $sheet->fromArray( [array_merge($source->getColumnNames(), [ 'error', ])], null, 'A1' ); $rowNumber = 2; while ($source->valid()) { try { $rowData = $source->current(); } catch (\InvalidArgumentException $e) { $source->next(); continue; } $rowErrors = $errors[$source->getCurrentRowNumber()] ?? []; if (! empty($rowErrors)) { $rowErrors = Arr::pluck($rowErrors, 'message'); } $rowData[] = implode('|', $rowErrors); $sheet->fromArray([$rowData], null, 'A'.$rowNumber++); $source->next(); } $fileType = pathinfo($this->import->file_path, PATHINFO_EXTENSION); switch ($fileType) { case 'csv': $writer = new Csv($spreadsheet); $writer->setDelimiter($this->import->field_separator); break; case 'xls': $writer = new Xls($spreadsheet); case 'xlsx': $writer = new Xlsx($spreadsheet); break; default: throw new \InvalidArgumentException("Unsupported file type: $fileType"); } $errorFilePath = 'imports/'.time().'-error-report.'.$fileType; $writer->save(Storage::disk('public')->path($errorFilePath)); return $errorFilePath; } /** * Validates source file and returns validation result. */ public function getTypeImporter(): AbstractImporter { if (! $this->typeImporter) { $importerConfig = config('importers.'.$this->import->type); $this->typeImporter = app()->make($importerConfig['importer']) ->setImport($this->import) ->setErrorHelper($this->errorHelper); } return $this->typeImporter; } /** * Returns number of checked rows. */ public function getProcessedRowsCount(): int { return $this->getTypeImporter()->getProcessedRowsCount(); } /** * Is linking resource required for the import operation. */ public function isLinkingRequired(): bool { return $this->getTypeImporter()->isLinkingRequired(); } /** * Is indexing resource required for the import operation. */ public function isIndexingRequired(): bool { return $this->getTypeImporter()->isIndexingRequired(); } } ================================================ FILE: packages/Webkul/DataTransfer/src/Helpers/Importers/AbstractImporter.php ================================================ 'data_transfer::app.validation.errors.system', self::ERROR_CODE_COLUMN_NOT_FOUND => 'data_transfer::app.validation.errors.column-not-found', self::ERROR_CODE_COLUMN_EMPTY_HEADER => 'data_transfer::app.validation.errors.column-empty-headers', self::ERROR_CODE_COLUMN_NAME_INVALID => 'data_transfer::app.validation.errors.column-name-invalid', self::ERROR_CODE_INVALID_ATTRIBUTE => 'data_transfer::app.validation.errors.invalid-attribute', self::ERROR_CODE_WRONG_QUOTES => 'data_transfer::app.validation.errors.wrong-quotes', self::ERROR_CODE_COLUMNS_NUMBER => 'data_transfer::app.validation.errors.column-numbers', ]; public const BATCH_SIZE = 100; /** * Is linking required. */ protected bool $linkingRequired = false; /** * Is indexing required. */ protected bool $indexingRequired = false; /** * Error helper instance. * * @var Error */ protected $errorHelper; /** * Import instance. */ protected ImportContract $import; /** * Source instance. * * @var Source */ protected $source; /** * Valid column names. */ protected array $validColumnNames = []; /** * Array of numbers of validated rows as keys and boolean TRUE as values. */ protected array $validatedRows = []; /** * Number of rows processed by validation. */ protected int $processedRowsCount = 0; /** * Number of created items. */ protected int $createdItemsCount = 0; /** * Number of updated items. */ protected int $updatedItemsCount = 0; /** * Number of deleted items. */ protected int $deletedItemsCount = 0; /** * Create a new helper instance. * * @return void */ public function __construct( protected ImportBatchRepository $importBatchRepository, protected AttributeRepository $attributeRepository, protected AttributeValueRepository $attributeValueRepository ) {} /** * Validate data row. */ abstract public function validateRow(array $rowData, int $rowNumber): bool; /** * Import data rows. */ abstract public function importBatch(ImportBatchContract $importBatchContract): bool; /** * Initialize Product error messages. */ protected function initErrorMessages(): void { foreach ($this->errorMessages as $errorCode => $message) { $this->errorHelper->addErrorMessage($errorCode, trans($message)); } } /** * Import instance. */ public function setImport(ImportContract $import): self { $this->import = $import; return $this; } /** * Import instance. * * @param Source $errorHelper */ public function setSource($source) { $this->source = $source; return $this; } /** * Import instance. * * @param Error $errorHelper */ public function setErrorHelper($errorHelper): self { $this->errorHelper = $errorHelper; $this->initErrorMessages(); return $this; } /** * Import instance. * * @return Source */ public function getSource() { return $this->source; } /** * Retrieve valid column names. */ public function getValidColumnNames(): array { return $this->validColumnNames; } /** * Validate data. */ public function validateData(): void { Event::dispatch('data_transfer.imports.validate.before', $this->import); $errors = []; $absentColumns = array_diff($this->permanentAttributes, $columns = $this->getSource()->getColumnNames()); if (! empty($absentColumns)) { $errors[self::ERROR_CODE_COLUMN_NOT_FOUND] = $absentColumns; } foreach ($columns as $columnNumber => $columnName) { if (empty($columnName)) { $errors[self::ERROR_CODE_COLUMN_EMPTY_HEADER][] = $columnNumber + 1; } elseif (! preg_match('/^[a-z][a-z0-9_]*$/', $columnName)) { $errors[self::ERROR_CODE_COLUMN_NAME_INVALID][] = $columnName; } elseif (! in_array($columnName, $this->getValidColumnNames())) { $errors[self::ERROR_CODE_INVALID_ATTRIBUTE][] = $columnName; } } /** * Add Columns Errors. */ foreach ($errors as $errorCode => $error) { $this->addErrors($errorCode, $error); } if (! $this->errorHelper->getErrorsCount()) { $this->saveValidatedBatches(); } Event::dispatch('data_transfer.imports.validate.after', $this->import); } /** * Save validated batches. */ protected function saveValidatedBatches(): self { $source = $this->getSource(); $batchRows = []; $source->rewind(); /** * Clean previous saved batches. */ $this->importBatchRepository->deleteWhere([ 'import_id' => $this->import->id, ]); while ( $source->valid() || count($batchRows) ) { if ( count($batchRows) == self::BATCH_SIZE || ! $source->valid() ) { $this->importBatchRepository->create([ 'import_id' => $this->import->id, 'data' => $batchRows, ]); $batchRows = []; } if ($source->valid()) { $rowData = $source->current(); if ($this->validateRow($rowData, $source->getCurrentRowNumber())) { $batchRows[] = $this->prepareRowForDb($rowData); } $this->processedRowsCount++; $source->next(); } } return $this; } /** * Prepare validation rules. */ public function getValidationRules(string $entityType, array $rowData): array { if (empty($entityType)) { return []; } $rules = []; $attributes = $this->attributeRepository->scopeQuery(fn ($query) => $query->whereIn('code', array_keys($rowData))->where('entity_type', $entityType))->get(); foreach ($attributes as $attribute) { $validations = []; if ($attribute->type == 'boolean') { continue; } elseif ($attribute->type == 'address') { if (! $attribute->is_required) { continue; } $validations = [ $attribute->code.'.address' => 'required', $attribute->code.'.country' => 'required', $attribute->code.'.state' => 'required', $attribute->code.'.city' => 'required', $attribute->code.'.postcode' => 'required', ]; } elseif ($attribute->type == 'email') { $validations = [ $attribute->code => [$attribute->is_required ? 'required' : 'nullable'], $attribute->code.'.*.value' => [$attribute->is_required ? 'required' : 'nullable', 'email'], $attribute->code.'.*.label' => $attribute->is_required ? 'required' : 'nullable', ]; } elseif ($attribute->type == 'phone') { $validations = [ $attribute->code => [$attribute->is_required ? 'required' : 'nullable'], $attribute->code.'.*.value' => [$attribute->is_required ? 'required' : 'nullable'], $attribute->code.'.*.label' => $attribute->is_required ? 'required' : 'nullable', ]; } else { $validations[$attribute->code] = [$attribute->is_required ? 'required' : 'nullable']; if ($attribute->type == 'text' && $attribute->validation) { array_push($validations[$attribute->code], $attribute->validation == 'decimal' ? new Decimal : $attribute->validation ); } if ($attribute->type == 'price') { array_push($validations[$attribute->code], new Decimal); } } if ($attribute->is_unique) { array_push($validations[in_array($attribute->type, ['email', 'phone']) ? $attribute->code.'.*.value' : $attribute->code ], function ($field, $value, $fail) use ($attribute) { if (! $this->attributeValueRepository->isValueUnique(null, $attribute->entity_type, $attribute, $field)) { $fail(trans('data_transfer::app.validation.errors.already-exists', ['attribute' => $attribute->name])); } }); } $rules = [ ...$rules, ...$validations, ]; } return $rules; } /** * Start the import process. */ public function importData(?ImportBatchContract $importBatch = null): bool { if ($importBatch) { $this->importBatch($importBatch); return true; } $typeBatches = []; foreach ($this->import->batches as $batch) { $typeBatches['import'][] = new ImportBatchJob($batch); if ($this->isLinkingRequired()) { $typeBatches['link'][] = new LinkBatchJob($batch); } if ($this->isIndexingRequired()) { $typeBatches['index'][] = new IndexBatchJob($batch); } } $chain[] = Bus::batch($typeBatches['import']); if (! empty($typeBatches['link'])) { $chain[] = new LinkingJob($this->import); $chain[] = Bus::batch($typeBatches['link']); } if (! empty($typeBatches['index'])) { $chain[] = new IndexingJob($this->import); $chain[] = Bus::batch($typeBatches['index']); } $chain[] = new CompletedJob($this->import); Bus::chain($chain)->dispatch(); return true; } /** * Link resource data. */ public function linkData(ImportBatchContract $importBatch): bool { $this->linkBatch($importBatch); return true; } /** * Index resource data. */ public function indexData(ImportBatchContract $importBatch): bool { $this->indexBatch($importBatch); return true; } /** * Add errors to error aggregator. */ protected function addErrors(string $code, mixed $errors): void { $this->errorHelper->addError( $code, null, implode('", "', $errors) ); } /** * Add row as skipped. * * @param int|null $rowNumber * @param string|null $columnName * @param string|null $errorMessage * @return $this */ protected function skipRow($rowNumber, string $errorCode, $columnName = null, $errorMessage = null): self { $this->errorHelper->addError( $errorCode, $rowNumber, $columnName, $errorMessage ); $this->errorHelper->addRowToSkip($rowNumber); return $this; } /** * Prepare row data to save into the database. */ protected function prepareRowForDb(array $rowData): array { $rowData = array_map(function ($value) { return $value === '' ? null : $value; }, $rowData); return $rowData; } /** * Returns number of checked rows. */ public function getProcessedRowsCount(): int { return $this->processedRowsCount; } /** * Returns number of created items count. */ public function getCreatedItemsCount(): int { return $this->createdItemsCount; } /** * Returns number of updated items count. */ public function getUpdatedItemsCount(): int { return $this->updatedItemsCount; } /** * Returns number of deleted items count. */ public function getDeletedItemsCount(): int { return $this->deletedItemsCount; } /** * Is linking resource required for the import operation. */ public function isLinkingRequired(): bool { if ($this->import->action == Import::ACTION_DELETE) { return false; } return $this->linkingRequired; } /** * Is indexing resource required for the import operation. */ public function isIndexingRequired(): bool { if ($this->import->action == Import::ACTION_DELETE) { return false; } return $this->indexingRequired; } } ================================================ FILE: packages/Webkul/DataTransfer/src/Helpers/Importers/Leads/Importer.php ================================================ 'data_transfer::app.importers.leads.validation.errors.id-not-found', ]; /** * Permanent entity columns. * * @var string[] */ protected $permanentAttributes = ['title']; /** * Permanent entity column. */ protected string $masterAttributeCode = 'id'; /** * Is linking required */ protected bool $linkingRequired = true; /** * Create a new helper instance. * * @return void */ public function __construct( protected ImportBatchRepository $importBatchRepository, protected LeadRepository $leadRepository, protected LeadProductRepository $leadProductRepository, protected AttributeRepository $attributeRepository, protected AttributeValueRepository $attributeValueRepository, protected Storage $leadsStorage, ) { parent::__construct( $importBatchRepository, $attributeRepository, $attributeValueRepository, ); } /** * Initialize leads error templates. */ protected function initErrorMessages(): void { foreach ($this->messages as $errorCode => $message) { $this->errorHelper->addErrorMessage($errorCode, trans($message)); } parent::initErrorMessages(); } /** * Validate data. */ public function validateData(): void { $this->leadsStorage->init(); parent::validateData(); } /** * Validates row. */ public function validateRow(array $rowData, int $rowNumber): bool { /** * If row is already validated than no need for further validation. */ if (isset($this->validatedRows[$rowNumber])) { return ! $this->errorHelper->isRowInvalid($rowNumber); } $this->validatedRows[$rowNumber] = true; /** * If import action is delete than no need for further validation. */ if ($this->import->action == Import::ACTION_DELETE) { if (! $this->isTitleExist($rowData['title'])) { $this->skipRow($rowNumber, self::ERROR_ID_NOT_FOUND_FOR_DELETE, 'id'); return false; } return true; } if (! empty($rowData['product'])) { $product = $this->parseProducts($rowData['product']); $validator = Validator::make($product, [ 'id' => 'required|exists:products,id', 'price' => 'required', 'quantity' => 'required', ]); if ($validator->fails()) { $failedAttributes = $validator->failed(); foreach ($validator->errors()->getMessages() as $attributeCode => $message) { $errorCode = array_key_first($failedAttributes[$attributeCode] ?? []); $this->skipRow($rowNumber, $errorCode, $attributeCode, current($message)); } } } /** * Validate leads attributes. */ $validator = Validator::make($rowData, [ ...$this->getValidationRules('leads|persons', $rowData), 'id' => 'numeric', 'status' => 'sometimes|required|in:0,1', 'user_id' => 'required|exists:users,id', 'person_id' => 'required|exists:persons,id', 'lead_source_id' => 'required|exists:lead_sources,id', 'lead_type_id' => 'required|exists:lead_types,id', 'lead_pipeline_id' => 'required|exists:lead_pipelines,id', 'lead_pipeline_stage_id' => 'required|exists:lead_pipeline_stages,id', ]); if ($validator->fails()) { $failedAttributes = $validator->failed(); foreach ($validator->errors()->getMessages() as $attributeCode => $message) { $errorCode = array_key_first($failedAttributes[$attributeCode] ?? []); $this->skipRow($rowNumber, $errorCode, $attributeCode, current($message)); } } return ! $this->errorHelper->isRowInvalid($rowNumber); } /** * Prepare row data for lead product. */ protected function parseProducts(?string $products): array { $productData = []; $productArray = explode(',', $products); foreach ($productArray as $product) { if (empty($product)) { continue; } [$key, $value] = explode('=', $product); $productData[$key] = $value; } if ( isset($productData['price']) && isset($productData['quantity']) ) { $productData['amount'] = $productData['price'] * $productData['quantity']; } return $productData; } /** * Get validation rules. */ public function getValidationRules(string $entityTypes, array $rowData): array { $rules = []; foreach (explode('|', $entityTypes) as $entityType) { $attributes = $this->attributeRepository->scopeQuery(fn ($query) => $query->whereIn('code', array_keys($rowData))->where('entity_type', $entityType))->get(); foreach ($attributes as $attribute) { if ($entityType == 'persons') { $attribute->code = 'person.'.$attribute->code; } $validations = []; if ($attribute->type == 'boolean') { continue; } elseif ($attribute->type == 'address') { if (! $attribute->is_required) { continue; } $validations = [ $attribute->code.'.address' => 'required', $attribute->code.'.country' => 'required', $attribute->code.'.state' => 'required', $attribute->code.'.city' => 'required', $attribute->code.'.postcode' => 'required', ]; } elseif ($attribute->type == 'email') { $validations = [ $attribute->code => [$attribute->is_required ? 'required' : 'nullable'], $attribute->code.'.*.value' => [$attribute->is_required ? 'required' : 'nullable', 'email'], $attribute->code.'.*.label' => $attribute->is_required ? 'required' : 'nullable', ]; } elseif ($attribute->type == 'phone') { $validations = [ $attribute->code => [$attribute->is_required ? 'required' : 'nullable'], $attribute->code.'.*.value' => [$attribute->is_required ? 'required' : 'nullable'], $attribute->code.'.*.label' => $attribute->is_required ? 'required' : 'nullable', ]; } else { $validations[$attribute->code] = [$attribute->is_required ? 'required' : 'nullable']; if ($attribute->type == 'text' && $attribute->validation) { array_push($validations[$attribute->code], $attribute->validation == 'decimal' ? new Decimal : $attribute->validation ); } if ($attribute->type == 'price') { array_push($validations[$attribute->code], new Decimal); } } if ($attribute->is_unique) { array_push($validations[in_array($attribute->type, ['email', 'phone']) ? $attribute->code.'.*.value' : $attribute->code ], function ($field, $value, $fail) use ($attribute) { if (! $this->attributeValueRepository->isValueUnique( null, $attribute->entity_type, $attribute, request($field) ) ) { $fail(trans('data_transfer::app.validation.errors.already-exists', ['attribute' => $attribute->name])); } }); } $rules = [ ...$rules, ...$validations, ]; } } return $rules; } /** * Start the import process. */ public function importBatch(ImportBatchContract $batch): bool { Event::dispatch('data_transfer.imports.batch.import.before', $batch); if ($batch->import->action == Import::ACTION_DELETE) { $this->deleteLeads($batch); } else { $this->saveLeads($batch); } /** * Update import batch summary. */ $batch = $this->importBatchRepository->update([ 'state' => Import::STATE_PROCESSED, 'summary' => [ 'created' => $this->getCreatedItemsCount(), 'updated' => $this->getUpdatedItemsCount(), 'deleted' => $this->getDeletedItemsCount(), ], ], $batch->id); Event::dispatch('data_transfer.imports.batch.import.after', $batch); return true; } /** * Start the products linking process */ public function linkBatch(ImportBatchContract $batch): bool { Event::dispatch('data_transfer.imports.batch.linking.before', $batch); /** * Load leads storage with batch ids. */ $this->leadsStorage->load(Arr::pluck($batch->data, 'title')); $products = []; foreach ($batch->data as $rowData) { /** * Prepare products. */ $this->prepareProducts($rowData, $products); } $this->saveProducts($products); /** * Update import batch summary */ $this->importBatchRepository->update([ 'state' => Import::STATE_LINKED, ], $batch->id); Event::dispatch('data_transfer.imports.batch.linking.after', $batch); return true; } /** * Prepare products. */ public function prepareProducts($rowData, &$product): void { if (! empty($rowData['product'])) { $product[$rowData['title']] = $this->parseProducts($rowData['product']); } } /** * Save products. */ public function saveProducts(array $products): void { $leadProducts = []; foreach ($products as $title => $product) { $lead = $this->leadsStorage->get($title); $leadProducts['insert'][] = [ 'lead_id' => $lead['id'], 'product_id' => $product['id'], 'price' => $product['price'], 'quantity' => $product['quantity'], 'amount' => $product['amount'], ]; } foreach ($leadProducts['insert'] as $key => $leadProduct) { $this->leadProductRepository->deleteWhere([ 'lead_id' => $leadProduct['lead_id'], 'product_id' => $leadProduct['product_id'], ]); } $this->leadProductRepository->upsert($leadProducts['insert'], ['lead_id', 'product_id']); } /** * Delete leads from current batch. */ protected function deleteLeads(ImportBatchContract $batch): bool { /** * Load leads storage with batch ids. */ $this->leadsStorage->load(Arr::pluck($batch->data, 'title')); $idsToDelete = []; foreach ($batch->data as $rowData) { if (! $this->isTitleExist($rowData['title'])) { continue; } $idsToDelete[] = $this->leadsStorage->get($rowData['title']); } $idsToDelete = array_unique($idsToDelete); $this->deletedItemsCount = count($idsToDelete); $this->leadRepository->deleteWhere([['id', 'IN', $idsToDelete]]); return true; } /** * Save leads from current batch. */ protected function saveLeads(ImportBatchContract $batch): bool { /** * Load lead storage with batch unique title. */ $this->leadsStorage->load(Arr::pluck($batch->data, 'title')); $leads = []; /** * Prepare leads for import. */ foreach ($batch->data as $rowData) { if (isset($rowData['id'])) { $leads['update'][$rowData['id']] = Arr::except($rowData, ['product']); } else { $leads['insert'][$rowData['title']] = [ ...Arr::except($rowData, ['id', 'product']), 'created_at' => $rowData['created_at'] ?? now(), 'updated_at' => $rowData['updated_at'] ?? now(), ]; } } if (! empty($leads['update'])) { $this->updatedItemsCount += count($leads['update']); $this->leadRepository->upsert( $leads['update'], $this->masterAttributeCode ); } if (! empty($leads['insert'])) { $this->createdItemsCount += count($leads['insert']); $this->leadRepository->insert($leads['insert']); /** * Update the sku storage with newly created products */ $newLeads = $this->leadRepository->findWhereIn( 'title', array_keys($leads['insert']), [ 'id', 'title', ] ); foreach ($newLeads as $lead) { $this->leadsStorage->set($lead->title, [ 'id' => $lead->id, 'title' => $lead->title, ]); } } return true; } /** * Check if title exists. */ public function isTitleExist(string $title): bool { return $this->leadsStorage->has($title); } /** * Prepare row data to save into the database. */ protected function prepareRowForDb(array $rowData): array { return parent::prepareRowForDb($rowData); } } ================================================ FILE: packages/Webkul/DataTransfer/src/Helpers/Importers/Leads/Storage.php ================================================ items = []; $this->load(); } /** * Load the leads. */ public function load(array $titles = []): void { if (empty($titles)) { $leads = $this->leadRepository->all($this->selectColumns); } else { $leads = $this->leadRepository->findWhereIn('title', $titles, $this->selectColumns); } foreach ($leads as $lead) { $this->set($lead->title, [ 'id' => $lead->id, 'title' => $lead->title, ]); } } /** * Get Ids and Unique Id. */ public function set(string $title, array $data): self { $this->items[$title] = $data; return $this; } /** * Check if unique id exists. */ public function has(string $title): bool { return isset($this->items[$title]); } /** * Get unique id information. */ public function get(string $title): ?array { if (! $this->has($title)) { return null; } return $this->items[$title]; } public function getItems(): array { return $this->items; } /** * Is storage is empty. */ public function isEmpty(): bool { return empty($this->items); } } ================================================ FILE: packages/Webkul/DataTransfer/src/Helpers/Importers/Persons/Importer.php ================================================ 'data_transfer::app.importers.persons.validation.errors.email-not-found', self::ERROR_DUPLICATE_EMAIL => 'data_transfer::app.importers.persons.validation.errors.duplicate-email', self::ERROR_DUPLICATE_PHONE => 'data_transfer::app.importers.persons.validation.errors.duplicate-phone', ]; /** * Permanent entity columns. * * @var string[] */ protected $permanentAttributes = ['emails']; /** * Permanent entity column. */ protected string $masterAttributeCode = 'unique_id'; /** * Emails storage. */ protected array $emails = []; /** * Phones storage. */ protected array $phones = []; /** * Create a new helper instance. * * @return void */ public function __construct( protected ImportBatchRepository $importBatchRepository, protected PersonRepository $personRepository, protected AttributeRepository $attributeRepository, protected AttributeValueRepository $attributeValueRepository, protected Storage $personStorage, ) { parent::__construct( $importBatchRepository, $attributeRepository, $attributeValueRepository, ); } /** * Initialize Product error templates. */ protected function initErrorMessages(): void { foreach ($this->messages as $errorCode => $message) { $this->errorHelper->addErrorMessage($errorCode, trans($message)); } parent::initErrorMessages(); } /** * Validate data. */ public function validateData(): void { $this->personStorage->init(); parent::validateData(); } /** * Validates row. */ public function validateRow(array $rowData, int $rowNumber): bool { $rowData = $this->parsedRowData($rowData); /** * If row is already validated than no need for further validation. */ if (isset($this->validatedRows[$rowNumber])) { return ! $this->errorHelper->isRowInvalid($rowNumber); } $this->validatedRows[$rowNumber] = true; /** * If import action is delete than no need for further validation. */ if ($this->import->action == Import::ACTION_DELETE) { foreach ($rowData['emails'] as $email) { if (! $this->isEmailExist($email['value'])) { $this->skipRow($rowNumber, self::ERROR_EMAIL_NOT_FOUND_FOR_DELETE, 'email'); return false; } return true; } } /** * Validate row data. */ $validator = Validator::make($rowData, [ ...$this->getValidationRules('persons', $rowData), 'organization_id' => 'required|exists:organizations,id', 'user_id' => 'required|exists:users,id', 'contact_numbers' => 'required|array', 'contact_numbers.*.value' => 'required|numeric', 'contact_numbers.*.label' => 'required|in:home,work', 'emails' => 'required|array', 'emails.*.value' => 'required|email', 'emails.*.label' => 'required|in:home,work', ]); if ($validator->fails()) { $failedAttributes = $validator->failed(); foreach ($validator->errors()->getMessages() as $attributeCode => $message) { $errorCode = array_key_first($failedAttributes[$attributeCode] ?? []); $this->skipRow($rowNumber, $errorCode, $attributeCode, current($message)); } } /** * Check if email is unique. */ if (! empty($emails = $rowData['emails'])) { foreach ($emails as $email) { if (! in_array($email['value'], $this->emails)) { $this->emails[] = $email['value']; } else { $message = sprintf( trans($this->messages[self::ERROR_DUPLICATE_EMAIL]), $email['value'] ); $this->skipRow($rowNumber, self::ERROR_DUPLICATE_EMAIL, 'email', $message); } } } /** * Check if phone(s) are unique. */ if (! empty($rowData['contact_numbers'])) { foreach ($rowData['contact_numbers'] as $phone) { if (! in_array($phone['value'], $this->phones)) { if (! empty($phone['value'])) { $this->phones[] = $phone['value']; } } else { $message = sprintf( trans($this->messages[self::ERROR_DUPLICATE_PHONE]), $phone['value'] ); $this->skipRow($rowNumber, self::ERROR_DUPLICATE_PHONE, 'phone', $message); } } } return ! $this->errorHelper->isRowInvalid($rowNumber); } /** * Start the import process. */ public function importBatch(ImportBatchContract $batch): bool { Event::dispatch('data_transfer.imports.batch.import.before', $batch); if ($batch->import->action == Import::ACTION_DELETE) { $this->deletePersons($batch); } else { $this->savePersonData($batch); } /** * Update import batch summary. */ $batch = $this->importBatchRepository->update([ 'state' => Import::STATE_PROCESSED, 'summary' => [ 'created' => $this->getCreatedItemsCount(), 'updated' => $this->getUpdatedItemsCount(), 'deleted' => $this->getDeletedItemsCount(), ], ], $batch->id); Event::dispatch('data_transfer.imports.batch.import.after', $batch); return true; } /** * Delete persons from current batch. */ protected function deletePersons(ImportBatchContract $batch): bool { /** * Load person storage with batch emails. */ $emails = collect(Arr::pluck($batch->data, 'emails')) ->map(function ($emails) { $emails = json_decode($emails, true); foreach ($emails as $email) { return $email['value']; } }); $this->personStorage->load($emails->toArray()); $idsToDelete = []; foreach ($batch->data as $rowData) { $rowData = $this->parsedRowData($rowData); foreach ($rowData['emails'] as $email) { if (! $this->isEmailExist($email['value'])) { continue; } $idsToDelete[] = $this->personStorage->get($email['value']); } } $idsToDelete = array_unique($idsToDelete); $this->deletedItemsCount = count($idsToDelete); $this->personRepository->deleteWhere([['id', 'IN', $idsToDelete]]); return true; } /** * Save person from current batch. */ protected function savePersonData(ImportBatchContract $batch): bool { /** * Load person storage with batch email. */ $emails = collect(Arr::pluck($batch->data, 'emails')) ->map(function ($emails) { $emails = json_decode($emails, true); foreach ($emails as $email) { return $email['value']; } }); $this->personStorage->load($emails->toArray()); $persons = []; $attributeValues = []; /** * Prepare persons for import. */ foreach ($batch->data as $rowData) { $this->preparePersons($rowData, $persons); $this->prepareAttributeValues($rowData, $attributeValues); } $this->savePersons($persons); $this->saveAttributeValues($attributeValues); return true; } /** * Prepare persons from current batch. */ public function preparePersons(array $rowData, array &$persons): void { $emails = $this->prepareEmail($rowData['emails']); foreach ($emails as $email) { $contactNumber = json_decode($rowData['contact_numbers'], true); $rowData['unique_id'] = "{$rowData['user_id']}|{$rowData['organization_id']}|{$email}|{$contactNumber[0]['value']}"; if ($this->isEmailExist($email)) { $persons['update'][$email] = $rowData; } else { $persons['insert'][$email] = [ ...$rowData, 'created_at' => $rowData['created_at'] ?? now(), 'updated_at' => $rowData['updated_at'] ?? now(), ]; } } } /** * Save persons from current batch. */ public function savePersons(array $persons): void { if (! empty($persons['update'])) { $this->updatedItemsCount += count($persons['update']); $this->personRepository->upsert( $persons['update'], $this->masterAttributeCode, ); } if (! empty($persons['insert'])) { $this->createdItemsCount += count($persons['insert']); $this->personRepository->insert($persons['insert']); /** * Update the sku storage with newly created products */ $emails = array_keys($persons['insert']); $newPersons = $this->personRepository->where(function ($query) use ($emails) { foreach ($emails as $email) { $query->orWhereJsonContains('emails', [['value' => $email]]); } })->get(); foreach ($newPersons as $person) { $this->personStorage->set($person->emails[0]['value'], $person->id); } } } /** * Save attribute values for the person. */ public function saveAttributeValues(array $attributeValues): void { $personAttributeValues = []; foreach ($attributeValues as $email => $attributeValue) { foreach ($attributeValue as $attribute) { $attribute['entity_id'] = (int) $this->personStorage->get($email); $attribute['unique_id'] = implode('|', array_filter([ $attribute['entity_id'], $attribute['attribute_id'], ])); $attribute['entity_type'] = 'persons'; $personAttributeValues[$attribute['unique_id']] = $attribute; } } $this->attributeValueRepository->upsert($personAttributeValues, 'unique_id'); } /** * Check if email exists. */ public function isEmailExist(string $email): bool { return $this->personStorage->has($email); } /** * Prepare attribute values for the person. */ public function prepareAttributeValues(array $rowData, array &$attributeValues): void { foreach ($rowData as $code => $value) { if (is_null($value)) { continue; } $where = ['code' => $code]; if ($code === 'name') { $where['entity_type'] = 'persons'; } $attribute = $this->attributeRepository->findOneWhere($where); if (! $attribute) { continue; } $typeFields = $this->personRepository->getModel()::$attributeTypeFields; $attributeTypeValues = array_fill_keys(array_values($typeFields), null); $emails = $this->prepareEmail($rowData['emails']); foreach ($emails as $email) { $attributeValues[$email][] = array_merge($attributeTypeValues, [ 'attribute_id' => $attribute->id, $typeFields[$attribute->type] => $value, ]); } } } /** * Get parsed email and phone. */ private function parsedRowData(array $rowData): array { $rowData['emails'] = json_decode($rowData['emails'], true); $rowData['contact_numbers'] = json_decode($rowData['contact_numbers'], true); return $rowData; } /** * Prepare email from row data. */ private function prepareEmail(array|string $emails): Collection { static $cache = []; return collect($emails) ->map(function ($emailString) use (&$cache) { if (isset($cache[$emailString])) { return $cache[$emailString]; } $decoded = json_decode($emailString, true); $emailValue = is_array($decoded) && isset($decoded[0]['value']) ? $decoded[0]['value'] : null; return $cache[$emailString] = $emailValue; }); } } ================================================ FILE: packages/Webkul/DataTransfer/src/Helpers/Importers/Persons/Storage.php ================================================ items = []; $this->load(); } /** * Load the Emails. */ public function load(array $emails = []): void { if (empty($emails)) { $persons = $this->personRepository->all($this->selectColumns); } else { $persons = $this->personRepository->scopeQuery(function ($query) use ($emails) { return $query->where(function ($subQuery) use ($emails) { foreach ($emails as $email) { $subQuery->orWhereJsonContains('emails', ['value' => $email]); } }); })->all($this->selectColumns); } $persons->each(function ($person) { collect($person->emails) ->each(fn ($email) => $this->set($email['value'], $person->id)); }); } /** * Get email information. */ public function set(string $email, int $id): self { $this->items[$email] = $id; return $this; } /** * Check if email exists. */ public function has(string $email): bool { return isset($this->items[$email]); } /** * Get email information. */ public function get(string $email): ?int { if (! $this->has($email)) { return null; } return $this->items[$email]; } /** * Is storage is empty. */ public function isEmpty(): int { return empty($this->items); } } ================================================ FILE: packages/Webkul/DataTransfer/src/Helpers/Importers/Products/Importer.php ================================================ 'data_transfer::app.importers.products.validation.errors.sku-not-found', ]; /** * Permanent entity columns. */ protected array $permanentAttributes = ['sku']; /** * Permanent entity column. */ protected string $masterAttributeCode = 'sku'; /** * Cached attributes. */ protected mixed $attributes = []; /** * Valid csv columns. */ protected array $validColumnNames = [ 'sku', 'name', 'description', 'quantity', 'price', ]; /** * Create a new helper instance. * * @return void */ public function __construct( protected ImportBatchRepository $importBatchRepository, protected AttributeRepository $attributeRepository, protected AttributeOptionRepository $attributeOptionRepository, protected ProductRepository $productRepository, protected ProductInventoryRepository $productInventoryRepository, protected AttributeValueRepository $attributeValueRepository, protected SKUStorage $skuStorage ) { parent::__construct( $importBatchRepository, $attributeRepository, $attributeValueRepository ); $this->initAttributes(); } /** * Load all attributes and families to use later. */ protected function initAttributes(): void { $this->attributes = $this->attributeRepository->all(); foreach ($this->attributes as $attribute) { $this->validColumnNames[] = $attribute->code; } } /** * Initialize Product error templates. */ protected function initErrorMessages(): void { foreach ($this->messages as $errorCode => $message) { $this->errorHelper->addErrorMessage($errorCode, trans($message)); } parent::initErrorMessages(); } /** * Save validated batches. */ protected function saveValidatedBatches(): self { $source = $this->getSource(); $source->rewind(); $this->skuStorage->init(); while ($source->valid()) { try { $rowData = $source->current(); } catch (\InvalidArgumentException $e) { $source->next(); continue; } $this->validateRow($rowData, $source->getCurrentRowNumber()); $source->next(); } parent::saveValidatedBatches(); return $this; } /** * Validates row. */ public function validateRow(array $rowData, int $rowNumber): bool { /** * If row is already validated than no need for further validation. */ if (isset($this->validatedRows[$rowNumber])) { return ! $this->errorHelper->isRowInvalid($rowNumber); } $this->validatedRows[$rowNumber] = true; /** * If import action is delete than no need for further validation. */ if ($this->import->action == Import::ACTION_DELETE) { if (! $this->isSKUExist($rowData['sku'])) { $this->skipRow($rowNumber, self::ERROR_SKU_NOT_FOUND_FOR_DELETE, 'sku'); return false; } return true; } /** * Validate product attributes */ $validator = Validator::make($rowData, $this->getValidationRules('products', $rowData)); if ($validator->fails()) { foreach ($validator->errors()->getMessages() as $attributeCode => $message) { $failedAttributes = $validator->failed(); $errorCode = array_key_first($failedAttributes[$attributeCode] ?? []); $this->skipRow($rowNumber, $errorCode, $attributeCode, current($message)); } } return ! $this->errorHelper->isRowInvalid($rowNumber); } /** * Start the import process. */ public function importBatch(ImportBatchContract $batch): bool { Event::dispatch('data_transfer.imports.batch.import.before', $batch); if ($batch->import->action == Import::ACTION_DELETE) { $this->deleteProducts($batch); } else { $this->saveProductsData($batch); } /** * Update import batch summary. */ $batch = $this->importBatchRepository->update([ 'state' => Import::STATE_PROCESSED, 'summary' => [ 'created' => $this->getCreatedItemsCount(), 'updated' => $this->getUpdatedItemsCount(), 'deleted' => $this->getDeletedItemsCount(), ], ], $batch->id); Event::dispatch('data_transfer.imports.batch.import.after', $batch); return true; } /** * Delete products from current batch. */ protected function deleteProducts(ImportBatchContract $batch): bool { /** * Load SKU storage with batch skus. */ $this->skuStorage->load(Arr::pluck($batch->data, 'sku')); $idsToDelete = []; foreach ($batch->data as $rowData) { if (! $this->isSKUExist($rowData['sku'])) { continue; } $product = $this->skuStorage->get($rowData['sku']); $idsToDelete[] = $product['id']; } $idsToDelete = array_unique($idsToDelete); $this->deletedItemsCount = count($idsToDelete); $this->productRepository->deleteWhere([['id', 'IN', $idsToDelete]]); return true; } /** * Save products from current batch. */ protected function saveProductsData(ImportBatchContract $batch): bool { /** * Load SKU storage with batch skus. */ $this->skuStorage->load(Arr::pluck($batch->data, 'sku')); $products = []; /** * Prepare products for import. */ foreach ($batch->data as $rowData) { $this->prepareProducts($rowData, $products); } $this->saveProducts($products); return true; } /** * Prepare products from current batch. */ public function prepareProducts(array $rowData, array &$products): void { if ($this->isSKUExist($rowData['sku'])) { $products['update'][$rowData['sku']] = $rowData; } else { $products['insert'][$rowData['sku']] = [ ...$rowData, 'created_at' => $rowData['created_at'] ?? now(), 'updated_at' => $rowData['updated_at'] ?? now(), ]; } } /** * Save products from current batch. */ public function saveProducts(array $products): void { if (! empty($products['update'])) { $this->updatedItemsCount += count($products['update']); $this->productRepository->upsert( $products['update'], $this->masterAttributeCode ); } if (! empty($products['insert'])) { $this->createdItemsCount += count($products['insert']); $this->productRepository->insert($products['insert']); } } /** * Save channels from current batch. */ public function saveChannels(array $channels): void { $productChannels = []; foreach ($channels as $sku => $channelIds) { $product = $this->skuStorage->get($sku); foreach (array_unique($channelIds) as $channelId) { $productChannels[] = [ 'product_id' => $product['id'], 'channel_id' => $channelId, ]; } } DB::table('product_channels')->upsert( $productChannels, [ 'product_id', 'channel_id', ], ); } /** * Save links. */ public function loadUnloadedSKUs(array $skus): void { $notLoadedSkus = []; foreach ($skus as $sku) { if ($this->skuStorage->has($sku)) { continue; } $notLoadedSkus[] = $sku; } /** * Load not loaded SKUs to the sku storage. */ if (! empty($notLoadedSkus)) { $this->skuStorage->load($notLoadedSkus); } } /** * Check if SKU exists. */ public function isSKUExist(string $sku): bool { return $this->skuStorage->has($sku); } /** * Prepare row data to save into the database. */ protected function prepareRowForDb(array $rowData): array { return parent::prepareRowForDb($rowData); } } ================================================ FILE: packages/Webkul/DataTransfer/src/Helpers/Importers/Products/SKUStorage.php ================================================ items = []; $this->load(); } /** * Load the SKU. */ public function load(array $skus = []): void { if (empty($skus)) { $products = $this->productRepository->all($this->selectColumns); } else { $products = $this->productRepository->findWhereIn('sku', $skus, $this->selectColumns); } foreach ($products as $product) { $this->set($product->sku, [ 'id' => $product->id, 'sku' => $product->sku, ]); } } /** * Get SKU information. */ public function set(string $sku, array $data): self { $this->items[$sku] = implode(self::DELIMITER, [ $data['id'], $data['sku'], ]); return $this; } /** * Check if SKU exists. */ public function has(string $sku): bool { return isset($this->items[$sku]); } /** * Get SKU information. */ public function get(string $sku): ?array { if (! $this->has($sku)) { return null; } $data = explode(self::DELIMITER, $this->items[$sku]); return [ 'id' => $data[0], ]; } /** * Is storage is empty. */ public function isEmpty(): int { return empty($this->items); } } ================================================ FILE: packages/Webkul/DataTransfer/src/Helpers/Sources/AbstractSource.php ================================================ currentRowNumber; } /** * Checks if current position is valid. */ public function valid(): bool { return $this->currentRowNumber !== -1; } /** * Read next line from source. */ public function current(): array { $row = $this->currentRowData; if (count($row) != $this->totalColumns) { if ($this->foundWrongQuoteFlag) { throw new \InvalidArgumentException(AbstractImporter::ERROR_CODE_WRONG_QUOTES); } else { throw new \InvalidArgumentException(AbstractImporter::ERROR_CODE_COLUMNS_NUMBER); } } return array_combine($this->columnNames, $row); } /** * Read next line from source. */ public function next(): void { $this->currentRowNumber++; $row = $this->getNextRow(); if ($row === false || $row === []) { $this->currentRowData = []; $this->currentRowNumber = -1; } else { $this->currentRowData = $row; } } /** * Rewind the iterator to the first row. */ public function rewind(): void { $this->currentRowNumber = 0; $this->currentRowData = []; $this->getNextRow(); $this->next(); } /** * Column names getter. */ public function getColumnNames(): array { return $this->columnNames; } /** * Column names getter. */ public function getTotalColumns(): int { return count($this->columnNames); } } ================================================ FILE: packages/Webkul/DataTransfer/src/Helpers/Sources/CSV.php ================================================ reader = fopen(Storage::disk('public')->path($filePath), 'r'); $this->columnNames = fgetcsv($this->reader, 4096, $delimiter); $this->totalColumns = count($this->columnNames); } catch (\Exception $e) { throw new \LogicException("Unable to open file: '{$filePath}'"); } } /** * Close file handle. * * @return void */ public function __destruct() { if (! is_object($this->reader)) { return; } $this->reader->close(); } /** * Read next line from csv. */ protected function getNextRow(): array { $parsed = fgetcsv($this->reader, 4096, $this->delimiter); if (is_array($parsed) && count($parsed) != $this->totalColumns) { foreach ($parsed as $element) { if ($element && strpos($element, "'") !== false) { $this->foundWrongQuoteFlag = true; break; } } } else { $this->foundWrongQuoteFlag = false; } return is_array($parsed) ? $parsed : []; } /** * Rewind the iterator to the first row. */ public function rewind(): void { rewind($this->reader); parent::rewind(); } } ================================================ FILE: packages/Webkul/DataTransfer/src/Helpers/Sources/Excel.php ================================================ path($filePath)); $this->reader = $factory->getActiveSheet(); $highestColumn = $this->reader->getHighestColumn(); $this->totalColumns = Coordinate::columnIndexFromString($highestColumn); $this->columnNames = $this->getNextRow(); } catch (\Exception $e) { throw new \LogicException("Unable to open file: '{$filePath}'"); } } /** * Read next line from csv. */ protected function getNextRow(): array|bool { for ($column = 1; $column <= $this->totalColumns; $column++) { $rowData[] = $this->reader->getCellByColumnAndRow($column, $this->currentRowNumber)->getValue(); } $filteredRowData = array_filter($rowData); if (empty($filteredRowData)) { return false; } return $rowData; } /** * Rewind the iterator to the first row. */ public function rewind(): void { $this->currentRowNumber = 1; $this->next(); } } ================================================ FILE: packages/Webkul/DataTransfer/src/Jobs/Import/Completed.php ================================================ import = $import; } /** * Execute the job. * * @return void */ public function handle() { app(ImportHelper::class) ->setImport($this->import) ->completed(); } } ================================================ FILE: packages/Webkul/DataTransfer/src/Jobs/Import/ImportBatch.php ================================================ importBatch = $importBatch; } /** * Execute the job. * * @return void */ public function handle() { $typeImported = app(ImportHelper::class) ->setImport($this->importBatch->import) ->getTypeImporter(); $typeImported->importBatch($this->importBatch); } } ================================================ FILE: packages/Webkul/DataTransfer/src/Jobs/Import/IndexBatch.php ================================================ importBatch = $importBatch; } /** * Execute the job. * * @return void */ public function handle() { $typeImported = app(ImportHelper::class) ->setImport($this->importBatch->import) ->getTypeImporter(); $typeImported->indexBatch($this->importBatch); } } ================================================ FILE: packages/Webkul/DataTransfer/src/Jobs/Import/Indexing.php ================================================ import = $import; } /** * Execute the job. * * @return void */ public function handle() { app(ImportHelper::class) ->setImport($this->import) ->indexing(); } } ================================================ FILE: packages/Webkul/DataTransfer/src/Jobs/Import/LinkBatch.php ================================================ importBatch = $importBatch; } /** * Execute the job. * * @return void */ public function handle() { $typeImported = app(ImportHelper::class) ->setImport($this->importBatch->import) ->getTypeImporter(); $typeImported->linkBatch($this->importBatch); } } ================================================ FILE: packages/Webkul/DataTransfer/src/Jobs/Import/Linking.php ================================================ import = $import; } /** * Execute the job. * * @return void */ public function handle() { app(ImportHelper::class) ->setImport($this->import) ->linking(); } } ================================================ FILE: packages/Webkul/DataTransfer/src/Models/Import.php ================================================ 'array', 'errors' => 'array', 'started_at' => 'datetime', 'completed_at' => 'datetime', ]; /** * Get the options. */ public function batches(): HasMany { return $this->hasMany(ImportBatchProxy::modelClass()); } /** * Get the file name. */ public function getFileNameAttribute(): string { return preg_replace('/^.*?\/\d+-/', '', $this->file_path); } } ================================================ FILE: packages/Webkul/DataTransfer/src/Models/ImportBatch.php ================================================ 'array', 'data' => 'array', ]; /** * Get the import that owns the import batch. * * @return BelongsTo */ public function import() { return $this->belongsTo(ImportProxy::modelClass()); } } ================================================ FILE: packages/Webkul/DataTransfer/src/Models/ImportBatchProxy.php ================================================ loadTranslationsFrom(__DIR__.'/../Resources/lang', 'data_transfer'); $this->loadMigrationsFrom(__DIR__.'/../Database/Migrations'); } /** * Register any application services. */ public function register(): void { $this->mergeConfigFrom(dirname(__DIR__).'/Config/importers.php', 'importers'); } } ================================================ FILE: packages/Webkul/DataTransfer/src/Providers/ModuleServiceProvider.php ================================================ [ 'persons' => [ 'title' => 'الأشخاص', 'validation' => [ 'errors' => [ 'duplicate-email' => 'البريد الإلكتروني: \'%s\' تم العثور عليه أكثر من مرة في ملف الاستيراد.', 'duplicate-phone' => 'الهاتف: \'%s\' تم العثور عليه أكثر من مرة في ملف الاستيراد.', 'email-not-found' => 'البريد الإلكتروني: \'%s\' لم يتم العثور عليه في النظام.', ], ], ], 'products' => [ 'title' => 'المنتجات', 'validation' => [ 'errors' => [ 'sku-not-found' => 'المنتج ذو الكود المحدد لم يتم العثور عليه.', ], ], ], 'leads' => [ 'title' => 'العملاء المحتملون', 'validation' => [ 'errors' => [ 'id-not-found' => 'المعرف: \'%s\' غير موجود في النظام.', ], ], ], ], 'validation' => [ 'errors' => [ 'column-empty-headers' => 'الأعمدة رقم "%s" تحتوي على رؤوس فارغة.', 'column-name-invalid' => 'أسماء الأعمدة غير صالحة: "%s".', 'column-not-found' => 'الأعمدة المطلوبة غير موجودة: %s.', 'column-numbers' => 'عدد الأعمدة لا يتوافق مع عدد الصفوف في الرأس.', 'invalid-attribute' => 'الرأس يحتوي على سمة غير صالحة: "%s".', 'system' => 'حدث خطأ غير متوقع في النظام.', 'wrong-quotes' => 'تم استخدام علامات الاقتباس الملتوية بدلاً من الاقتباسات المستقيمة.', ], ], ]; ================================================ FILE: packages/Webkul/DataTransfer/src/Resources/lang/en/app.php ================================================ [ 'persons' => [ 'title' => 'Persons', 'validation' => [ 'errors' => [ 'duplicate-email' => 'Email : \'%s\' is found more than once in the import file.', 'duplicate-phone' => 'Phone : \'%s\' is found more than once in the import file.', 'email-not-found' => 'Email : \'%s\' not found in the system.', ], ], ], 'products' => [ 'title' => 'Products', 'validation' => [ 'errors' => [ 'sku-not-found' => 'Product with specified SKU not found', ], ], ], 'leads' => [ 'title' => 'Leads', 'validation' => [ 'errors' => [ 'id-not-found' => 'ID : \'%s\' not found in the system.', ], ], ], ], 'validation' => [ 'errors' => [ 'column-empty-headers' => 'Columns number "%s" have empty headers.', 'column-name-invalid' => 'Invalid column names: "%s".', 'column-not-found' => 'Required columns not found: %s.', 'column-numbers' => 'Number of columns does not correspond to the number of rows in the header.', 'invalid-attribute' => 'Header contains invalid attribute(s): "%s".', 'system' => 'An unexpected system error occurred.', 'wrong-quotes' => 'Curly quotes used instead of straight quotes.', 'already-exists' => 'The :attribute already exists.', ], ], ]; ================================================ FILE: packages/Webkul/DataTransfer/src/Resources/lang/es/app.php ================================================ [ 'persons' => [ 'title' => 'Personas', 'validation' => [ 'errors' => [ 'duplicate-email' => 'Correo electrónico: \'%s\' se encontró más de una vez en el archivo de importación.', 'duplicate-phone' => 'Teléfono: \'%s\' se encontró más de una vez en el archivo de importación.', 'email-not-found' => 'Correo electrónico: \'%s\' no se encontró en el sistema.', ], ], ], 'products' => [ 'title' => 'Productos', 'validation' => [ 'errors' => [ 'sku-not-found' => 'Producto con el SKU especificado no encontrado.', ], ], ], 'leads' => [ 'title' => 'Clientes Potenciales', 'validation' => [ 'errors' => [ 'id-not-found' => 'ID: \'%s\' no se encuentra en el sistema.', ], ], ], ], 'validation' => [ 'errors' => [ 'column-empty-headers' => 'Las columnas número "%s" tienen encabezados vacíos.', 'column-name-invalid' => 'Nombres de columnas no válidos: "%s".', 'column-not-found' => 'No se encontraron las columnas requeridas: %s.', 'column-numbers' => 'El número de columnas no corresponde al número de filas en el encabezado.', 'invalid-attribute' => 'El encabezado contiene atributos no válidos: "%s".', 'system' => 'Ocurrió un error inesperado en el sistema.', 'wrong-quotes' => 'Se usaron comillas curvas en lugar de comillas rectas.', ], ], ]; ================================================ FILE: packages/Webkul/DataTransfer/src/Resources/lang/fa/app.php ================================================ [ 'persons' => [ 'title' => 'افراد', 'validation' => [ 'errors' => [ 'duplicate-email' => 'ایمیل: \'%s\' بیش از یک بار در فایل واردات یافت شد.', 'duplicate-phone' => 'تلفن: \'%s\' بیش از یک بار در فایل واردات یافت شد.', 'email-not-found' => 'ایمیل: \'%s\' در سیستم یافت نشد.', ], ], ], 'products' => [ 'title' => 'محصولات', 'validation' => [ 'errors' => [ 'sku-not-found' => 'محصول با کد SKU مشخص شده یافت نشد.', ], ], ], 'leads' => [ 'title' => 'سرنخ‌ها', 'validation' => [ 'errors' => [ 'id-not-found' => 'شناسه: \'%s\' در سیستم یافت نشد.', ], ], ], ], 'validation' => [ 'errors' => [ 'column-empty-headers' => 'ستون‌های شماره "%s" دارای سرصفحه‌های خالی هستند.', 'column-name-invalid' => 'نام‌های ستون نامعتبر: "%s".', 'column-not-found' => 'ستون‌های مورد نیاز یافت نشد: %s.', 'column-numbers' => 'تعداد ستون‌ها با تعداد سطرهای سرصفحه مطابقت ندارد.', 'invalid-attribute' => 'سرصفحه شامل ویژگی‌های نامعتبر است: "%s".', 'system' => 'خطای غیرمنتظره‌ای در سیستم رخ داد.', 'wrong-quotes' => 'به جای گیومه‌های مستقیم از گیومه‌های خمیده استفاده شده است.', ], ], ]; ================================================ FILE: packages/Webkul/DataTransfer/src/Resources/lang/pt_BR/app.php ================================================ [ 'persons' => [ 'title' => 'Pessoas', 'validation' => [ 'errors' => [ 'duplicate-email' => 'E-mail : \'%s\' é encontrado mais de uma vez no arquivo de importação.', 'duplicate-phone' => 'Telefone : \'%s\' é encontrado mais de uma vez no arquivo de importação.', 'email-not-found' => 'E-mail : \'%s\' não foi encontrado no sistema.', ], ], ], 'products' => [ 'title' => 'Produtos', 'validation' => [ 'errors' => [ 'sku-not-found' => 'Produto com este código não foi encontrado', ], ], ], 'leads' => [ 'title' => 'Oportunidades', 'validation' => [ 'errors' => [ 'id-not-found' => 'ID : \'%s\' não foi encontrado no sistema.', ], ], ], ], 'validation' => [ 'errors' => [ 'column-empty-headers' => 'As colunas de número "%s" têm cabeçalhos vazios.', 'column-name-invalid' => 'Nomes de colunas inválidos: "%s".', 'column-not-found' => 'Colunas obrigatórias não encontradas: %s.', 'column-numbers' => 'O número de colunas não corresponde ao número de linhas no cabeçalho.', 'invalid-attribute' => 'O cabeçalho contém atributo(s) inválido(s): "%s".', 'system' => 'Ocorreu um erro inesperado no sistema.', 'wrong-quotes' => 'Aspas curvas usadas em vez de aspas retas.', 'already-exists' => 'O :attribute já existe.', ], ], ]; ================================================ FILE: packages/Webkul/DataTransfer/src/Resources/lang/tr/app.php ================================================ [ 'persons' => [ 'title' => 'Kişiler', 'validation' => [ 'errors' => [ 'duplicate-email' => 'E-posta: \'%s\' içe aktarma dosyasında birden fazla kez bulundu.', 'duplicate-phone' => 'Telefon: \'%s\' içe aktarma dosyasında birden fazla kez bulundu.', 'email-not-found' => 'E-posta: \'%s\' sistemde bulunamadı.', ], ], ], 'products' => [ 'title' => 'Ürünler', 'validation' => [ 'errors' => [ 'sku-not-found' => 'Belirtilen SKU\'ya sahip ürün bulunamadı.', ], ], ], 'leads' => [ 'title' => 'Müşteri Adayları', 'validation' => [ 'errors' => [ 'id-not-found' => 'ID: \'%s\' sistemde bulunamadı.', ], ], ], ], 'validation' => [ 'errors' => [ 'column-empty-headers' => '"%s" numaralı sütunların başlıkları boş.', 'column-name-invalid' => 'Geçersiz sütun adları: "%s".', 'column-not-found' => 'Gerekli sütunlar bulunamadı: %s.', 'column-numbers' => 'Sütun sayısı başlıktaki satır sayısına karşılık gelmiyor.', 'invalid-attribute' => 'Başlık geçersiz öznitelikler içeriyor: "%s".', 'system' => 'Beklenmeyen bir sistem hatası oluştu.', 'wrong-quotes' => 'Doğru olmayan tırnak işaretleri kullanıldı.', ], ], ]; ================================================ FILE: packages/Webkul/Email/composer.json ================================================ { "name": "krayin/laravel-email", "license": "MIT", "authors": [ { "name": "Jitendra Singh", "email": "jitendra@webkul.com" } ], "require": { "krayin/laravel-contact": "^1.0", "krayin/laravel-core": "^1.0" }, "autoload": { "psr-4": { "Webkul\\Email\\": "src/" } }, "extra": { "laravel": { "providers": [ "Webkul\\Email\\Providers\\EmailServiceProvider" ], "aliases": {} } }, "minimum-stability": "dev" } ================================================ FILE: packages/Webkul/Email/src/Console/Commands/ProcessInboundEmails.php ================================================ info('Processing the incoming emails.'); $this->inboundEmailProcessor->processMessagesFromAllFolders(); $this->info('Incoming emails processed successfully.'); } } ================================================ FILE: packages/Webkul/Email/src/Contracts/Attachment.php ================================================ increments('id'); $table->string('subject')->nullable(); $table->string('source'); $table->string('user_type'); $table->string('name')->nullable(); $table->text('reply')->nullable(); $table->boolean('is_read')->default(0); $table->json('folders')->nullable(); $table->json('from')->nullable(); $table->json('sender')->nullable(); $table->json('reply_to')->nullable(); $table->json('cc')->nullable(); $table->json('bcc')->nullable(); $table->string('unique_id')->nullable()->unique(); $table->string('message_id')->unique(); $table->json('reference_ids')->nullable(); $table->integer('person_id')->unsigned()->nullable(); $table->foreign('person_id')->references('id')->on('persons')->onDelete('set null'); $table->integer('lead_id')->unsigned()->nullable(); $table->foreign('lead_id')->references('id')->on('leads')->onDelete('set null'); $table->timestamps(); }); Schema::table('emails', function (Blueprint $table) { $table->integer('parent_id')->unsigned()->nullable(); $table->foreign('parent_id')->references('id')->on('emails')->onDelete('cascade'); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('emails'); } }; ================================================ FILE: packages/Webkul/Email/src/Database/Migrations/2021_05_25_072700_create_email_attachments_table.php ================================================ increments('id'); $table->string('name')->nullable(); $table->string('path'); $table->integer('size')->nullable(); $table->string('content_type')->nullable(); $table->string('content_id')->nullable(); $table->integer('email_id')->unsigned(); $table->foreign('email_id')->references('id')->on('emails')->onDelete('cascade'); $table->timestamps(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('email_attachments'); } }; ================================================ FILE: packages/Webkul/Email/src/Database/Migrations/2024_08_27_091619_create_email_tags_table.php ================================================ integer('tag_id')->unsigned(); $table->foreign('tag_id')->references('id')->on('tags')->onDelete('cascade'); $table->integer('email_id')->unsigned(); $table->foreign('email_id')->references('id')->on('emails')->onDelete('cascade'); }); } /** * Reverse the migrations. */ public function down(): void { Schema::dropIfExists('email_tags'); } }; ================================================ FILE: packages/Webkul/Email/src/Enums/SupportedFolderEnum.php ================================================ filename; } /** * Retrieve the attachment content type. * * @return string */ public function getContentType() { return $this->contentType; } /** * Retrieve the attachment content disposition. * * @return string */ public function getContentDisposition() { return $this->contentDisposition; } /** * Retrieve the attachment content ID. * * @return string */ public function getContentID() { return $this->contentId; } /** * Retrieve the attachment headers. * * @return string */ public function getHeaders() { return $this->headers; } /** * Read the contents a few bytes at a time until completed. * * Once read to completion, it always returns false. * * @param int $bytes * @return string */ public function read($bytes = 2082) { return feof($this->stream) ? false : fread($this->stream, $bytes); } /** * Retrieve the file content in one go. * * Once you retrieve the content you cannot use MimeMailParser_attachment::read(). * * @return string */ public function getContent() { if ($this->content === null) { fseek($this->stream, 0); while (($buf = $this->read()) !== false) { $this->content .= $buf; } } return $this->content; } } ================================================ FILE: packages/Webkul/Email/src/Helpers/Charset.php ================================================ 'us-ascii', 'us-ascii' => 'us-ascii', 'ansi_x3.4-1968' => 'us-ascii', '646' => 'us-ascii', 'iso-8859-1' => 'ISO-8859-1', 'iso-8859-2' => 'ISO-8859-2', 'iso-8859-3' => 'ISO-8859-3', 'iso-8859-4' => 'ISO-8859-4', 'iso-8859-5' => 'ISO-8859-5', 'iso-8859-6' => 'ISO-8859-6', 'iso-8859-6-i' => 'ISO-8859-6-I', 'iso-8859-6-e' => 'ISO-8859-6-E', 'iso-8859-7' => 'ISO-8859-7', 'iso-8859-8' => 'ISO-8859-8', 'iso-8859-8-i' => 'ISO-8859-8-I', 'iso-8859-8-e' => 'ISO-8859-8-E', 'iso-8859-9' => 'ISO-8859-9', 'iso-8859-10' => 'ISO-8859-10', 'iso-8859-11' => 'ISO-8859-11', 'iso-8859-13' => 'ISO-8859-13', 'iso-8859-14' => 'ISO-8859-14', 'iso-8859-15' => 'ISO-8859-15', 'iso-8859-16' => 'ISO-8859-16', 'iso-ir-111' => 'ISO-IR-111', 'iso-2022-cn' => 'ISO-2022-CN', 'iso-2022-cn-ext' => 'ISO-2022-CN', 'iso-2022-kr' => 'ISO-2022-KR', 'iso-2022-jp' => 'ISO-2022-JP', 'utf-16be' => 'UTF-16BE', 'utf-16le' => 'UTF-16LE', 'utf-16' => 'UTF-16', 'windows-1250' => 'windows-1250', 'windows-1251' => 'windows-1251', 'windows-1252' => 'windows-1252', 'windows-1253' => 'windows-1253', 'windows-1254' => 'windows-1254', 'windows-1255' => 'windows-1255', 'windows-1256' => 'windows-1256', 'windows-1257' => 'windows-1257', 'windows-1258' => 'windows-1258', 'ibm866' => 'IBM866', 'ibm850' => 'IBM850', 'ibm852' => 'IBM852', 'ibm855' => 'IBM855', 'ibm857' => 'IBM857', 'ibm862' => 'IBM862', 'ibm864' => 'IBM864', 'utf-8' => 'UTF-8', 'utf-7' => 'UTF-7', 'shift_jis' => 'Shift_JIS', 'big5' => 'Big5', 'euc-jp' => 'EUC-JP', 'euc-kr' => 'EUC-KR', 'gb2312' => 'GB2312', 'gb18030' => 'gb18030', 'viscii' => 'VISCII', 'koi8-r' => 'KOI8-R', 'koi8_r' => 'KOI8-R', 'cskoi8r' => 'KOI8-R', 'koi' => 'KOI8-R', 'koi8' => 'KOI8-R', 'koi8-u' => 'KOI8-U', 'tis-620' => 'TIS-620', 't.61-8bit' => 'T.61-8bit', 'hz-gb-2312' => 'HZ-GB-2312', 'big5-hkscs' => 'Big5-HKSCS', 'gbk' => 'gbk', 'cns11643' => 'x-euc-tw', 'x-imap4-modified-utf7' => 'x-imap4-modified-utf7', 'x-euc-tw' => 'x-euc-tw', 'x-mac-ce' => 'x-mac-ce', 'x-mac-turkish' => 'x-mac-turkish', 'x-mac-greek' => 'x-mac-greek', 'x-mac-icelandic' => 'x-mac-icelandic', 'x-mac-croatian' => 'x-mac-croatian', 'x-mac-romanian' => 'x-mac-romanian', 'x-mac-cyrillic' => 'x-mac-cyrillic', 'x-mac-ukrainian' => 'x-mac-cyrillic', 'x-mac-hebrew' => 'x-mac-hebrew', 'x-mac-arabic' => 'x-mac-arabic', 'x-mac-farsi' => 'x-mac-farsi', 'x-mac-devanagari' => 'x-mac-devanagari', 'x-mac-gujarati' => 'x-mac-gujarati', 'x-mac-gurmukhi' => 'x-mac-gurmukhi', 'armscii-8' => 'armscii-8', 'x-viet-tcvn5712' => 'x-viet-tcvn5712', 'x-viet-vps' => 'x-viet-vps', 'iso-10646-ucs-2' => 'UTF-16BE', 'x-iso-10646-ucs-2-be' => 'UTF-16BE', 'x-iso-10646-ucs-2-le' => 'UTF-16LE', 'x-user-defined' => 'x-user-defined', 'x-johab' => 'x-johab', 'latin1' => 'ISO-8859-1', 'iso_8859-1' => 'ISO-8859-1', 'iso8859-1' => 'ISO-8859-1', 'iso8859-2' => 'ISO-8859-2', 'iso8859-3' => 'ISO-8859-3', 'iso8859-4' => 'ISO-8859-4', 'iso8859-5' => 'ISO-8859-5', 'iso8859-6' => 'ISO-8859-6', 'iso8859-7' => 'ISO-8859-7', 'iso8859-8' => 'ISO-8859-8', 'iso8859-9' => 'ISO-8859-9', 'iso8859-10' => 'ISO-8859-10', 'iso8859-11' => 'ISO-8859-11', 'iso8859-13' => 'ISO-8859-13', 'iso8859-14' => 'ISO-8859-14', 'iso8859-15' => 'ISO-8859-15', 'iso_8859-1:1987' => 'ISO-8859-1', 'iso-ir-100' => 'ISO-8859-1', 'l1' => 'ISO-8859-1', 'ibm819' => 'ISO-8859-1', 'cp819' => 'ISO-8859-1', 'csisolatin1' => 'ISO-8859-1', 'latin2' => 'ISO-8859-2', 'iso_8859-2' => 'ISO-8859-2', 'iso_8859-2:1987' => 'ISO-8859-2', 'iso-ir-101' => 'ISO-8859-2', 'l2' => 'ISO-8859-2', 'csisolatin2' => 'ISO-8859-2', 'latin3' => 'ISO-8859-3', 'iso_8859-3' => 'ISO-8859-3', 'iso_8859-3:1988' => 'ISO-8859-3', 'iso-ir-109' => 'ISO-8859-3', 'l3' => 'ISO-8859-3', 'csisolatin3' => 'ISO-8859-3', 'latin4' => 'ISO-8859-4', 'iso_8859-4' => 'ISO-8859-4', 'iso_8859-4:1988' => 'ISO-8859-4', 'iso-ir-110' => 'ISO-8859-4', 'l4' => 'ISO-8859-4', 'csisolatin4' => 'ISO-8859-4', 'cyrillic' => 'ISO-8859-5', 'iso_8859-5' => 'ISO-8859-5', 'iso_8859-5:1988' => 'ISO-8859-5', 'iso-ir-144' => 'ISO-8859-5', 'csisolatincyrillic' => 'ISO-8859-5', 'arabic' => 'ISO-8859-6', 'iso_8859-6' => 'ISO-8859-6', 'iso_8859-6:1987' => 'ISO-8859-6', 'iso-ir-127' => 'ISO-8859-6', 'ecma-114' => 'ISO-8859-6', 'asmo-708' => 'ISO-8859-6', 'csisolatinarabic' => 'ISO-8859-6', 'csiso88596i' => 'ISO-8859-6-I', 'csiso88596e' => 'ISO-8859-6-E', 'greek' => 'ISO-8859-7', 'greek8' => 'ISO-8859-7', 'sun_eu_greek' => 'ISO-8859-7', 'iso_8859-7' => 'ISO-8859-7', 'iso_8859-7:1987' => 'ISO-8859-7', 'iso-ir-126' => 'ISO-8859-7', 'elot_928' => 'ISO-8859-7', 'ecma-118' => 'ISO-8859-7', 'csisolatingreek' => 'ISO-8859-7', 'hebrew' => 'ISO-8859-8', 'iso_8859-8' => 'ISO-8859-8', 'visual' => 'ISO-8859-8', 'iso_8859-8:1988' => 'ISO-8859-8', 'iso-ir-138' => 'ISO-8859-8', 'csisolatinhebrew' => 'ISO-8859-8', 'csiso88598i' => 'ISO-8859-8-I', 'iso-8859-8i' => 'ISO-8859-8-I', 'logical' => 'ISO-8859-8-I', 'csiso88598e' => 'ISO-8859-8-E', 'latin5' => 'ISO-8859-9', 'iso_8859-9' => 'ISO-8859-9', 'iso_8859-9:1989' => 'ISO-8859-9', 'iso-ir-148' => 'ISO-8859-9', 'l5' => 'ISO-8859-9', 'csisolatin5' => 'ISO-8859-9', 'unicode-1-1-utf-8' => 'UTF-8', 'utf8' => 'UTF-8', 'x-sjis' => 'Shift_JIS', 'shift-jis' => 'Shift_JIS', 'ms_kanji' => 'Shift_JIS', 'csshiftjis' => 'Shift_JIS', 'windows-31j' => 'Shift_JIS', 'cp932' => 'Shift_JIS', 'sjis' => 'Shift_JIS', 'cseucpkdfmtjapanese' => 'EUC-JP', 'x-euc-jp' => 'EUC-JP', 'csiso2022jp' => 'ISO-2022-JP', 'iso-2022-jp-2' => 'ISO-2022-JP', 'csiso2022jp2' => 'ISO-2022-JP', 'csbig5' => 'Big5', 'cn-big5' => 'Big5', 'x-x-big5' => 'Big5', 'zh_tw-big5' => 'Big5', 'cseuckr' => 'EUC-KR', 'ks_c_5601-1987' => 'EUC-KR', 'iso-ir-149' => 'EUC-KR', 'ks_c_5601-1989' => 'EUC-KR', 'ksc_5601' => 'EUC-KR', 'ksc5601' => 'EUC-KR', 'korean' => 'EUC-KR', 'csksc56011987' => 'EUC-KR', '5601' => 'EUC-KR', 'windows-949' => 'EUC-KR', 'gb_2312-80' => 'GB2312', 'iso-ir-58' => 'GB2312', 'chinese' => 'GB2312', 'csiso58gb231280' => 'GB2312', 'csgb2312' => 'GB2312', 'zh_cn.euc' => 'GB2312', 'gb_2312' => 'GB2312', 'x-cp1250' => 'windows-1250', 'x-cp1251' => 'windows-1251', 'x-cp1252' => 'windows-1252', 'x-cp1253' => 'windows-1253', 'x-cp1254' => 'windows-1254', 'x-cp1255' => 'windows-1255', 'x-cp1256' => 'windows-1256', 'x-cp1257' => 'windows-1257', 'x-cp1258' => 'windows-1258', 'windows-874' => 'windows-874', 'ibm874' => 'windows-874', 'dos-874' => 'windows-874', 'macintosh' => 'macintosh', 'x-mac-roman' => 'macintosh', 'mac' => 'macintosh', 'csmacintosh' => 'macintosh', 'cp866' => 'IBM866', 'cp-866' => 'IBM866', '866' => 'IBM866', 'csibm866' => 'IBM866', 'cp850' => 'IBM850', '850' => 'IBM850', 'csibm850' => 'IBM850', 'cp852' => 'IBM852', '852' => 'IBM852', 'csibm852' => 'IBM852', 'cp855' => 'IBM855', '855' => 'IBM855', 'csibm855' => 'IBM855', 'cp857' => 'IBM857', '857' => 'IBM857', 'csibm857' => 'IBM857', 'cp862' => 'IBM862', '862' => 'IBM862', 'csibm862' => 'IBM862', 'cp864' => 'IBM864', '864' => 'IBM864', 'csibm864' => 'IBM864', 'ibm-864' => 'IBM864', 't.61' => 'T.61-8bit', 'iso-ir-103' => 'T.61-8bit', 'csiso103t618bit' => 'T.61-8bit', 'x-unicode-2-0-utf-7' => 'UTF-7', 'unicode-2-0-utf-7' => 'UTF-7', 'unicode-1-1-utf-7' => 'UTF-7', 'csunicode11utf7' => 'UTF-7', 'csunicode' => 'UTF-16BE', 'csunicode11' => 'UTF-16BE', 'iso-10646-ucs-basic' => 'UTF-16BE', 'csunicodeascii' => 'UTF-16BE', 'iso-10646-unicode-latin1' => 'UTF-16BE', 'csunicodelatin1' => 'UTF-16BE', 'iso-10646' => 'UTF-16BE', 'iso-10646-j-1' => 'UTF-16BE', 'latin6' => 'ISO-8859-10', 'iso-ir-157' => 'ISO-8859-10', 'l6' => 'ISO-8859-10', 'csisolatin6' => 'ISO-8859-10', 'iso_8859-15' => 'ISO-8859-15', 'csisolatin9' => 'ISO-8859-15', 'l9' => 'ISO-8859-15', 'ecma-cyrillic' => 'ISO-IR-111', 'csiso111ecmacyrillic' => 'ISO-IR-111', 'csiso2022kr' => 'ISO-2022-KR', 'csviscii' => 'VISCII', 'zh_tw-euc' => 'x-euc-tw', 'iso88591' => 'ISO-8859-1', 'iso88592' => 'ISO-8859-2', 'iso88593' => 'ISO-8859-3', 'iso88594' => 'ISO-8859-4', 'iso88595' => 'ISO-8859-5', 'iso88596' => 'ISO-8859-6', 'iso88597' => 'ISO-8859-7', 'iso88598' => 'ISO-8859-8', 'iso88599' => 'ISO-8859-9', 'iso885910' => 'ISO-8859-10', 'iso885911' => 'ISO-8859-11', 'iso885912' => 'ISO-8859-12', 'iso885913' => 'ISO-8859-13', 'iso885914' => 'ISO-8859-14', 'iso885915' => 'ISO-8859-15', 'tis620' => 'TIS-620', 'cp1250' => 'windows-1250', 'cp1251' => 'windows-1251', 'cp1252' => 'windows-1252', 'cp1253' => 'windows-1253', 'cp1254' => 'windows-1254', 'cp1255' => 'windows-1255', 'cp1256' => 'windows-1256', 'cp1257' => 'windows-1257', 'cp1258' => 'windows-1258', 'x-gbk' => 'gbk', 'windows-936' => 'gbk', 'ansi-1251' => 'windows-1251', ]; /** * Decode the string from charset. * * @param string $encodedString * @param string $charset * @return string */ public function decodeCharset($encodedString, $charset) { if (strtolower($charset) == 'utf-8' || strtolower($charset) == 'us-ascii') { return $encodedString; } try { return iconv($this->getCharsetAlias($charset), 'UTF-8//TRANSLIT', $encodedString); } catch (\Exception $e) { return iconv($this->getCharsetAlias($charset), 'UTF-8//IGNORE', $encodedString); } } /** * Get charset alias. * * @param string $charset. * @return string */ public function getCharsetAlias($charset) { $charset = strtolower($charset); if (array_key_exists($charset, $this->charsetAlias)) { return $this->charsetAlias[$charset]; } return null; } } ================================================ FILE: packages/Webkul/Email/src/Helpers/Contracts/CharsetManager.php ================================================ '; } else { $fulltag = '<'.$tagname; if (is_array($attary) && count($attary)) { $atts = []; foreach ($attary as $attname => $attvalue) { array_push($atts, "$attname=$attvalue"); } $fulltag .= ' '.implode(' ', $atts); } if ($tagtype == 3) { $fulltag .= ' /'; } $fulltag .= '>'; } return $fulltag; } /** * A small helper function to use with array_walk. Modifies a by-ref * value and makes it lowercase. * * @param string $val * @return void */ public function tln_casenormalize(&$val) { $val = strtolower($val); } /** * This function skips any whitespace from the current position within * a string and to the next non-whitespace value. * * @param string $body * @param int $offset * @return int */ public function tln_skipspace($body, $offset) { preg_match('/^(\s*)/s', substr($body, $offset), $matches); try { if (! empty($matches[1])) { $count = strlen($matches[1]); $offset += $count; } } catch (\Exception $e) { } return $offset; } /** * This function looks for the next character within a string. It's * really just a glorified "strpos", except it catches the failures * nicely. * * @param string $body * @param int $offset * @param string $needle * @return int */ public function tln_findnxstr($body, $offset, $needle) { $pos = strpos($body, $needle, $offset); if ($pos === false) { $pos = strlen($body); } return $pos; } /** * This function takes a PCRE-style regexp and tries to match it * within the string. * * @param string $body * @param int $offset * @param string $reg * @return array|bool */ public function tln_findnxreg($body, $offset, $reg) { $matches = $retarr = []; $preg_rule = '%^(.*?)('.$reg.')%s'; preg_match($preg_rule, substr($body, $offset), $matches); if (! isset($matches[0]) || ! $matches[0]) { $retarr = false; } else { $retarr[0] = $offset + strlen($matches[1]); $retarr[1] = $matches[1]; $retarr[2] = $matches[2]; } return $retarr; } /** * This function looks for the next tag. * * @param string $body * @param int $offset * @return array|bool */ public function tln_getnxtag($body, $offset) { if ($offset > strlen($body)) { return false; } $lt = $this->tln_findnxstr($body, $offset, '<'); if ($lt == strlen($body)) { return false; } /** * We are here: * blah blah * \---------^ */ $pos = $this->tln_skipspace($body, $lt + 1); if ($pos >= strlen($body)) { return [false, false, false, $lt, strlen($body)]; } /** * There are 3 kinds of tags: * 1. Opening tag, e.g.: * * 2. Closing tag, e.g.: * * 3. XHTML-style content-less tag, e.g.: * */ switch (substr($body, $pos, 1)) { case '/': $tagtype = 2; $pos++; break; case '!': /** * A comment or an SGML declaration. */ if (substr($body, $pos + 1, 2) == '--') { $gt = strpos($body, '-->', $pos); if ($gt === false) { $gt = strlen($body); } else { $gt += 2; } return [false, false, false, $lt, $gt]; } else { $gt = $this->tln_findnxstr($body, $pos, '>'); return [false, false, false, $lt, $gt]; } break; default: $tagtype = 1; break; } /** * Look for next [\W-_], which will indicate the end of the tag name. */ $regary = $this->tln_findnxreg($body, $pos, '[^\w\-_]'); if ($regary == false) { return [false, false, false, $lt, strlen($body)]; } [$pos, $tagname, $match] = $regary; $tagname = strtolower($tagname); /** * $match can be either of these: * '>' indicating the end of the tag entirely. * '\s' indicating the end of the tag name. * '/' indicating that this is type-3 xhtml tag. * * Whatever else we find there indicates an invalid tag. */ switch ($match) { case '/': /** * This is an xhtml-style tag with a closing / at the * end, like so: . Check if it's followed * by the closing bracket. If not, then this tag is invalid */ if (substr($body, $pos, 2) == '/>') { $pos++; $tagtype = 3; } else { $gt = $this->tln_findnxstr($body, $pos, '>'); $retary = [false, false, false, $lt, $gt]; return $retary; } // intentional fall-through case '>': return [$tagname, false, $tagtype, $lt, $pos]; default: /** * Check if it's whitespace. */ if (! preg_match('/\s/', $match)) { /** * This is an invalid tag! Look for the next closing ">". */ $gt = $this->tln_findnxstr($body, $lt, '>'); return [false, false, false, $lt, $gt]; } break; } /** * At this point we're here: * * \-------^ * * At this point we loop in order to find all attributes. */ $attary = []; while ($pos <= strlen($body)) { $pos = $this->tln_skipspace($body, $pos); if ($pos == strlen($body)) { /** * Non-closed tag. */ return [false, false, false, $lt, $pos]; } /** * See if we arrived at a ">" or "/>", which means that we reached * the end of the tag. */ $matches = []; if (preg_match('%^(\s*)(>|/>)%s', substr($body, $pos), $matches)) { /** * Yep. So we did. */ $pos += strlen($matches[1]); if ($matches[2] == '/>') { $tagtype = 3; $pos++; } return [$tagname, $attary, $tagtype, $lt, $pos]; } /** * There are several types of attributes, with optional * [:space:] between members. * Type 1: * attrname[:space:]=[:space:]'CDATA' * Type 2: * attrname[:space:]=[:space:]"CDATA" * Type 3: * attr[:space:]=[:space:]CDATA * Type 4: * attrname * * We leave types 1 and 2 the same, type 3 we check for * '"' and convert to """ if needed, then wrap in * double quotes. Type 4 we convert into: * attrname="yes". */ $regary = $this->tln_findnxreg($body, $pos, '[^\w\-_]'); if ($regary == false) { /** * Looks like body ended before the end of tag. */ return [false, false, false, $lt, strlen($body)]; } [$pos, $attname, $match] = $regary; $attname = strtolower($attname); /** * We arrived at the end of attribute name. Several things possible * here: * '>' means the end of the tag and this is attribute type 4 * '/' if followed by '>' means the same thing as above * '\s' means a lot of things -- look what it's followed by. * anything else means the attribute is invalid. */ switch ($match) { case '/': /** * This is an xhtml-style tag with a closing / at the * end, like so: . Check if it's followed * by the closing bracket. If not, then this tag is invalid */ if (substr($body, $pos, 2) == '/>') { $pos++; $tagtype = 3; } else { $gt = $this->tln_findnxstr($body, $pos, '>'); $retary = [false, false, false, $lt, $gt]; return $retary; } // intentional fall-through case '>': $attary[$attname] = '"yes"'; return [$tagname, $attary, $tagtype, $lt, $pos]; break; default: /** * Skip whitespace and see what we arrive at. */ $pos = $this->tln_skipspace($body, $pos); $char = substr($body, $pos, 1); /** * Two things are valid here: * '=' means this is attribute type 1 2 or 3. * \w means this was attribute type 4. * anything else we ignore and re-loop. End of tag and * invalid stuff will be caught by our checks at the beginning * of the loop. */ if ($char == '=') { $pos++; $pos = $this->tln_skipspace($body, $pos); /** * Here are 3 possibilities: * "'" attribute type 1 * '"' attribute type 2 * everything else is the content of tag type 3 */ $quot = substr($body, $pos, 1); if ($quot == '\'') { $regary = $this->tln_findnxreg($body, $pos + 1, '\''); if ($regary == false) { return [false, false, false, $lt, strlen($body)]; } [$pos, $attval, $match] = $regary; $pos++; $attary[$attname] = '\''.$attval.'\''; } elseif ($quot == '"') { $regary = $this->tln_findnxreg($body, $pos + 1, '\"'); if ($regary == false) { return [false, false, false, $lt, strlen($body)]; } [$pos, $attval, $match] = $regary; $pos++; $attary[$attname] = '"'.$attval.'"'; } else { /** * These are hateful. Look for \s, or >. */ $regary = $this->tln_findnxreg($body, $pos, '[\s>]'); if ($regary == false) { return [false, false, false, $lt, strlen($body)]; } [$pos, $attval, $match] = $regary; $attval = preg_replace('/\"/s', '"', $attval); $attary[$attname] = '"'.$attval.'"'; } } elseif (preg_match('|[\w/>]|', $char)) { $attary[$attname] = '"yes"'; } else { $gt = $this->tln_findnxstr($body, $pos, '>'); return [false, false, false, $lt, $gt]; } break; } } /** * The fact that we got here indicates that the tag end was never * found. Return invalid tag indication so it gets stripped. */ return [false, false, false, $lt, strlen($body)]; } /** * Translates entities into literal values so they can be checked. * * @param string $attvalue * @param string $regex * @param bool $hex * @return bool */ public function tln_deent(&$attvalue, $regex, $hex = false) { preg_match_all($regex, $attvalue, $matches); if (is_array($matches) && count($matches[0]) > 0) { $repl = []; for ($i = 0; $i < count($matches[0]); $i++) { $numval = $matches[1][$i]; if ($hex) { $numval = hexdec($numval); } $repl[$matches[0][$i]] = chr($numval); } $attvalue = strtr($attvalue, $repl); return true; } else { return false; } } /** * This function checks attribute values for entity-encoded values * and returns them translated into 8-bit strings so we can run * checks on them. * * @param string $attvalue * @return void */ public function tln_defang(&$attvalue) { /** * Skip this if there aren't ampersands or backslashes. */ if (strpos($attvalue, '&') === false && strpos($attvalue, '\\') === false ) { return; } do { $m = false; $m = $m || $this->tln_deent($attvalue, '/\�*(\d+);*/s'); $m = $m || $this->tln_deent($attvalue, '/\�*((\d|[a-f])+);*/si', true); $m = $m || $this->tln_deent($attvalue, '/\\\\(\d+)/s', true); } while ($m == true); $attvalue = stripslashes($attvalue); } /** * Kill any tabs, newlines, or carriage returns. Our friends the * makers of the browser with 95% market value decided that it'd * be funny to make "java[tab]script" be just as good as "javascript". * * @param string $attvalue * @return void */ public function tln_unspace(&$attvalue) { if (strcspn($attvalue, "\t\r\n\0 ") != strlen($attvalue)) { $attvalue = str_replace( ["\t", "\r", "\n", "\0", ' '], ['', '', '', '', ''], $attvalue ); } } /** * This function runs various checks against the attributes. * * @param string $tagname * @param array $attary * @param array $rm_attnames * @param array $bad_attvals * @param array $add_attr_to_tag * @param string $trans_image_path * @param bool $block_external_images * @return array with modified attributes. */ public function tln_fixatts( $tagname, $attary, $rm_attnames, $bad_attvals, $add_attr_to_tag, $trans_image_path, $block_external_images ) { /** * Convert to array if is not. */ $attary = is_array($attary) ? $attary : []; foreach ($attary as $attname => $attvalue) { /** * See if this attribute should be removed. */ foreach ($rm_attnames as $matchtag => $matchattrs) { if (preg_match($matchtag, $tagname)) { foreach ($matchattrs as $matchattr) { if (preg_match($matchattr, $attname)) { unset($attary[$attname]); continue 2; } } } } $this->tln_defang($attvalue); $this->tln_unspace($attvalue); /** * Now let's run checks on the attvalues. * I don't expect anyone to comprehend this. If you do, * get in touch with me so I can drive to where you live and * shake your hand personally. :) */ foreach ($bad_attvals as $matchtag => $matchattrs) { if (preg_match($matchtag, $tagname)) { foreach ($matchattrs as $matchattr => $valary) { if (preg_match($matchattr, $attname)) { [$valmatch, $valrepl] = $valary; $newvalue = preg_replace($valmatch, $valrepl, $attvalue); if ($newvalue != $attvalue) { $attary[$attname] = $newvalue; $attvalue = $newvalue; } } } } } } /** * See if we need to append any attributes to this tag. */ foreach ($add_attr_to_tag as $matchtag => $addattary) { if (preg_match($matchtag, $tagname)) { $attary = array_merge($attary, $addattary); } } return $attary; } /** * Fix url. * * @return void */ public function tln_fixurl($attname, &$attvalue, $trans_image_path, $block_external_images) { $sQuote = '"'; $attvalue = trim($attvalue); if ($attvalue && ($attvalue[0] == '"' || $attvalue[0] == "'")) { // remove the double quotes $sQuote = $attvalue[0]; $attvalue = trim(substr($attvalue, 1, -1)); } /** * Replace empty src tags with the blank image. src is only used * for frames, images, and image inputs. Doing a replace should * not affect them working as should be, however it will stop * IE from being kicked off when src for img tags are not set. */ if ($attvalue == '') { $attvalue = $sQuote.$trans_image_path.$sQuote; } else { // first, disallow 8 bit characters and control characters if (preg_match('/[\0-\37\200-\377]+/', $attvalue)) { switch ($attname) { case 'href': $attvalue = $sQuote.'http://invalid-stuff-detected.example.com'.$sQuote; break; default: $attvalue = $sQuote.$trans_image_path.$sQuote; break; } } else { $aUrl = parse_url($attvalue); if (isset($aUrl['scheme'])) { switch (strtolower($aUrl['scheme'])) { case 'mailto': case 'http': case 'https': case 'ftp': if ($attname != 'href') { if ($block_external_images == true) { $attvalue = $sQuote.$trans_image_path.$sQuote; } else { if (! isset($aUrl['path'])) { $attvalue = $sQuote.$trans_image_path.$sQuote; } } } else { $attvalue = $sQuote.$attvalue.$sQuote; } break; case 'outbind': $attvalue = $sQuote.$attvalue.$sQuote; break; case 'cid': $attvalue = $sQuote.$attvalue.$sQuote; break; default: $attvalue = $sQuote.$trans_image_path.$sQuote; break; } } else { if (! isset($aUrl['path']) || $aUrl['path'] != $trans_image_path) { $$attvalue = $sQuote.$trans_image_path.$sQuote; } } } } } /** * Fix style. * * @return void */ public function tln_fixstyle($body, $pos, $trans_image_path, $block_external_images) { $me = 'tln_fixstyle'; $content = ''; $sToken = ''; $bSucces = false; $bEndTag = false; for ($i = $pos,$iCount = strlen($body); $i < $iCount; $i++) { $char = $body[$i]; switch ($char) { case '<': $sToken = $char; break; case '/': if ($sToken == '<') { $sToken .= $char; $bEndTag = true; } else { $content .= $char; } break; case '>': if ($bEndTag) { $sToken .= $char; if (preg_match('/\<\/\s*style\s*\>/i', $sToken, $aMatch)) { $newpos = $i + 1; $bSucces = true; break 2; } else { $content .= $sToken; } $bEndTag = false; } else { $content .= $char; } break; case '!': if ($sToken == '<') { if (isset($body[$i + 2]) && substr($body, $i, 3) == '!--') { $i = strpos($body, '-->', $i + 3); if (! $i) { $i = strlen($body); } $sToken = ''; } } else { $content .= $char; } break; default: if ($bEndTag) { $sToken .= $char; } else { $content .= $char; } break; } } if (! $bSucces) { return [false, strlen($body)]; } /** * First look for general BODY style declaration, which would be * like so: * body {background: blah-blah} * and change it to .bodyclass so we can just assign it to a
    */ $content = preg_replace("|body(\s*\{.*?\})|si", '.bodyclass\\1', $content); $trans_image_path = $trans_image_path; // first check for 8bit sequences and disallowed control characters if (preg_match('/[\16-\37\200-\377]+/', $content)) { $content = ''; return [$content, $newpos]; } // remove @import line $content = preg_replace("/^\s*(@import.*)$/mi", "\n\n", $content); $content = preg_replace('/(\\\\)?u(\\\\)?r(\\\\)?l(\\\\)?/i', 'url', $content); preg_match_all("/url\s*\((.+)\)/si", $content, $aMatch); if (count($aMatch)) { $aValue = $aReplace = []; foreach ($aMatch[1] as $sMatch) { $urlvalue = $sMatch; $this->tln_fixurl('style', $urlvalue, $trans_image_path, $block_external_images); $aValue[] = $sMatch; $aReplace[] = $urlvalue; } $content = str_replace($aValue, $aReplace, $content); } /** * Remove any backslashes, entities, and extraneous whitespace. */ $contentTemp = $content; $this->tln_defang($contentTemp); $this->tln_unspace($contentTemp); $match = ['/\/\*.*\*\//', '/expression/i', '/behaviou*r/i', '/binding/i', '/include-source/i', '/javascript/i', '/script/i', '/position/i']; $replace = ['', 'idiocy', 'idiocy', 'idiocy', 'idiocy', 'idiocy', 'idiocy', '']; $contentNew = preg_replace($match, $replace, $contentTemp); if ($contentNew !== $contentTemp) { $content = $contentNew; } return [$content, $newpos]; } /** * Body to div. * * @return void */ public function tln_body2div($attary, $trans_image_path) { $me = 'tln_body2div'; $divattary = ['class' => "'bodyclass'"]; $has_bgc_stl = $has_txt_stl = false; $styledef = ''; if (is_array($attary) && count($attary) > 0) { foreach ($attary as $attname => $attvalue) { $quotchar = substr($attvalue, 0, 1); $attvalue = str_replace($quotchar, '', $attvalue); switch ($attname) { case 'background': $styledef .= "background-image: url('$trans_image_path'); "; break; case 'bgcolor': $has_bgc_stl = true; $styledef .= "background-color: $attvalue; "; break; case 'text': $has_txt_stl = true; $styledef .= "color: $attvalue; "; break; } } // Outlook defines a white bgcolor and no text color. This can lead to white text on a white bg with certain themes. if ($has_bgc_stl && ! $has_txt_stl) { $styledef .= 'color: #000000; '; } if (strlen($styledef) > 0) { $divattary['style'] = "\"$styledef\""; } } return $divattary; } /** * Sanitize. * * @param string $body * @param array $tag_list * @param array $rm_tags_with_content * @param array $self_closing_tags * @param bool $force_tag_closing * @param array $rm_attnames * @param array $bad_attvals * @param array $add_attr_to_tag * @param string $trans_image_path * @param bool $block_external_images * @return string */ public function tln_sanitize( $body, $tag_list, $rm_tags_with_content, $self_closing_tags, $force_tag_closing, $rm_attnames, $bad_attvals, $add_attr_to_tag, $trans_image_path, $block_external_images ) { /** * Normalize rm_tags and rm_tags_with_content. */ $rm_tags = array_shift($tag_list); @array_walk($tag_list, [$this, 'tln_casenormalize']); @array_walk($rm_tags_with_content, [$this, 'tln_casenormalize']); @array_walk($self_closing_tags, [$this, 'tln_casenormalize']); /** * See if tag_list is of tags to remove or tags to allow. * false means remove these tags * true means allow these tags */ $curpos = 0; $open_tags = []; $trusted = ''; $skip_content = false; /** * Take care of netscape's stupid javascript entities like * &{alert('boo')}; */ $body = preg_replace('/&(\{.*?\};)/si', '&\\1', $body); while (($curtag = $this->tln_getnxtag($body, $curpos)) != false) { [$tagname, $attary, $tagtype, $lt, $gt] = $curtag; $free_content = substr($body, $curpos, $lt - $curpos); /** * Take care of {!! view_render_event('webform.layout.head') !!} {!! view_render_event('webform.layout.body.before') !!}
    {!! view_render_event('webform.layout.content.before') !!} {{ $slot }} {!! view_render_event('webform.layout.content.after') !!}
    {!! view_render_event('webform.layout.body.after') !!} @stack('scripts') {!! view_render_event('webform.layout.vue-app-mount.before') !!} {!! view_render_event('webform.layout.vue-app-mount.after') !!} ================================================ FILE: packages/Webkul/WebForm/src/Resources/views/components/spinner/index.blade.php ================================================ @props(['color' => 'currentColor']) ================================================ FILE: packages/Webkul/WebForm/src/Resources/views/settings/web-forms/controls.blade.php ================================================ @foreach ($webForm->attributes as $attribute) @php $parentAttribute = $attribute->attribute; $fieldName = $parentAttribute->entity_type . '[' . $parentAttribute->code . ']'; $validations = $attribute->is_required ? 'required' : ''; @endphp {{ $attribute->name ?? $parentAttribute->name }} @switch($parentAttribute->type) @case('text') @break @case('price') @break @case('email') @break @case('checkbox') @php $options = $parentAttribute->lookup_type ? app('Webkul\Attribute\Repositories\AttributeRepository')->getLookUpOptions($parentAttribute->lookup_type) : $parentAttribute->options()->orderBy('sort_order')->get(); @endphp @foreach ($options as $option) @endforeach @case('file') @case('image') @break; @case('phone') @break @case('date') @break @case('datetime') @break @case('select') @case('lookup') @php $options = $parentAttribute->lookup_type ? app('Webkul\Attribute\Repositories\AttributeRepository')->getLookUpOptions($parentAttribute->lookup_type) : $parentAttribute->options()->orderBy('sort_order')->get(); @endphp @foreach ($options as $option) @endforeach @break @case('multiselect') @php $options = $parentAttribute->lookup_type ? app('Webkul\Attribute\Repositories\AttributeRepository')->getLookUpOptions($parentAttribute->lookup_type) : $parentAttribute->options()->orderBy('sort_order')->get(); @endphp @foreach ($options as $option) @endforeach @break @case('checkbox')
    @php $options = $parentAttribute->lookup_type ? app('Webkul\Attribute\Repositories\AttributeRepository')->getLookUpOptions($parentAttribute->lookup_type) : $parentAttribute->options()->orderBy('sort_order')->get(); @endphp @foreach ($options as $option) {{ $option->name }} @endforeach

    @break @case('boolean') @break @endswitch
    @endforeach ================================================ FILE: packages/Webkul/WebForm/src/Resources/views/settings/web-forms/embed.blade.php ================================================ (function() { document.write(`{!! view('web_form::settings.web-forms.preview', compact('webForm'))->render() !!}`.replaceAll('$', '\$')); })(); ================================================ FILE: packages/Webkul/WebForm/src/Resources/views/settings/web-forms/preview.blade.php ================================================ {{ strip_tags($webForm->title) }}
    @pushOnce('scripts') @endPushOnce
    ================================================ FILE: packages/Webkul/WebForm/src/Routes/routes.php ================================================ middleware(['web', 'admin_locale'])->prefix('web-forms')->group(function () { Route::get('forms/{id}/form.js', 'formJS')->name('admin.settings.web_forms.form_js'); Route::get('forms/{id}/form.html', 'preview')->name('admin.settings.web_forms.preview'); Route::post('forms/{id}', 'formStore')->name('admin.settings.web_forms.form_store'); Route::group(['middleware' => ['user']], function () { Route::get('form/{id}/form.html', 'view')->name('admin.settings.web_forms.view'); }); }); ================================================ FILE: packages/Webkul/WebForm/src/Rules/PhoneNumber.php ================================================ { const envDir = "../../../"; Object.assign(process.env, loadEnv(mode, envDir)); return { build: { emptyOutDir: true, }, envDir, server: { host: process.env.VITE_HOST || "localhost", port: process.env.VITE_PORT || 5174, }, plugins: [ vue(), laravel({ hotFile: "../../../public/webform-vite.hot", publicDirectory: "../../../public", buildDirectory: "webform/build", input: [ "src/Resources/assets/css/app.css", "src/Resources/assets/js/app.js", ], refresh: true, }), ], experimental: { renderBuiltUrl(filename, { hostId, hostType, type }) { if (hostType === "css") { return path.basename(filename); } }, }, }; }); ================================================ FILE: phpunit.xml ================================================ ./tests/Unit ./tests/Feature ./app ================================================ FILE: pint.json ================================================ { "preset": "laravel", "rules": { "binary_operator_spaces": { "operators": { "=>": "single_space" } } } } ================================================ FILE: public/.htaccess ================================================ # Rewrite Module Options -MultiViews -Indexes RewriteEngine On # Handle Authorization Header RewriteCond %{HTTP:Authorization} . RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] # Redirect Trailing Slashes If Not A Folder... RewriteCond %{REQUEST_FILENAME} !-d RewriteCond %{REQUEST_URI} (.+)/$ RewriteRule ^ %1 [L,R=301] # Handle Front Controller... RewriteCond %{REQUEST_FILENAME} !-d RewriteCond %{REQUEST_FILENAME} !-f RewriteRule ^ index.php [L] ================================================ FILE: public/admin/build/assets/app-B1rBjssc.js ================================================ const xE="modulepreload",NE=function(e,t){return new URL(e,t).href},xd={},He=function(t,n,r){let i=Promise.resolve();if(n&&n.length>0){const o=document.getElementsByTagName("link"),a=document.querySelector("meta[property=csp-nonce]"),l=(a==null?void 0:a.nonce)||(a==null?void 0:a.getAttribute("nonce"));i=Promise.allSettled(n.map(c=>{if(c=NE(c,r),c in xd)return;xd[c]=!0;const u=c.endsWith(".css"),f=u?'[rel="stylesheet"]':"";if(!!r)for(let m=o.length-1;m>=0;m--){const g=o[m];if(g.href===c&&(!u||g.rel==="stylesheet"))return}else if(document.querySelector(`link[href="${c}"]${f}`))return;const p=document.createElement("link");if(p.rel=u?"stylesheet":xE,u||(p.as="script"),p.crossOrigin="",p.href=c,l&&p.setAttribute("nonce",l),document.head.appendChild(p),u)return new Promise((m,g)=>{p.addEventListener("load",m),p.addEventListener("error",()=>g(new Error(`Unable to preload CSS for ${c}`)))})}))}function s(o){const a=new Event("vite:preloadError",{cancelable:!0});if(a.payload=o,window.dispatchEvent(a),!a.defaultPrevented)throw o}return i.then(o=>{for(const a of o||[])a.status==="rejected"&&s(a.reason);return t().catch(s)})};/** * @vue/shared v3.5.16 * (c) 2018-present Yuxi (Evan) You and Vue contributors * @license MIT **//*! #__NO_SIDE_EFFECTS__ */function zt(e){const t=Object.create(null);for(const n of e.split(","))t[n]=1;return n=>n in t}const et={},bi=[],Ft=()=>{},Zi=()=>!1,ai=e=>e.charCodeAt(0)===111&&e.charCodeAt(1)===110&&(e.charCodeAt(2)>122||e.charCodeAt(2)<97),Al=e=>e.startsWith("onUpdate:"),Ze=Object.assign,_l=(e,t)=>{const n=e.indexOf(t);n>-1&&e.splice(n,1)},IE=Object.prototype.hasOwnProperty,st=(e,t)=>IE.call(e,t),ye=Array.isArray,Si=e=>Vi(e)==="[object Map]",li=e=>Vi(e)==="[object Set]",mu=e=>Vi(e)==="[object Date]",Kp=e=>Vi(e)==="[object RegExp]",Pe=e=>typeof e=="function",ke=e=>typeof e=="string",Sn=e=>typeof e=="symbol",ct=e=>e!==null&&typeof e=="object",Ml=e=>(ct(e)||Pe(e))&&Pe(e.then)&&Pe(e.catch),cf=Object.prototype.toString,Vi=e=>cf.call(e),Gp=e=>Vi(e).slice(8,-1),xo=e=>Vi(e)==="[object Object]",xl=e=>ke(e)&&e!=="NaN"&&e[0]!=="-"&&""+parseInt(e,10)===e,xr=zt(",key,ref,ref_for,ref_key,onVnodeBeforeMount,onVnodeMounted,onVnodeBeforeUpdate,onVnodeUpdated,onVnodeBeforeUnmount,onVnodeUnmounted"),Xp=zt("bind,cloak,else-if,else,for,html,if,model,on,once,pre,show,slot,text,memo"),Nl=e=>{const t=Object.create(null);return n=>t[n]||(t[n]=e(n))},RE=/-(\w)/g,gt=Nl(e=>e.replace(RE,(t,n)=>n?n.toUpperCase():"")),PE=/\B([A-Z])/g,ln=Nl(e=>e.replace(PE,"-$1").toLowerCase()),ci=Nl(e=>e.charAt(0).toUpperCase()+e.slice(1)),Ti=Nl(e=>e?`on${ci(e)}`:""),on=(e,t)=>!Object.is(e,t),wi=(e,...t)=>{for(let n=0;n{Object.defineProperty(e,t,{configurable:!0,enumerable:!1,writable:r,value:n})},lo=e=>{const t=parseFloat(e);return isNaN(t)?e:t},co=e=>{const t=ke(e)?Number(e):NaN;return isNaN(t)?e:t};let Nd;const No=()=>Nd||(Nd=typeof globalThis<"u"?globalThis:typeof self<"u"?self:typeof window<"u"?window:typeof global<"u"?global:{}),LE=/^[_$a-zA-Z\xA0-\uFFFF][_$a-zA-Z0-9\xA0-\uFFFF]*$/;function kE(e){return LE.test(e)?`__props.${e}`:`__props[${JSON.stringify(e)}]`}function Jp(e,t){return e+JSON.stringify(t,(n,r)=>typeof r=="function"?r.toString():r)}const FE={TEXT:1,1:"TEXT",CLASS:2,2:"CLASS",STYLE:4,4:"STYLE",PROPS:8,8:"PROPS",FULL_PROPS:16,16:"FULL_PROPS",NEED_HYDRATION:32,32:"NEED_HYDRATION",STABLE_FRAGMENT:64,64:"STABLE_FRAGMENT",KEYED_FRAGMENT:128,128:"KEYED_FRAGMENT",UNKEYED_FRAGMENT:256,256:"UNKEYED_FRAGMENT",NEED_PATCH:512,512:"NEED_PATCH",DYNAMIC_SLOTS:1024,1024:"DYNAMIC_SLOTS",DEV_ROOT_FRAGMENT:2048,2048:"DEV_ROOT_FRAGMENT",CACHED:-1,"-1":"CACHED",BAIL:-2,"-2":"BAIL"},VE={1:"TEXT",2:"CLASS",4:"STYLE",8:"PROPS",16:"FULL_PROPS",32:"NEED_HYDRATION",64:"STABLE_FRAGMENT",128:"KEYED_FRAGMENT",256:"UNKEYED_FRAGMENT",512:"NEED_PATCH",1024:"DYNAMIC_SLOTS",2048:"DEV_ROOT_FRAGMENT",[-1]:"HOISTED",[-2]:"BAIL"},BE={ELEMENT:1,1:"ELEMENT",FUNCTIONAL_COMPONENT:2,2:"FUNCTIONAL_COMPONENT",STATEFUL_COMPONENT:4,4:"STATEFUL_COMPONENT",TEXT_CHILDREN:8,8:"TEXT_CHILDREN",ARRAY_CHILDREN:16,16:"ARRAY_CHILDREN",SLOTS_CHILDREN:32,32:"SLOTS_CHILDREN",TELEPORT:64,64:"TELEPORT",SUSPENSE:128,128:"SUSPENSE",COMPONENT_SHOULD_KEEP_ALIVE:256,256:"COMPONENT_SHOULD_KEEP_ALIVE",COMPONENT_KEPT_ALIVE:512,512:"COMPONENT_KEPT_ALIVE",COMPONENT:6,6:"COMPONENT"},jE={STABLE:1,1:"STABLE",DYNAMIC:2,2:"DYNAMIC",FORWARDED:3,3:"FORWARDED"},HE={1:"STABLE",2:"DYNAMIC",3:"FORWARDED"},UE="Infinity,undefined,NaN,isFinite,isNaN,parseFloat,parseInt,decodeURI,decodeURIComponent,encodeURI,encodeURIComponent,Math,Number,Date,Array,Object,Boolean,String,RegExp,Map,Set,JSON,Intl,BigInt,console,Error,Symbol",ff=zt(UE),$E=ff,Id=2;function Zp(e,t=0,n=e.length){if(t=Math.max(0,Math.min(t,e.length)),n=Math.max(0,Math.min(n,e.length)),t>n)return"";let r=e.split(/(\r?\n)/);const i=r.filter((a,l)=>l%2===1);r=r.filter((a,l)=>l%2===0);let s=0;const o=[];for(let a=0;a=t){for(let l=a-Id;l<=a+Id||n>s;l++){if(l<0||l>=r.length)continue;const c=l+1;o.push(`${c}${" ".repeat(Math.max(3-String(c).length,0))}| ${r[l]}`);const u=r[l].length,f=i[l]&&i[l].length||0;if(l===a){const d=t-(s-(u+f)),p=Math.max(1,n>s?u-d:n-t);o.push(" | "+" ".repeat(d)+"^".repeat(p))}else if(l>a){if(n>s){const d=Math.max(Math.min(n-s,u),1);o.push(" | "+"^".repeat(d))}s+=u+f}}break}return o.join(` `)}function vs(e){if(ye(e)){const t={};for(let n=0;n{if(n){const r=n.split(YE);r.length>1&&(t[r[0].trim()]=r[1].trim())}}),t}function KE(e){if(!e)return"";if(ke(e))return e;let t="";for(const n in e){const r=e[n];if(ke(r)||typeof r=="number"){const i=n.startsWith("--")?n:ln(n);t+=`${i}:${r};`}}return t}function ys(e){let t="";if(ke(e))t=e;else if(ye(e))for(let n=0;n/="'\u0009\u000a\u000c\u0020]/,Oc={};function eb(e){if(Oc.hasOwnProperty(e))return Oc[e];const t=qE.test(e);return t&&console.error(`unsafe attribute name: ${e}`),Oc[e]=!t}const tb={acceptCharset:"accept-charset",className:"class",htmlFor:"for",httpEquiv:"http-equiv"},nb=zt("accept,accept-charset,accesskey,action,align,allow,alt,async,autocapitalize,autocomplete,autofocus,autoplay,background,bgcolor,border,buffered,capture,challenge,charset,checked,cite,class,code,codebase,color,cols,colspan,content,contenteditable,contextmenu,controls,coords,crossorigin,csp,data,datetime,decoding,default,defer,dir,dirname,disabled,download,draggable,dropzone,enctype,enterkeyhint,for,form,formaction,formenctype,formmethod,formnovalidate,formtarget,headers,height,hidden,high,href,hreflang,http-equiv,icon,id,importance,inert,integrity,ismap,itemprop,keytype,kind,label,lang,language,loading,list,loop,low,manifest,max,maxlength,minlength,media,min,multiple,muted,name,novalidate,open,optimum,pattern,ping,placeholder,poster,preload,radiogroup,readonly,referrerpolicy,rel,required,reversed,rows,rowspan,sandbox,scope,scoped,selected,shape,size,sizes,slot,span,spellcheck,src,srcdoc,srclang,srcset,start,step,style,summary,tabindex,target,title,translate,type,usemap,value,width,wrap"),rb=zt("xmlns,accent-height,accumulate,additive,alignment-baseline,alphabetic,amplitude,arabic-form,ascent,attributeName,attributeType,azimuth,baseFrequency,baseline-shift,baseProfile,bbox,begin,bias,by,calcMode,cap-height,class,clip,clipPathUnits,clip-path,clip-rule,color,color-interpolation,color-interpolation-filters,color-profile,color-rendering,contentScriptType,contentStyleType,crossorigin,cursor,cx,cy,d,decelerate,descent,diffuseConstant,direction,display,divisor,dominant-baseline,dur,dx,dy,edgeMode,elevation,enable-background,end,exponent,fill,fill-opacity,fill-rule,filter,filterRes,filterUnits,flood-color,flood-opacity,font-family,font-size,font-size-adjust,font-stretch,font-style,font-variant,font-weight,format,from,fr,fx,fy,g1,g2,glyph-name,glyph-orientation-horizontal,glyph-orientation-vertical,glyphRef,gradientTransform,gradientUnits,hanging,height,href,hreflang,horiz-adv-x,horiz-origin-x,id,ideographic,image-rendering,in,in2,intercept,k,k1,k2,k3,k4,kernelMatrix,kernelUnitLength,kerning,keyPoints,keySplines,keyTimes,lang,lengthAdjust,letter-spacing,lighting-color,limitingConeAngle,local,marker-end,marker-mid,marker-start,markerHeight,markerUnits,markerWidth,mask,maskContentUnits,maskUnits,mathematical,max,media,method,min,mode,name,numOctaves,offset,opacity,operator,order,orient,orientation,origin,overflow,overline-position,overline-thickness,panose-1,paint-order,path,pathLength,patternContentUnits,patternTransform,patternUnits,ping,pointer-events,points,pointsAtX,pointsAtY,pointsAtZ,preserveAlpha,preserveAspectRatio,primitiveUnits,r,radius,referrerPolicy,refX,refY,rel,rendering-intent,repeatCount,repeatDur,requiredExtensions,requiredFeatures,restart,result,rotate,rx,ry,scale,seed,shape-rendering,slope,spacing,specularConstant,specularExponent,speed,spreadMethod,startOffset,stdDeviation,stemh,stemv,stitchTiles,stop-color,stop-opacity,strikethrough-position,strikethrough-thickness,string,stroke,stroke-dasharray,stroke-dashoffset,stroke-linecap,stroke-linejoin,stroke-miterlimit,stroke-opacity,stroke-width,style,surfaceScale,systemLanguage,tabindex,tableValues,target,targetX,targetY,text-anchor,text-decoration,text-rendering,textLength,to,transform,transform-origin,type,u1,u2,underline-position,underline-thickness,unicode,unicode-bidi,unicode-range,units-per-em,v-alphabetic,v-hanging,v-ideographic,v-mathematical,values,vector-effect,version,vert-adv-y,vert-origin-x,vert-origin-y,viewBox,viewTarget,visibility,width,widths,word-spacing,writing-mode,x,x-height,x1,x2,xChannelSelector,xlink:actuate,xlink:arcrole,xlink:href,xlink:role,xlink:show,xlink:title,xlink:type,xmlns:xlink,xml:base,xml:lang,xml:space,y,y1,y2,yChannelSelector,z,zoomAndPan"),ib=zt("accent,accentunder,actiontype,align,alignmentscope,altimg,altimg-height,altimg-valign,altimg-width,alttext,bevelled,close,columnsalign,columnlines,columnspan,denomalign,depth,dir,display,displaystyle,encoding,equalcolumns,equalrows,fence,fontstyle,fontweight,form,frame,framespacing,groupalign,height,href,id,indentalign,indentalignfirst,indentalignlast,indentshift,indentshiftfirst,indentshiftlast,indextype,justify,largetop,largeop,lquote,lspace,mathbackground,mathcolor,mathsize,mathvariant,maxsize,minlabelspacing,mode,other,overflow,position,rowalign,rowlines,rowspan,rquote,rspace,scriptlevel,scriptminsize,scriptsizemultiplier,selection,separator,separators,shift,side,src,stackalign,stretchy,subscriptshift,superscriptshift,symmetric,voffset,width,widths,xlink:href,xlink:show,xlink:type,xmlns");function sb(e){if(e==null)return!1;const t=typeof e;return t==="string"||t==="number"||t==="boolean"}const ob=/["'&<>]/;function ab(e){const t=""+e,n=ob.exec(t);if(!n)return t;let r="",i,s,o=0;for(s=n.index;s||--!>|?@[\\\]^`{|}~]/g;function ub(e,t){return e.replace(sm,n=>t?n==='"'?'\\\\\\"':`\\\\${n}`:`\\${n}`)}function fb(e,t){if(e.length!==t.length)return!1;let n=!0;for(let r=0;n&&rPr(n,t))}const om=e=>!!(e&&e.__v_isRef===!0),pf=e=>ke(e)?e:e==null?"":ye(e)||ct(e)&&(e.toString===cf||!Pe(e.toString))?om(e)?pf(e.value):JSON.stringify(e,am,2):String(e),am=(e,t)=>om(t)?am(e,t.value):Si(t)?{[`Map(${t.size})`]:[...t.entries()].reduce((n,[r,i],s)=>(n[Ac(r,s)+" =>"]=i,n),{})}:li(t)?{[`Set(${t.size})`]:[...t.values()].map(n=>Ac(n))}:Sn(t)?Ac(t):ct(t)&&!ye(t)&&!xo(t)?String(t):t,Ac=(e,t="")=>{var n;return Sn(e)?`Symbol(${(n=e.description)!=null?n:t})`:e},db=Object.freeze(Object.defineProperty({__proto__:null,EMPTY_ARR:bi,EMPTY_OBJ:et,NO:Zi,NOOP:Ft,PatchFlagNames:VE,PatchFlags:FE,ShapeFlags:BE,SlotFlags:jE,camelize:gt,capitalize:ci,cssVarNameEscapeSymbolsRE:sm,def:uf,escapeHtml:ab,escapeHtmlComment:cb,extend:Ze,genCacheKey:Jp,genPropsAccessExp:kE,generateCodeFrame:Zp,getEscapedCssVarName:ub,getGlobalThis:No,hasChanged:on,hasOwn:st,hyphenate:ln,includeBooleanAttr:hf,invokeArrayFns:wi,isArray:ye,isBooleanAttr:QE,isBuiltInDirective:Xp,isDate:mu,isFunction:Pe,isGloballyAllowed:ff,isGloballyWhitelisted:$E,isHTMLTag:qp,isIntegerKey:xl,isKnownHtmlAttr:nb,isKnownMathMLAttr:ib,isKnownSvgAttr:rb,isMap:Si,isMathMLTag:tm,isModelListener:Al,isObject:ct,isOn:ai,isPlainObject:xo,isPromise:Ml,isRegExp:Kp,isRenderableAttrValue:sb,isReservedProp:xr,isSSRSafeAttrName:eb,isSVGTag:em,isSet:li,isSpecialBooleanAttr:im,isString:ke,isSymbol:Sn,isVoidTag:nm,looseEqual:Pr,looseIndexOf:Io,looseToNumber:lo,makeMap:zt,normalizeClass:ys,normalizeProps:Qp,normalizeStyle:vs,objectToString:cf,parseStringStyle:df,propsToAttrMap:tb,remove:_l,slotFlagsText:HE,stringifyStyle:KE,toDisplayString:pf,toHandlerKey:Ti,toNumber:co,toRawType:Gp,toTypeString:Vi},Symbol.toStringTag,{value:"Module"}));/** * @vue/reactivity v3.5.16 * (c) 2018-present Yuxi (Evan) You and Vue contributors * @license MIT **/let nn;class mf{constructor(t=!1){this.detached=t,this._active=!0,this._on=0,this.effects=[],this.cleanups=[],this._isPaused=!1,this.parent=nn,!t&&nn&&(this.index=(nn.scopes||(nn.scopes=[])).push(this)-1)}get active(){return this._active}pause(){if(this._active){this._isPaused=!0;let t,n;if(this.scopes)for(t=0,n=this.scopes.length;t0&&--this._on===0&&(nn=this.prevScope,this.prevScope=void 0)}stop(t){if(this._active){this._active=!1;let n,r;for(n=0,r=this.effects.length;n0)return;if(Xs){let t=Xs;for(Xs=void 0;t;){const n=t.next;t.next=void 0,t.flags&=-9,t=n}}let e;for(;Gs;){let t=Gs;for(Gs=void 0;t;){const n=t.next;if(t.next=void 0,t.flags&=-9,t.flags&1)try{t.trigger()}catch(r){e||(e=r)}t=n}}if(e)throw e}function fm(e){for(let t=e.deps;t;t=t.nextDep)t.version=-1,t.prevActiveLink=t.dep.activeLink,t.dep.activeLink=t}function dm(e){let t,n=e.depsTail,r=n;for(;r;){const i=r.prevDep;r.version===-1?(r===n&&(n=i),yf(r),mb(r)):t=r,r.dep.activeLink=r.prevActiveLink,r.prevActiveLink=void 0,r=i}e.deps=t,e.depsTail=n}function gu(e){for(let t=e.deps;t;t=t.nextDep)if(t.dep.version!==t.version||t.dep.computed&&(hm(t.dep.computed)||t.dep.version!==t.version))return!0;return!!e._dirty}function hm(e){if(e.flags&4&&!(e.flags&16)||(e.flags&=-17,e.globalVersion===fo)||(e.globalVersion=fo,!e.isSSR&&e.flags&128&&(!e.deps&&!e._dirty||!gu(e))))return;e.flags|=2;const t=e.dep,n=mt,r=lr;mt=e,lr=!0;try{fm(e);const i=e.fn(e._value);(t.version===0||on(i,e._value))&&(e.flags|=128,e._value=i,t.version++)}catch(i){throw t.version++,i}finally{mt=n,lr=r,dm(e),e.flags&=-3}}function yf(e,t=!1){const{dep:n,prevSub:r,nextSub:i}=e;if(r&&(r.nextSub=i,e.prevSub=void 0),i&&(i.prevSub=r,e.nextSub=void 0),n.subs===e&&(n.subs=r,!r&&n.computed)){n.computed.flags&=-5;for(let s=n.computed.deps;s;s=s.nextDep)yf(s,!0)}!t&&!--n.sc&&n.map&&n.map.delete(n.key)}function mb(e){const{prevDep:t,nextDep:n}=e;t&&(t.nextDep=n,e.prevDep=void 0),n&&(n.prevDep=t,e.nextDep=void 0)}function gb(e,t){e.effect instanceof uo&&(e=e.effect.fn);const n=new uo(e);t&&Ze(n,t);try{n.run()}catch(i){throw n.stop(),i}const r=n.run.bind(n);return r.effect=n,r}function vb(e){e.effect.stop()}let lr=!0;const pm=[];function Lr(){pm.push(lr),lr=!1}function kr(){const e=pm.pop();lr=e===void 0?!0:e}function Rd(e){const{cleanup:t}=e;if(e.cleanup=void 0,t){const n=mt;mt=void 0;try{t()}finally{mt=n}}}let fo=0;class yb{constructor(t,n){this.sub=t,this.dep=n,this.version=n.version,this.nextDep=this.prevDep=this.nextSub=this.prevSub=this.prevActiveLink=void 0}}class Il{constructor(t){this.computed=t,this.version=0,this.activeLink=void 0,this.subs=void 0,this.map=void 0,this.key=void 0,this.sc=0}track(t){if(!mt||!lr||mt===this.computed)return;let n=this.activeLink;if(n===void 0||n.sub!==mt)n=this.activeLink=new yb(mt,this),mt.deps?(n.prevDep=mt.depsTail,mt.depsTail.nextDep=n,mt.depsTail=n):mt.deps=mt.depsTail=n,mm(n);else if(n.version===-1&&(n.version=this.version,n.nextDep)){const r=n.nextDep;r.prevDep=n.prevDep,n.prevDep&&(n.prevDep.nextDep=r),n.prevDep=mt.depsTail,n.nextDep=void 0,mt.depsTail.nextDep=n,mt.depsTail=n,mt.deps===n&&(mt.deps=r)}return n}trigger(t){this.version++,fo++,this.notify(t)}notify(t){gf();try{for(let n=this.subs;n;n=n.prevSub)n.sub.notify()&&n.sub.dep.notify()}finally{vf()}}}function mm(e){if(e.dep.sc++,e.sub.flags&4){const t=e.dep.computed;if(t&&!e.dep.subs){t.flags|=20;for(let r=t.deps;r;r=r.nextDep)mm(r)}const n=e.dep.subs;n!==e&&(e.prevSub=n,n&&(n.nextSub=e)),e.dep.subs=e}}const Ka=new WeakMap,Di=Symbol(""),vu=Symbol(""),ho=Symbol("");function an(e,t,n){if(lr&&mt){let r=Ka.get(e);r||Ka.set(e,r=new Map);let i=r.get(n);i||(r.set(n,i=new Il),i.map=r,i.key=n),i.track()}}function Cr(e,t,n,r,i,s){const o=Ka.get(e);if(!o){fo++;return}const a=l=>{l&&l.trigger()};if(gf(),t==="clear")o.forEach(a);else{const l=ye(e),c=l&&xl(n);if(l&&n==="length"){const u=Number(r);o.forEach((f,d)=>{(d==="length"||d===ho||!Sn(d)&&d>=u)&&a(f)})}else switch((n!==void 0||o.has(void 0))&&a(o.get(n)),c&&a(o.get(ho)),t){case"add":l?c&&a(o.get("length")):(a(o.get(Di)),Si(e)&&a(o.get(vu)));break;case"delete":l||(a(o.get(Di)),Si(e)&&a(o.get(vu)));break;case"set":Si(e)&&a(o.get(Di));break}}vf()}function Eb(e,t){const n=Ka.get(e);return n&&n.get(t)}function Hi(e){const t=rt(e);return t===e?t:(an(t,"iterate",ho),$n(e)?t:t.map(Jt))}function Rl(e){return an(e=rt(e),"iterate",ho),e}const bb={__proto__:null,[Symbol.iterator](){return Mc(this,Symbol.iterator,Jt)},concat(...e){return Hi(this).concat(...e.map(t=>ye(t)?Hi(t):t))},entries(){return Mc(this,"entries",e=>(e[1]=Jt(e[1]),e))},every(e,t){return Er(this,"every",e,t,void 0,arguments)},filter(e,t){return Er(this,"filter",e,t,n=>n.map(Jt),arguments)},find(e,t){return Er(this,"find",e,t,Jt,arguments)},findIndex(e,t){return Er(this,"findIndex",e,t,void 0,arguments)},findLast(e,t){return Er(this,"findLast",e,t,Jt,arguments)},findLastIndex(e,t){return Er(this,"findLastIndex",e,t,void 0,arguments)},forEach(e,t){return Er(this,"forEach",e,t,void 0,arguments)},includes(...e){return xc(this,"includes",e)},indexOf(...e){return xc(this,"indexOf",e)},join(e){return Hi(this).join(e)},lastIndexOf(...e){return xc(this,"lastIndexOf",e)},map(e,t){return Er(this,"map",e,t,void 0,arguments)},pop(){return Os(this,"pop")},push(...e){return Os(this,"push",e)},reduce(e,...t){return Pd(this,"reduce",e,t)},reduceRight(e,...t){return Pd(this,"reduceRight",e,t)},shift(){return Os(this,"shift")},some(e,t){return Er(this,"some",e,t,void 0,arguments)},splice(...e){return Os(this,"splice",e)},toReversed(){return Hi(this).toReversed()},toSorted(e){return Hi(this).toSorted(e)},toSpliced(...e){return Hi(this).toSpliced(...e)},unshift(...e){return Os(this,"unshift",e)},values(){return Mc(this,"values",Jt)}};function Mc(e,t,n){const r=Rl(e),i=r[t]();return r!==e&&!$n(e)&&(i._next=i.next,i.next=()=>{const s=i._next();return s.value&&(s.value=n(s.value)),s}),i}const Sb=Array.prototype;function Er(e,t,n,r,i,s){const o=Rl(e),a=o!==e&&!$n(e),l=o[t];if(l!==Sb[t]){const f=l.apply(e,s);return a?Jt(f):f}let c=n;o!==e&&(a?c=function(f,d){return n.call(this,Jt(f),d,e)}:n.length>2&&(c=function(f,d){return n.call(this,f,d,e)}));const u=l.call(o,c,r);return a&&i?i(u):u}function Pd(e,t,n,r){const i=Rl(e);let s=n;return i!==e&&($n(e)?n.length>3&&(s=function(o,a,l){return n.call(this,o,a,l,e)}):s=function(o,a,l){return n.call(this,o,Jt(a),l,e)}),i[t](s,...r)}function xc(e,t,n){const r=rt(e);an(r,"iterate",ho);const i=r[t](...n);return(i===-1||i===!1)&&Fl(n[0])?(n[0]=rt(n[0]),r[t](...n)):i}function Os(e,t,n=[]){Lr(),gf();const r=rt(e)[t].apply(e,n);return vf(),kr(),r}const Tb=zt("__proto__,__v_isRef,__isVue"),gm=new Set(Object.getOwnPropertyNames(Symbol).filter(e=>e!=="arguments"&&e!=="caller").map(e=>Symbol[e]).filter(Sn));function wb(e){Sn(e)||(e=String(e));const t=rt(this);return an(t,"has",e),t.hasOwnProperty(e)}class vm{constructor(t=!1,n=!1){this._isReadonly=t,this._isShallow=n}get(t,n,r){if(n==="__v_skip")return t.__v_skip;const i=this._isReadonly,s=this._isShallow;if(n==="__v_isReactive")return!i;if(n==="__v_isReadonly")return i;if(n==="__v_isShallow")return s;if(n==="__v_raw")return r===(i?s?wm:Tm:s?Sm:bm).get(t)||Object.getPrototypeOf(t)===Object.getPrototypeOf(r)?t:void 0;const o=ye(t);if(!i){let l;if(o&&(l=bb[n]))return l;if(n==="hasOwnProperty")return wb}const a=Reflect.get(t,n,Ut(t)?t:r);return(Sn(n)?gm.has(n):Tb(n))||(i||an(t,"get",n),s)?a:Ut(a)?o&&xl(n)?a:a.value:ct(a)?i?Ef(a):Ll(a):a}}class ym extends vm{constructor(t=!1){super(!1,t)}set(t,n,r,i){let s=t[n];if(!this._isShallow){const l=Fr(s);if(!$n(r)&&!Fr(r)&&(s=rt(s),r=rt(r)),!ye(t)&&Ut(s)&&!Ut(r))return l?!1:(s.value=r,!0)}const o=ye(t)&&xl(n)?Number(n)e,Zo=e=>Reflect.getPrototypeOf(e);function _b(e,t,n){return function(...r){const i=this.__v_raw,s=rt(i),o=Si(s),a=e==="entries"||e===Symbol.iterator&&o,l=e==="keys"&&o,c=i[e](...r),u=n?yu:t?Ga:Jt;return!t&&an(s,"iterate",l?vu:Di),{next(){const{value:f,done:d}=c.next();return d?{value:f,done:d}:{value:a?[u(f[0]),u(f[1])]:u(f),done:d}},[Symbol.iterator](){return this}}}}function Qo(e){return function(...t){return e==="delete"?!1:e==="clear"?void 0:this}}function Mb(e,t){const n={get(i){const s=this.__v_raw,o=rt(s),a=rt(i);e||(on(i,a)&&an(o,"get",i),an(o,"get",a));const{has:l}=Zo(o),c=t?yu:e?Ga:Jt;if(l.call(o,i))return c(s.get(i));if(l.call(o,a))return c(s.get(a));s!==o&&s.get(i)},get size(){const i=this.__v_raw;return!e&&an(rt(i),"iterate",Di),Reflect.get(i,"size",i)},has(i){const s=this.__v_raw,o=rt(s),a=rt(i);return e||(on(i,a)&&an(o,"has",i),an(o,"has",a)),i===a?s.has(i):s.has(i)||s.has(a)},forEach(i,s){const o=this,a=o.__v_raw,l=rt(a),c=t?yu:e?Ga:Jt;return!e&&an(l,"iterate",Di),a.forEach((u,f)=>i.call(s,c(u),c(f),o))}};return Ze(n,e?{add:Qo("add"),set:Qo("set"),delete:Qo("delete"),clear:Qo("clear")}:{add(i){!t&&!$n(i)&&!Fr(i)&&(i=rt(i));const s=rt(this);return Zo(s).has.call(s,i)||(s.add(i),Cr(s,"add",i,i)),this},set(i,s){!t&&!$n(s)&&!Fr(s)&&(s=rt(s));const o=rt(this),{has:a,get:l}=Zo(o);let c=a.call(o,i);c||(i=rt(i),c=a.call(o,i));const u=l.call(o,i);return o.set(i,s),c?on(s,u)&&Cr(o,"set",i,s):Cr(o,"add",i,s),this},delete(i){const s=rt(this),{has:o,get:a}=Zo(s);let l=o.call(s,i);l||(i=rt(i),l=o.call(s,i)),a&&a.call(s,i);const c=s.delete(i);return l&&Cr(s,"delete",i,void 0),c},clear(){const i=rt(this),s=i.size!==0,o=i.clear();return s&&Cr(i,"clear",void 0,void 0),o}}),["keys","values","entries",Symbol.iterator].forEach(i=>{n[i]=_b(i,e,t)}),n}function Pl(e,t){const n=Mb(e,t);return(r,i,s)=>i==="__v_isReactive"?!e:i==="__v_isReadonly"?e:i==="__v_raw"?r:Reflect.get(st(n,i)&&i in r?n:r,i,s)}const xb={get:Pl(!1,!1)},Nb={get:Pl(!1,!0)},Ib={get:Pl(!0,!1)},Rb={get:Pl(!0,!0)},bm=new WeakMap,Sm=new WeakMap,Tm=new WeakMap,wm=new WeakMap;function Pb(e){switch(e){case"Object":case"Array":return 1;case"Map":case"Set":case"WeakMap":case"WeakSet":return 2;default:return 0}}function Lb(e){return e.__v_skip||!Object.isExtensible(e)?0:Pb(Gp(e))}function Ll(e){return Fr(e)?e:kl(e,!1,Db,xb,bm)}function Dm(e){return kl(e,!1,Ob,Nb,Sm)}function Ef(e){return kl(e,!0,Cb,Ib,Tm)}function kb(e){return kl(e,!0,Ab,Rb,wm)}function kl(e,t,n,r,i){if(!ct(e)||e.__v_raw&&!(t&&e.__v_isReactive))return e;const s=Lb(e);if(s===0)return e;const o=i.get(e);if(o)return o;const a=new Proxy(e,s===2?r:n);return i.set(e,a),a}function ti(e){return Fr(e)?ti(e.__v_raw):!!(e&&e.__v_isReactive)}function Fr(e){return!!(e&&e.__v_isReadonly)}function $n(e){return!!(e&&e.__v_isShallow)}function Fl(e){return e?!!e.__v_raw:!1}function rt(e){const t=e&&e.__v_raw;return t?rt(t):e}function Cm(e){return!st(e,"__v_skip")&&Object.isExtensible(e)&&uf(e,"__v_skip",!0),e}const Jt=e=>ct(e)?Ll(e):e,Ga=e=>ct(e)?Ef(e):e;function Ut(e){return e?e.__v_isRef===!0:!1}function Js(e){return Am(e,!1)}function Om(e){return Am(e,!0)}function Am(e,t){return Ut(e)?e:new Fb(e,t)}class Fb{constructor(t,n){this.dep=new Il,this.__v_isRef=!0,this.__v_isShallow=!1,this._rawValue=n?t:rt(t),this._value=n?t:Jt(t),this.__v_isShallow=n}get value(){return this.dep.track(),this._value}set value(t){const n=this._rawValue,r=this.__v_isShallow||$n(t)||Fr(t);t=r?t:rt(t),on(t,n)&&(this._rawValue=t,this._value=r?t:Jt(t),this.dep.trigger())}}function Vb(e){e.dep&&e.dep.trigger()}function Vl(e){return Ut(e)?e.value:e}function Bb(e){return Pe(e)?e():Vl(e)}const jb={get:(e,t,n)=>t==="__v_raw"?e:Vl(Reflect.get(e,t,n)),set:(e,t,n,r)=>{const i=e[t];return Ut(i)&&!Ut(n)?(i.value=n,!0):Reflect.set(e,t,n,r)}};function bf(e){return ti(e)?e:new Proxy(e,jb)}class Hb{constructor(t){this.__v_isRef=!0,this._value=void 0;const n=this.dep=new Il,{get:r,set:i}=t(n.track.bind(n),n.trigger.bind(n));this._get=r,this._set=i}get value(){return this._value=this._get()}set value(t){this._set(t)}}function _m(e){return new Hb(e)}function Ub(e){const t=ye(e)?new Array(e.length):{};for(const n in e)t[n]=Mm(e,n);return t}class $b{constructor(t,n,r){this._object=t,this._key=n,this._defaultValue=r,this.__v_isRef=!0,this._value=void 0}get value(){const t=this._object[this._key];return this._value=t===void 0?this._defaultValue:t}set value(t){this._object[this._key]=t}get dep(){return Eb(rt(this._object),this._key)}}class Wb{constructor(t){this._getter=t,this.__v_isRef=!0,this.__v_isReadonly=!0,this._value=void 0}get value(){return this._value=this._getter()}}function Yb(e,t,n){return Ut(e)?e:Pe(e)?new Wb(e):ct(e)&&arguments.length>1?Mm(e,t,n):Js(e)}function Mm(e,t,n){const r=e[t];return Ut(r)?r:new $b(e,t,n)}class zb{constructor(t,n,r){this.fn=t,this.setter=n,this._value=void 0,this.dep=new Il(this),this.__v_isRef=!0,this.deps=void 0,this.depsTail=void 0,this.flags=16,this.globalVersion=fo-1,this.next=void 0,this.effect=this,this.__v_isReadonly=!n,this.isSSR=r}notify(){if(this.flags|=16,!(this.flags&8)&&mt!==this)return um(this,!0),!0}get value(){const t=this.dep.track();return hm(this),t&&(t.version=this.dep.version),this._value}set value(t){this.setter&&this.setter(t)}}function Kb(e,t,n=!1){let r,i;return Pe(e)?r=e:(r=e.get,i=e.set),new zb(r,i,n)}const Gb={GET:"get",HAS:"has",ITERATE:"iterate"},Xb={SET:"set",ADD:"add",DELETE:"delete",CLEAR:"clear"},qo={},Xa=new WeakMap;let zr;function Jb(){return zr}function xm(e,t=!1,n=zr){if(n){let r=Xa.get(n);r||Xa.set(n,r=[]),r.push(e)}}function Zb(e,t,n=et){const{immediate:r,deep:i,once:s,scheduler:o,augmentJob:a,call:l}=n,c=E=>i?E:$n(E)||i===!1||i===0?Or(E,1):Or(E);let u,f,d,p,m=!1,g=!1;if(Ut(e)?(f=()=>e.value,m=$n(e)):ti(e)?(f=()=>c(e),m=!0):ye(e)?(g=!0,m=e.some(E=>ti(E)||$n(E)),f=()=>e.map(E=>{if(Ut(E))return E.value;if(ti(E))return c(E);if(Pe(E))return l?l(E,2):E()})):Pe(e)?t?f=l?()=>l(e,2):e:f=()=>{if(d){Lr();try{d()}finally{kr()}}const E=zr;zr=u;try{return l?l(e,3,[p]):e(p)}finally{zr=E}}:f=Ft,t&&i){const E=f,C=i===!0?1/0:i;f=()=>Or(E(),C)}const v=lm(),w=()=>{u.stop(),v&&v.active&&_l(v.effects,u)};if(s&&t){const E=t;t=(...C)=>{E(...C),w()}}let S=g?new Array(e.length).fill(qo):qo;const y=E=>{if(!(!(u.flags&1)||!u.dirty&&!E))if(t){const C=u.run();if(i||m||(g?C.some((M,B)=>on(M,S[B])):on(C,S))){d&&d();const M=zr;zr=u;try{const B=[C,S===qo?void 0:g&&S[0]===qo?[]:S,p];S=C,l?l(t,3,B):t(...B)}finally{zr=M}}}else u.run()};return a&&a(y),u=new uo(f),u.scheduler=o?()=>o(y,!1):y,p=E=>xm(E,!1,u),d=u.onStop=()=>{const E=Xa.get(u);if(E){if(l)l(E,4);else for(const C of E)C();Xa.delete(u)}},t?r?y(!0):S=u.run():o?o(y.bind(null,!0),!0):u.run(),w.pause=u.pause.bind(u),w.resume=u.resume.bind(u),w.stop=w,w}function Or(e,t=1/0,n){if(t<=0||!ct(e)||e.__v_skip||(n=n||new Set,n.has(e)))return e;if(n.add(e),t--,Ut(e))Or(e.value,t,n);else if(ye(e))for(let r=0;r{Or(r,t,n)});else if(xo(e)){for(const r in e)Or(e[r],t,n);for(const r of Object.getOwnPropertySymbols(e))Object.prototype.propertyIsEnumerable.call(e,r)&&Or(e[r],t,n)}return e}/** * @vue/runtime-core v3.5.16 * (c) 2018-present Yuxi (Evan) You and Vue contributors * @license MIT **/const Nm=[];function Qb(e){Nm.push(e)}function qb(){Nm.pop()}function eS(e,t){}const tS={SETUP_FUNCTION:0,0:"SETUP_FUNCTION",RENDER_FUNCTION:1,1:"RENDER_FUNCTION",NATIVE_EVENT_HANDLER:5,5:"NATIVE_EVENT_HANDLER",COMPONENT_EVENT_HANDLER:6,6:"COMPONENT_EVENT_HANDLER",VNODE_HOOK:7,7:"VNODE_HOOK",DIRECTIVE_HOOK:8,8:"DIRECTIVE_HOOK",TRANSITION_HOOK:9,9:"TRANSITION_HOOK",APP_ERROR_HANDLER:10,10:"APP_ERROR_HANDLER",APP_WARN_HANDLER:11,11:"APP_WARN_HANDLER",FUNCTION_REF:12,12:"FUNCTION_REF",ASYNC_COMPONENT_LOADER:13,13:"ASYNC_COMPONENT_LOADER",SCHEDULER:14,14:"SCHEDULER",COMPONENT_UPDATE:15,15:"COMPONENT_UPDATE",APP_UNMOUNT_CLEANUP:16,16:"APP_UNMOUNT_CLEANUP"},nS={sp:"serverPrefetch hook",bc:"beforeCreate hook",c:"created hook",bm:"beforeMount hook",m:"mounted hook",bu:"beforeUpdate hook",u:"updated",bum:"beforeUnmount hook",um:"unmounted hook",a:"activated hook",da:"deactivated hook",ec:"errorCaptured hook",rtc:"renderTracked hook",rtg:"renderTriggered hook",0:"setup function",1:"render function",2:"watcher getter",3:"watcher callback",4:"watcher cleanup function",5:"native event handler",6:"component event handler",7:"vnode hook",8:"directive hook",9:"transition hook",10:"app errorHandler",11:"app warnHandler",12:"ref function",13:"async component loader",14:"scheduler flush",15:"component update",16:"app unmount cleanup function"};function Es(e,t,n,r){try{return r?e(...r):e()}catch(i){Bi(i,t,n)}}function Jn(e,t,n,r){if(Pe(e)){const i=Es(e,t,n,r);return i&&Ml(i)&&i.catch(s=>{Bi(s,t,n)}),i}if(ye(e)){const i=[];for(let s=0;s>>1,i=vn[r],s=mo(i);s=mo(n)?vn.push(e):vn.splice(iS(t),0,e),e.flags|=1,Rm()}}function Rm(){Ja||(Ja=Im.then(Pm))}function po(e){ye(e)?es.push(...e):Kr&&e.id===-1?Kr.splice(Gi+1,0,e):e.flags&1||(es.push(e),e.flags|=1),Rm()}function Ld(e,t,n=hr+1){for(;nmo(n)-mo(r));if(es.length=0,Kr){Kr.push(...t);return}for(Kr=t,Gi=0;Gie.id==null?e.flags&2?-1:1/0:e.id;function Pm(e){try{for(hr=0;hrXi.emit(i,...s)),ea=[]):typeof window<"u"&&window.HTMLElement&&!((r=(n=window.navigator)==null?void 0:n.userAgent)!=null&&r.includes("jsdom"))?((t.__VUE_DEVTOOLS_HOOK_REPLAY__=t.__VUE_DEVTOOLS_HOOK_REPLAY__||[]).push(s=>{Lm(s,t)}),setTimeout(()=>{Xi||(t.__VUE_DEVTOOLS_HOOK_REPLAY__=null,ea=[])},3e3)):ea=[]}let Ht=null,jl=null;function go(e){const t=Ht;return Ht=e,jl=e&&e.type.__scopeId||null,t}function sS(e){jl=e}function oS(){jl=null}const aS=e=>Tf;function Tf(e,t=Ht,n){if(!t||e._n)return e;const r=(...i)=>{r._d&&Ou(-1);const s=go(t);let o;try{o=e(...i)}finally{go(s),r._d&&Ou(1)}return o};return r._n=!0,r._c=!0,r._d=!0,r}function lS(e,t){if(Ht===null)return e;const n=ko(Ht),r=e.dirs||(e.dirs=[]);for(let i=0;ie.__isTeleport,Zs=e=>e&&(e.disabled||e.disabled===""),kd=e=>e&&(e.defer||e.defer===""),Fd=e=>typeof SVGElement<"u"&&e instanceof SVGElement,Vd=e=>typeof MathMLElement=="function"&&e instanceof MathMLElement,Eu=(e,t)=>{const n=e&&e.to;return ke(n)?t?t(n):null:n},Vm={name:"Teleport",__isTeleport:!0,process(e,t,n,r,i,s,o,a,l,c){const{mc:u,pc:f,pbc:d,o:{insert:p,querySelector:m,createText:g,createComment:v}}=c,w=Zs(t.props);let{shapeFlag:S,children:y,dynamicChildren:E}=t;if(e==null){const C=t.el=g(""),M=t.anchor=g("");p(C,n,r),p(M,n,r);const B=(A,x)=>{S&16&&(i&&i.isCE&&(i.ce._teleportTarget=A),u(y,A,x,i,s,o,a,l))},j=()=>{const A=t.target=Eu(t.props,m),x=Bm(A,t,g,p);A&&(o!=="svg"&&Fd(A)?o="svg":o!=="mathml"&&Vd(A)&&(o="mathml"),w||(B(A,x),Oa(t,!1)))};w&&(B(n,M),Oa(t,!0)),kd(t.props)?(t.el.__isMounted=!1,Bt(()=>{j(),delete t.el.__isMounted},s)):j()}else{if(kd(t.props)&&e.el.__isMounted===!1){Bt(()=>{Vm.process(e,t,n,r,i,s,o,a,l,c)},s);return}t.el=e.el,t.targetStart=e.targetStart;const C=t.anchor=e.anchor,M=t.target=e.target,B=t.targetAnchor=e.targetAnchor,j=Zs(e.props),A=j?n:M,x=j?C:B;if(o==="svg"||Fd(M)?o="svg":(o==="mathml"||Vd(M))&&(o="mathml"),E?(d(e.dynamicChildren,E,A,i,s,o,a),Pf(e,t,!0)):l||f(e,t,A,x,i,s,o,a,!1),w)j?t.props&&e.props&&t.props.to!==e.props.to&&(t.props.to=e.props.to):ta(t,n,C,c,1);else if((t.props&&t.props.to)!==(e.props&&e.props.to)){const k=t.target=Eu(t.props,m);k&&ta(t,k,null,c,0)}else j&&ta(t,M,B,c,1);Oa(t,w)}},remove(e,t,n,{um:r,o:{remove:i}},s){const{shapeFlag:o,children:a,anchor:l,targetStart:c,targetAnchor:u,target:f,props:d}=e;if(f&&(i(c),i(u)),s&&i(l),o&16){const p=s||!Zs(d);for(let m=0;m{e.isMounted=!0}),Wl(()=>{e.isUnmounting=!0}),e}const Wn=[Function,Array],Df={mode:String,appear:Boolean,persisted:Boolean,onBeforeEnter:Wn,onEnter:Wn,onAfterEnter:Wn,onEnterCancelled:Wn,onBeforeLeave:Wn,onLeave:Wn,onAfterLeave:Wn,onLeaveCancelled:Wn,onBeforeAppear:Wn,onAppear:Wn,onAfterAppear:Wn,onAppearCancelled:Wn},jm=e=>{const t=e.subTree;return t.component?jm(t.component):t},fS={name:"BaseTransition",props:Df,setup(e,{slots:t}){const n=qn(),r=wf();return()=>{const i=t.default&&Hl(t.default(),!0);if(!i||!i.length)return;const s=Hm(i),o=rt(e),{mode:a}=o;if(r.isLeaving)return Nc(s);const l=Bd(s);if(!l)return Nc(s);let c=os(l,o,r,n,f=>c=f);l.type!==Rt&&Vr(l,c);let u=n.subTree&&Bd(n.subTree);if(u&&u.type!==Rt&&!or(l,u)&&jm(n).type!==Rt){let f=os(u,o,r,n);if(Vr(u,f),a==="out-in"&&l.type!==Rt)return r.isLeaving=!0,f.afterLeave=()=>{r.isLeaving=!1,n.job.flags&8||n.update(),delete f.afterLeave,u=void 0},Nc(s);a==="in-out"&&l.type!==Rt?f.delayLeave=(d,p,m)=>{const g=$m(r,u);g[String(u.key)]=u,d[Gr]=()=>{p(),d[Gr]=void 0,delete c.delayedLeave,u=void 0},c.delayedLeave=()=>{m(),delete c.delayedLeave,u=void 0}}:u=void 0}else u&&(u=void 0);return s}}};function Hm(e){let t=e[0];if(e.length>1){for(const n of e)if(n.type!==Rt){t=n;break}}return t}const Um=fS;function $m(e,t){const{leavingVNodes:n}=e;let r=n.get(t.type);return r||(r=Object.create(null),n.set(t.type,r)),r}function os(e,t,n,r,i){const{appear:s,mode:o,persisted:a=!1,onBeforeEnter:l,onEnter:c,onAfterEnter:u,onEnterCancelled:f,onBeforeLeave:d,onLeave:p,onAfterLeave:m,onLeaveCancelled:g,onBeforeAppear:v,onAppear:w,onAfterAppear:S,onAppearCancelled:y}=t,E=String(e.key),C=$m(n,e),M=(A,x)=>{A&&Jn(A,r,9,x)},B=(A,x)=>{const k=x[1];M(A,x),ye(A)?A.every(P=>P.length<=1)&&k():A.length<=1&&k()},j={mode:o,persisted:a,beforeEnter(A){let x=l;if(!n.isMounted)if(s)x=v||l;else return;A[Gr]&&A[Gr](!0);const k=C[E];k&&or(e,k)&&k.el[Gr]&&k.el[Gr](),M(x,[A])},enter(A){let x=c,k=u,P=f;if(!n.isMounted)if(s)x=w||c,k=S||u,P=y||f;else return;let V=!1;const $=A[na]=Z=>{V||(V=!0,Z?M(P,[A]):M(k,[A]),j.delayedLeave&&j.delayedLeave(),A[na]=void 0)};x?B(x,[A,$]):$()},leave(A,x){const k=String(e.key);if(A[na]&&A[na](!0),n.isUnmounting)return x();M(d,[A]);let P=!1;const V=A[Gr]=$=>{P||(P=!0,x(),$?M(g,[A]):M(m,[A]),A[Gr]=void 0,C[k]===e&&delete C[k])};C[k]=e,p?B(p,[A,V]):V()},clone(A){const x=os(A,t,n,r,i);return i&&i(x),x}};return j}function Nc(e){if(Ro(e))return e=gr(e),e.children=null,e}function Bd(e){if(!Ro(e))return Fm(e.type)&&e.children?Hm(e.children):e;if(e.component)return e.component.subTree;const{shapeFlag:t,children:n}=e;if(n){if(t&16)return n[0];if(t&32&&Pe(n.default))return n.default()}}function Vr(e,t){e.shapeFlag&6&&e.component?(e.transition=t,Vr(e.component.subTree,t)):e.shapeFlag&128?(e.ssContent.transition=t.clone(e.ssContent),e.ssFallback.transition=t.clone(e.ssFallback)):e.transition=t}function Hl(e,t=!1,n){let r=[],i=0;for(let s=0;s1)for(let s=0;sn.value,set:s=>n.value=s})}return n}function vo(e,t,n,r,i=!1){if(ye(e)){e.forEach((m,g)=>vo(m,t&&(ye(t)?t[g]:t),n,r,i));return}if(ni(r)&&!i){r.shapeFlag&512&&r.type.__asyncResolved&&r.component.subTree.component&&vo(e,t,n,r.component.subTree);return}const s=r.shapeFlag&4?ko(r.component):r.el,o=i?null:s,{i:a,r:l}=e,c=t&&t.r,u=a.refs===et?a.refs={}:a.refs,f=a.setupState,d=rt(f),p=f===et?()=>!1:m=>st(d,m);if(c!=null&&c!==l&&(ke(c)?(u[c]=null,p(c)&&(f[c]=null)):Ut(c)&&(c.value=null)),Pe(l))Es(l,a,12,[o,u]);else{const m=ke(l),g=Ut(l);if(m||g){const v=()=>{if(e.f){const w=m?p(l)?f[l]:u[l]:l.value;i?ye(w)&&_l(w,s):ye(w)?w.includes(s)||w.push(s):m?(u[l]=[s],p(l)&&(f[l]=u[l])):(l.value=[s],e.k&&(u[e.k]=l.value))}else m?(u[l]=o,p(l)&&(f[l]=o)):g&&(l.value=o,e.k&&(u[e.k]=o))};o?(v.id=-1,Bt(v,n)):v()}}}let jd=!1;const Ui=()=>{jd||(console.error("Hydration completed but contains mismatches."),jd=!0)},pS=e=>e.namespaceURI.includes("svg")&&e.tagName!=="foreignObject",mS=e=>e.namespaceURI.includes("MathML"),ra=e=>{if(e.nodeType===1){if(pS(e))return"svg";if(mS(e))return"mathml"}},Qi=e=>e.nodeType===8;function gS(e){const{mt:t,p:n,o:{patchProp:r,createText:i,nextSibling:s,parentNode:o,remove:a,insert:l,createComment:c}}=e,u=(y,E)=>{if(!E.hasChildNodes()){n(null,y,E),Za(),E._vnode=y;return}f(E.firstChild,y,null,null,null),Za(),E._vnode=y},f=(y,E,C,M,B,j=!1)=>{j=j||!!E.dynamicChildren;const A=Qi(y)&&y.data==="[",x=()=>g(y,E,C,M,B,A),{type:k,ref:P,shapeFlag:V,patchFlag:$}=E;let Z=y.nodeType;E.el=y,$===-2&&(j=!1,E.dynamicChildren=null);let U=null;switch(k){case ri:Z!==3?E.children===""?(l(E.el=i(""),o(y),y),U=y):U=x():(y.data!==E.children&&(Ui(),y.data=E.children),U=s(y));break;case Rt:S(y)?(U=s(y),w(E.el=y.content.firstChild,y,C)):Z!==8||A?U=x():U=s(y);break;case Oi:if(A&&(y=s(y),Z=y.nodeType),Z===1||Z===3){U=y;const W=!E.children.length;for(let ee=0;ee{j=j||!!E.dynamicChildren;const{type:A,props:x,patchFlag:k,shapeFlag:P,dirs:V,transition:$}=E,Z=A==="input"||A==="option";if(Z||k!==-1){V&&pr(E,null,C,"created");let U=!1;if(S(y)){U=mg(null,$)&&C&&C.vnode.props&&C.vnode.props.appear;const ee=y.content.firstChild;if(U){const Te=ee.getAttribute("class");Te&&(ee.$cls=Te),$.beforeEnter(ee)}w(ee,y,C),E.el=y=ee}if(P&16&&!(x&&(x.innerHTML||x.textContent))){let ee=p(y.firstChild,E,y,C,M,B,j);for(;ee;){ia(y,1)||Ui();const Te=ee;ee=ee.nextSibling,a(Te)}}else if(P&8){let ee=E.children;ee[0]===` `&&(y.tagName==="PRE"||y.tagName==="TEXTAREA")&&(ee=ee.slice(1)),y.textContent!==ee&&(ia(y,0)||Ui(),y.textContent=E.children)}if(x){if(Z||!j||k&48){const ee=y.tagName.includes("-");for(const Te in x)(Z&&(Te.endsWith("value")||Te==="indeterminate")||ai(Te)&&!xr(Te)||Te[0]==="."||ee)&&r(y,Te,null,x[Te],void 0,C)}else if(x.onClick)r(y,"onClick",null,x.onClick,void 0,C);else if(k&4&&ti(x.style))for(const ee in x.style)x.style[ee]}let W;(W=x&&x.onVnodeBeforeMount)&&_n(W,C,E),V&&pr(E,null,C,"beforeMount"),((W=x&&x.onVnodeMounted)||V||U)&&Dg(()=>{W&&_n(W,C,E),U&&$.enter(y),V&&pr(E,null,C,"mounted")},M)}return y.nextSibling},p=(y,E,C,M,B,j,A)=>{A=A||!!E.dynamicChildren;const x=E.children,k=x.length;for(let P=0;P{const{slotScopeIds:A}=E;A&&(B=B?B.concat(A):A);const x=o(y),k=p(s(y),E,x,C,M,B,j);return k&&Qi(k)&&k.data==="]"?s(E.anchor=k):(Ui(),l(E.anchor=c("]"),x,k),k)},g=(y,E,C,M,B,j)=>{if(ia(y.parentElement,1)||Ui(),E.el=null,j){const k=v(y);for(;;){const P=s(y);if(P&&P!==k)a(P);else break}}const A=s(y),x=o(y);return a(y),n(null,E,x,A,C,M,ra(x),B),C&&(C.vnode.el=E.el,Kl(C,E.el)),A},v=(y,E="[",C="]")=>{let M=0;for(;y;)if(y=s(y),y&&Qi(y)&&(y.data===E&&M++,y.data===C)){if(M===0)return s(y);M--}return y},w=(y,E,C)=>{const M=E.parentNode;M&&M.replaceChild(y,E);let B=C;for(;B;)B.vnode.el===E&&(B.vnode.el=B.subTree.el=y),B=B.parent},S=y=>y.nodeType===1&&y.tagName==="TEMPLATE";return[u,f]}const Hd="data-allow-mismatch",vS={0:"text",1:"children",2:"class",3:"style",4:"attribute"};function ia(e,t){if(t===0||t===1)for(;e&&!e.hasAttribute(Hd);)e=e.parentElement;const n=e&&e.getAttribute(Hd);if(n==null)return!1;if(n==="")return!0;{const r=n.split(",");return t===0&&r.includes("children")?!0:n.split(",").includes(vS[t])}}const yS=No().requestIdleCallback||(e=>setTimeout(e,1)),ES=No().cancelIdleCallback||(e=>clearTimeout(e)),bS=(e=1e4)=>t=>{const n=yS(t,{timeout:e});return()=>ES(n)};function SS(e){const{top:t,left:n,bottom:r,right:i}=e.getBoundingClientRect(),{innerHeight:s,innerWidth:o}=window;return(t>0&&t0&&r0&&n0&&i(t,n)=>{const r=new IntersectionObserver(i=>{for(const s of i)if(s.isIntersecting){r.disconnect(),t();break}},e);return n(i=>{if(i instanceof Element){if(SS(i))return t(),r.disconnect(),!1;r.observe(i)}}),()=>r.disconnect()},wS=e=>t=>{if(e){const n=matchMedia(e);if(n.matches)t();else return n.addEventListener("change",t,{once:!0}),()=>n.removeEventListener("change",t)}},DS=(e=[])=>(t,n)=>{ke(e)&&(e=[e]);let r=!1;const i=o=>{r||(r=!0,s(),t(),o.target.dispatchEvent(new o.constructor(o.type,o)))},s=()=>{n(o=>{for(const a of e)o.removeEventListener(a,i)})};return n(o=>{for(const a of e)o.addEventListener(a,i,{once:!0})}),s};function CS(e,t){if(Qi(e)&&e.data==="["){let n=1,r=e.nextSibling;for(;r;){if(r.nodeType===1){if(t(r)===!1)break}else if(Qi(r))if(r.data==="]"){if(--n===0)break}else r.data==="["&&n++;r=r.nextSibling}}else t(e)}const ni=e=>!!e.type.__asyncLoader;/*! #__NO_SIDE_EFFECTS__ */function OS(e){Pe(e)&&(e={loader:e});const{loader:t,loadingComponent:n,errorComponent:r,delay:i=200,hydrate:s,timeout:o,suspensible:a=!0,onError:l}=e;let c=null,u,f=0;const d=()=>(f++,c=null,p()),p=()=>{let m;return c||(m=c=t().catch(g=>{if(g=g instanceof Error?g:new Error(String(g)),l)return new Promise((v,w)=>{l(g,()=>v(d()),()=>w(g),f+1)});throw g}).then(g=>m!==c&&c?c:(g&&(g.__esModule||g[Symbol.toStringTag]==="Module")&&(g=g.default),u=g,g)))};return Cf({name:"AsyncComponentWrapper",__asyncLoader:p,__asyncHydrate(m,g,v){const w=s?()=>{const y=s(()=>{v()},E=>CS(m,E));y&&(g.bum||(g.bum=[])).push(y),(g.u||(g.u=[])).push(()=>!0)}:v;u?w():p().then(()=>!g.isUnmounted&&w())},get __asyncResolved(){return u},setup(){const m=jt;if(Of(m),u)return()=>Ic(u,m);const g=y=>{c=null,Bi(y,m,13,!r)};if(a&&m.suspense||as)return p().then(y=>()=>Ic(y,m)).catch(y=>(g(y),()=>r?yt(r,{error:y}):null));const v=Js(!1),w=Js(),S=Js(!!i);return i&&setTimeout(()=>{S.value=!1},i),o!=null&&setTimeout(()=>{if(!v.value&&!w.value){const y=new Error(`Async component timed out after ${o}ms.`);g(y),w.value=y}},o),p().then(()=>{v.value=!0,m.parent&&Ro(m.parent.vnode)&&m.parent.update()}).catch(y=>{g(y),w.value=y}),()=>{if(v.value&&u)return Ic(u,m);if(w.value&&r)return yt(r,{error:w.value});if(n&&!S.value)return yt(n)}}})}function Ic(e,t){const{ref:n,props:r,children:i,ce:s}=t.vnode,o=yt(e,r,i);return o.ref=n,o.ce=s,delete t.vnode.ce,o}const Ro=e=>e.type.__isKeepAlive,AS={name:"KeepAlive",__isKeepAlive:!0,props:{include:[String,RegExp,Array],exclude:[String,RegExp,Array],max:[String,Number]},setup(e,{slots:t}){const n=qn(),r=n.ctx;if(!r.renderer)return()=>{const S=t.default&&t.default();return S&&S.length===1?S[0]:S};const i=new Map,s=new Set;let o=null;const a=n.suspense,{renderer:{p:l,m:c,um:u,o:{createElement:f}}}=r,d=f("div");r.activate=(S,y,E,C,M)=>{const B=S.component;c(S,y,E,0,a),l(B.vnode,S,y,E,B,a,C,S.slotScopeIds,M),Bt(()=>{B.isDeactivated=!1,B.a&&wi(B.a);const j=S.props&&S.props.onVnodeMounted;j&&_n(j,B.parent,S)},a)},r.deactivate=S=>{const y=S.component;qa(y.m),qa(y.a),c(S,d,null,1,a),Bt(()=>{y.da&&wi(y.da);const E=S.props&&S.props.onVnodeUnmounted;E&&_n(E,y.parent,S),y.isDeactivated=!0},a)};function p(S){Rc(S),u(S,n,a,!0)}function m(S){i.forEach((y,E)=>{const C=Nu(y.type);C&&!S(C)&&g(E)})}function g(S){const y=i.get(S);y&&(!o||!or(y,o))?p(y):o&&Rc(o),i.delete(S),s.delete(S)}ts(()=>[e.include,e.exclude],([S,y])=>{S&&m(E=>Hs(S,E)),y&&m(E=>!Hs(y,E))},{flush:"post",deep:!0});let v=null;const w=()=>{v!=null&&(el(n.subTree.type)?Bt(()=>{i.set(v,sa(n.subTree))},n.subTree.suspense):i.set(v,sa(n.subTree)))};return Po(w),$l(w),Wl(()=>{i.forEach(S=>{const{subTree:y,suspense:E}=n,C=sa(y);if(S.type===C.type&&S.key===C.key){Rc(C);const M=C.component.da;M&&Bt(M,E);return}p(S)})}),()=>{if(v=null,!t.default)return o=null;const S=t.default(),y=S[0];if(S.length>1)return o=null,S;if(!Br(y)||!(y.shapeFlag&4)&&!(y.shapeFlag&128))return o=null,y;let E=sa(y);if(E.type===Rt)return o=null,E;const C=E.type,M=Nu(ni(E)?E.type.__asyncResolved||{}:C),{include:B,exclude:j,max:A}=e;if(B&&(!M||!Hs(B,M))||j&&M&&Hs(j,M))return E.shapeFlag&=-257,o=E,y;const x=E.key==null?C:E.key,k=i.get(x);return E.el&&(E=gr(E),y.shapeFlag&128&&(y.ssContent=E)),v=x,k?(E.el=k.el,E.component=k.component,E.transition&&Vr(E,E.transition),E.shapeFlag|=512,s.delete(x),s.add(x)):(s.add(x),A&&s.size>parseInt(A,10)&&g(s.values().next().value)),E.shapeFlag|=256,o=E,el(y.type)?y:E}}},_S=AS;function Hs(e,t){return ye(e)?e.some(n=>Hs(n,t)):ke(e)?e.split(",").includes(t):Kp(e)?(e.lastIndex=0,e.test(t)):!1}function Wm(e,t){zm(e,"a",t)}function Ym(e,t){zm(e,"da",t)}function zm(e,t,n=jt){const r=e.__wdc||(e.__wdc=()=>{let i=n;for(;i;){if(i.isDeactivated)return;i=i.parent}return e()});if(Ul(t,r,n),n){let i=n.parent;for(;i&&i.parent;)Ro(i.parent.vnode)&&MS(r,t,n,i),i=i.parent}}function MS(e,t,n,r){const i=Ul(t,e,r,!0);Yl(()=>{_l(r[t],i)},n)}function Rc(e){e.shapeFlag&=-257,e.shapeFlag&=-513}function sa(e){return e.shapeFlag&128?e.ssContent:e}function Ul(e,t,n=jt,r=!1){if(n){const i=n[e]||(n[e]=[]),s=t.__weh||(t.__weh=(...o)=>{Lr();const a=Ni(n),l=Jn(t,n,e,o);return a(),kr(),l});return r?i.unshift(s):i.push(s),s}}const jr=e=>(t,n=jt)=>{(!as||e==="sp")&&Ul(e,(...r)=>t(...r),n)},Km=jr("bm"),Po=jr("m"),Af=jr("bu"),$l=jr("u"),Wl=jr("bum"),Yl=jr("um"),Gm=jr("sp"),Xm=jr("rtg"),Jm=jr("rtc");function Zm(e,t=jt){Ul("ec",e,t)}const _f="components",xS="directives";function Qm(e,t){return Mf(_f,e,!0,t)||e}const qm=Symbol.for("v-ndc");function NS(e){return ke(e)?Mf(_f,e,!1)||e:e||qm}function IS(e){return Mf(xS,e)}function Mf(e,t,n=!0,r=!1){const i=Ht||jt;if(i){const s=i.type;if(e===_f){const a=Nu(s,!1);if(a&&(a===t||a===gt(t)||a===ci(gt(t))))return s}const o=Ud(i[e]||s[e],t)||Ud(i.appContext[e],t);return!o&&r?s:o}}function Ud(e,t){return e&&(e[t]||e[gt(t)]||e[ci(gt(t))])}function RS(e,t,n,r){let i;const s=n&&n[r],o=ye(e);if(o||ke(e)){const a=o&&ti(e);let l=!1,c=!1;a&&(l=!$n(e),c=Fr(e),e=Rl(e)),i=new Array(e.length);for(let u=0,f=e.length;ut(a,l,void 0,s&&s[l]));else{const a=Object.keys(e);i=new Array(a.length);for(let l=0,c=a.length;l{const s=r.fn(...i);return s&&(s.key=r.key),s}:r.fn)}return e}function LS(e,t,n={},r,i){if(Ht.ce||Ht.parent&&ni(Ht.parent)&&Ht.parent.ce)return t!=="default"&&(n.name=t),bo(),tl(Wt,null,[yt("slot",n,r&&r())],64);let s=e[t];s&&s._c&&(s._d=!1),bo();const o=s&&xf(s(n)),a=n.key||o&&o.key,l=tl(Wt,{key:(a&&!Sn(a)?a:`_${t}`)+(!o&&r?"_fb":"")},o||(r?r():[]),o&&e._===1?64:-2);return!i&&l.scopeId&&(l.slotScopeIds=[l.scopeId+"-s"]),s&&s._c&&(s._d=!0),l}function xf(e){return e.some(t=>Br(t)?!(t.type===Rt||t.type===Wt&&!xf(t.children)):!0)?e:null}function kS(e,t){const n={};for(const r in e)n[t&&/[A-Z]/.test(r)?`on:${r}`:Ti(r)]=e[r];return n}const bu=e=>e?Ng(e)?ko(e):bu(e.parent):null,Qs=Ze(Object.create(null),{$:e=>e,$el:e=>e.vnode.el,$data:e=>e.data,$props:e=>e.props,$attrs:e=>e.attrs,$slots:e=>e.slots,$refs:e=>e.refs,$parent:e=>bu(e.parent),$root:e=>bu(e.root),$host:e=>e.ce,$emit:e=>e.emit,$options:e=>Nf(e),$forceUpdate:e=>e.f||(e.f=()=>{Sf(e.update)}),$nextTick:e=>e.n||(e.n=Bl.bind(e.proxy)),$watch:e=>p0.bind(e)}),Pc=(e,t)=>e!==et&&!e.__isScriptSetup&&st(e,t),Su={get({_:e},t){if(t==="__v_skip")return!0;const{ctx:n,setupState:r,data:i,props:s,accessCache:o,type:a,appContext:l}=e;let c;if(t[0]!=="$"){const p=o[t];if(p!==void 0)switch(p){case 1:return r[t];case 2:return i[t];case 4:return n[t];case 3:return s[t]}else{if(Pc(r,t))return o[t]=1,r[t];if(i!==et&&st(i,t))return o[t]=2,i[t];if((c=e.propsOptions[0])&&st(c,t))return o[t]=3,s[t];if(n!==et&&st(n,t))return o[t]=4,n[t];Tu&&(o[t]=0)}}const u=Qs[t];let f,d;if(u)return t==="$attrs"&&an(e.attrs,"get",""),u(e);if((f=a.__cssModules)&&(f=f[t]))return f;if(n!==et&&st(n,t))return o[t]=4,n[t];if(d=l.config.globalProperties,st(d,t))return d[t]},set({_:e},t,n){const{data:r,setupState:i,ctx:s}=e;return Pc(i,t)?(i[t]=n,!0):r!==et&&st(r,t)?(r[t]=n,!0):st(e.props,t)||t[0]==="$"&&t.slice(1)in e?!1:(s[t]=n,!0)},has({_:{data:e,setupState:t,accessCache:n,ctx:r,appContext:i,propsOptions:s}},o){let a;return!!n[o]||e!==et&&st(e,o)||Pc(t,o)||(a=s[0])&&st(a,o)||st(r,o)||st(Qs,o)||st(i.config.globalProperties,o)},defineProperty(e,t,n){return n.get!=null?e._.accessCache[t]=0:st(n,"value")&&this.set(e,t,n.value,null),Reflect.defineProperty(e,t,n)}},FS=Ze({},Su,{get(e,t){if(t!==Symbol.unscopables)return Su.get(e,t,e)},has(e,t){return t[0]!=="_"&&!ff(t)}});function VS(){return null}function BS(){return null}function jS(e){}function HS(e){}function US(){return null}function $S(){}function WS(e,t){return null}function YS(){return eg().slots}function zS(){return eg().attrs}function eg(){const e=qn();return e.setupContext||(e.setupContext=Lg(e))}function yo(e){return ye(e)?e.reduce((t,n)=>(t[n]=null,t),{}):e}function KS(e,t){const n=yo(e);for(const r in t){if(r.startsWith("__skip"))continue;let i=n[r];i?ye(i)||Pe(i)?i=n[r]={type:i,default:t[r]}:i.default=t[r]:i===null&&(i=n[r]={default:t[r]}),i&&t[`__skip_${r}`]&&(i.skipFactory=!0)}return n}function GS(e,t){return!e||!t?e||t:ye(e)&&ye(t)?e.concat(t):Ze({},yo(e),yo(t))}function XS(e,t){const n={};for(const r in e)t.includes(r)||Object.defineProperty(n,r,{enumerable:!0,get:()=>e[r]});return n}function JS(e){const t=qn();let n=e();return _u(),Ml(n)&&(n=n.catch(r=>{throw Ni(t),r})),[n,()=>Ni(t)]}let Tu=!0;function ZS(e){const t=Nf(e),n=e.proxy,r=e.ctx;Tu=!1,t.beforeCreate&&$d(t.beforeCreate,e,"bc");const{data:i,computed:s,methods:o,watch:a,provide:l,inject:c,created:u,beforeMount:f,mounted:d,beforeUpdate:p,updated:m,activated:g,deactivated:v,beforeDestroy:w,beforeUnmount:S,destroyed:y,unmounted:E,render:C,renderTracked:M,renderTriggered:B,errorCaptured:j,serverPrefetch:A,expose:x,inheritAttrs:k,components:P,directives:V,filters:$}=t;if(c&&QS(c,r,null),o)for(const W in o){const ee=o[W];Pe(ee)&&(r[W]=ee.bind(n))}if(i){const W=i.call(n,n);ct(W)&&(e.data=Ll(W))}if(Tu=!0,s)for(const W in s){const ee=s[W],Te=Pe(ee)?ee.bind(n,n):Pe(ee.get)?ee.get.bind(n,n):Ft,ze=!Pe(ee)&&Pe(ee.set)?ee.set.bind(n):Ft,De=kg({get:Te,set:ze});Object.defineProperty(r,W,{enumerable:!0,configurable:!0,get:()=>De.value,set:$e=>De.value=$e})}if(a)for(const W in a)tg(a[W],r,n,W);if(l){const W=Pe(l)?l.call(n):l;Reflect.ownKeys(W).forEach(ee=>{rg(ee,W[ee])})}u&&$d(u,e,"c");function U(W,ee){ye(ee)?ee.forEach(Te=>W(Te.bind(n))):ee&&W(ee.bind(n))}if(U(Km,f),U(Po,d),U(Af,p),U($l,m),U(Wm,g),U(Ym,v),U(Zm,j),U(Jm,M),U(Xm,B),U(Wl,S),U(Yl,E),U(Gm,A),ye(x))if(x.length){const W=e.exposed||(e.exposed={});x.forEach(ee=>{Object.defineProperty(W,ee,{get:()=>n[ee],set:Te=>n[ee]=Te})})}else e.exposed||(e.exposed={});C&&e.render===Ft&&(e.render=C),k!=null&&(e.inheritAttrs=k),P&&(e.components=P),V&&(e.directives=V),A&&Of(e)}function QS(e,t,n=Ft){ye(e)&&(e=wu(e));for(const r in e){const i=e[r];let s;ct(i)?"default"in i?s=qs(i.from||r,i.default,!0):s=qs(i.from||r):s=qs(i),Ut(s)?Object.defineProperty(t,r,{enumerable:!0,configurable:!0,get:()=>s.value,set:o=>s.value=o}):t[r]=s}}function $d(e,t,n){Jn(ye(e)?e.map(r=>r.bind(t.proxy)):e.bind(t.proxy),t,n)}function tg(e,t,n,r){let i=r.includes(".")?bg(n,r):()=>n[r];if(ke(e)){const s=t[e];Pe(s)&&ts(i,s)}else if(Pe(e))ts(i,e.bind(n));else if(ct(e))if(ye(e))e.forEach(s=>tg(s,t,n,r));else{const s=Pe(e.handler)?e.handler.bind(n):t[e.handler];Pe(s)&&ts(i,s,e)}}function Nf(e){const t=e.type,{mixins:n,extends:r}=t,{mixins:i,optionsCache:s,config:{optionMergeStrategies:o}}=e.appContext,a=s.get(t);let l;return a?l=a:!i.length&&!n&&!r?l=t:(l={},i.length&&i.forEach(c=>Qa(l,c,o,!0)),Qa(l,t,o)),ct(t)&&s.set(t,l),l}function Qa(e,t,n,r=!1){const{mixins:i,extends:s}=t;s&&Qa(e,s,n,!0),i&&i.forEach(o=>Qa(e,o,n,!0));for(const o in t)if(!(r&&o==="expose")){const a=qS[o]||n&&n[o];e[o]=a?a(e[o],t[o]):t[o]}return e}const qS={data:Wd,props:Yd,emits:Yd,methods:Us,computed:Us,beforeCreate:mn,created:mn,beforeMount:mn,mounted:mn,beforeUpdate:mn,updated:mn,beforeDestroy:mn,beforeUnmount:mn,destroyed:mn,unmounted:mn,activated:mn,deactivated:mn,errorCaptured:mn,serverPrefetch:mn,components:Us,directives:Us,watch:t0,provide:Wd,inject:e0};function Wd(e,t){return t?e?function(){return Ze(Pe(e)?e.call(this,this):e,Pe(t)?t.call(this,this):t)}:t:e}function e0(e,t){return Us(wu(e),wu(t))}function wu(e){if(ye(e)){const t={};for(let n=0;n1)return n&&Pe(t)?t.call(r&&r.proxy):t}}function i0(){return!!(jt||Ht||Ci)}const ig={},sg=()=>Object.create(ig),og=e=>Object.getPrototypeOf(e)===ig;function s0(e,t,n,r=!1){const i={},s=sg();e.propsDefaults=Object.create(null),ag(e,t,i,s);for(const o in e.propsOptions[0])o in i||(i[o]=void 0);n?e.props=r?i:Dm(i):e.type.props?e.props=i:e.props=s,e.attrs=s}function o0(e,t,n,r){const{props:i,attrs:s,vnode:{patchFlag:o}}=e,a=rt(i),[l]=e.propsOptions;let c=!1;if((r||o>0)&&!(o&16)){if(o&8){const u=e.vnode.dynamicProps;for(let f=0;f{l=!0;const[d,p]=lg(f,t,!0);Ze(o,d),p&&a.push(...p)};!n&&t.mixins.length&&t.mixins.forEach(u),e.extends&&u(e.extends),e.mixins&&e.mixins.forEach(u)}if(!s&&!l)return ct(e)&&r.set(e,bi),bi;if(ye(s))for(let u=0;ue[0]==="_"||e==="$stable",Rf=e=>ye(e)?e.map(xn):[xn(e)],l0=(e,t,n)=>{if(t._n)return t;const r=Tf((...i)=>Rf(t(...i)),n);return r._c=!1,r},cg=(e,t,n)=>{const r=e._ctx;for(const i in e){if(If(i))continue;const s=e[i];if(Pe(s))t[i]=l0(i,s,r);else if(s!=null){const o=Rf(s);t[i]=()=>o}}},ug=(e,t)=>{const n=Rf(t);e.slots.default=()=>n},fg=(e,t,n)=>{for(const r in t)(n||!If(r))&&(e[r]=t[r])},c0=(e,t,n)=>{const r=e.slots=sg();if(e.vnode.shapeFlag&32){const i=t._;i?(fg(r,t,n),n&&uf(r,"_",i,!0)):cg(t,r)}else t&&ug(e,t)},u0=(e,t,n)=>{const{vnode:r,slots:i}=e;let s=!0,o=et;if(r.shapeFlag&32){const a=t._;a?n&&a===1?s=!1:fg(i,t,n):(s=!t.$stable,cg(t,i)),o=t}else t&&(ug(e,t),o={default:1});if(s)for(const a in i)!If(a)&&o[a]==null&&delete i[a]},Bt=Dg;function dg(e){return pg(e)}function hg(e){return pg(e,gS)}function pg(e,t){const n=No();n.__VUE__=!0;const{insert:r,remove:i,patchProp:s,createElement:o,createText:a,createComment:l,setText:c,setElementText:u,parentNode:f,nextSibling:d,setScopeId:p=Ft,insertStaticContent:m}=e,g=(_,N,Y,Q=null,J=null,q=null,se=void 0,ne=null,ie=!!N.dynamicChildren)=>{if(_===N)return;_&&!or(_,N)&&(Q=me(_),$e(_,J,q,!0),_=null),N.patchFlag===-2&&(ie=!1,N.dynamicChildren=null);const{type:te,ref:Se,shapeFlag:ce}=N;switch(te){case ri:v(_,N,Y,Q);break;case Rt:w(_,N,Y,Q);break;case Oi:_==null&&S(N,Y,Q,se);break;case Wt:P(_,N,Y,Q,J,q,se,ne,ie);break;default:ce&1?C(_,N,Y,Q,J,q,se,ne,ie):ce&6?V(_,N,Y,Q,J,q,se,ne,ie):(ce&64||ce&128)&&te.process(_,N,Y,Q,J,q,se,ne,ie,ut)}Se!=null&&J&&vo(Se,_&&_.ref,q,N||_,!N)},v=(_,N,Y,Q)=>{if(_==null)r(N.el=a(N.children),Y,Q);else{const J=N.el=_.el;N.children!==_.children&&c(J,N.children)}},w=(_,N,Y,Q)=>{_==null?r(N.el=l(N.children||""),Y,Q):N.el=_.el},S=(_,N,Y,Q)=>{[_.el,_.anchor]=m(_.children,N,Y,Q,_.el,_.anchor)},y=({el:_,anchor:N},Y,Q)=>{let J;for(;_&&_!==N;)J=d(_),r(_,Y,Q),_=J;r(N,Y,Q)},E=({el:_,anchor:N})=>{let Y;for(;_&&_!==N;)Y=d(_),i(_),_=Y;i(N)},C=(_,N,Y,Q,J,q,se,ne,ie)=>{N.type==="svg"?se="svg":N.type==="math"&&(se="mathml"),_==null?M(N,Y,Q,J,q,se,ne,ie):A(_,N,J,q,se,ne,ie)},M=(_,N,Y,Q,J,q,se,ne)=>{let ie,te;const{props:Se,shapeFlag:ce,transition:ge,dirs:we}=_;if(ie=_.el=o(_.type,q,Se&&Se.is,Se),ce&8?u(ie,_.children):ce&16&&j(_.children,ie,null,Q,J,Lc(_,q),se,ne),we&&pr(_,null,Q,"created"),B(ie,_,_.scopeId,se,Q),Se){for(const Xe in Se)Xe!=="value"&&!xr(Xe)&&s(ie,Xe,null,Se[Xe],q,Q);"value"in Se&&s(ie,"value",null,Se.value,q),(te=Se.onVnodeBeforeMount)&&_n(te,Q,_)}we&&pr(_,null,Q,"beforeMount");const Fe=mg(J,ge);Fe&&ge.beforeEnter(ie),r(ie,N,Y),((te=Se&&Se.onVnodeMounted)||Fe||we)&&Bt(()=>{te&&_n(te,Q,_),Fe&&ge.enter(ie),we&&pr(_,null,Q,"mounted")},J)},B=(_,N,Y,Q,J)=>{if(Y&&p(_,Y),Q)for(let q=0;q{for(let te=ie;te<_.length;te++){const Se=_[te]=ne?Xr(_[te]):xn(_[te]);g(null,Se,N,Y,Q,J,q,se,ne)}},A=(_,N,Y,Q,J,q,se)=>{const ne=N.el=_.el;let{patchFlag:ie,dynamicChildren:te,dirs:Se}=N;ie|=_.patchFlag&16;const ce=_.props||et,ge=N.props||et;let we;if(Y&&ui(Y,!1),(we=ge.onVnodeBeforeUpdate)&&_n(we,Y,N,_),Se&&pr(N,_,Y,"beforeUpdate"),Y&&ui(Y,!0),(ce.innerHTML&&ge.innerHTML==null||ce.textContent&&ge.textContent==null)&&u(ne,""),te?x(_.dynamicChildren,te,ne,Y,Q,Lc(N,J),q):se||ee(_,N,ne,null,Y,Q,Lc(N,J),q,!1),ie>0){if(ie&16)k(ne,ce,ge,Y,J);else if(ie&2&&ce.class!==ge.class&&s(ne,"class",null,ge.class,J),ie&4&&s(ne,"style",ce.style,ge.style,J),ie&8){const Fe=N.dynamicProps;for(let Xe=0;Xe{we&&_n(we,Y,N,_),Se&&pr(N,_,Y,"updated")},Q)},x=(_,N,Y,Q,J,q,se)=>{for(let ne=0;ne{if(N!==Y){if(N!==et)for(const q in N)!xr(q)&&!(q in Y)&&s(_,q,N[q],null,J,Q);for(const q in Y){if(xr(q))continue;const se=Y[q],ne=N[q];se!==ne&&q!=="value"&&s(_,q,ne,se,J,Q)}"value"in Y&&s(_,"value",N.value,Y.value,J)}},P=(_,N,Y,Q,J,q,se,ne,ie)=>{const te=N.el=_?_.el:a(""),Se=N.anchor=_?_.anchor:a("");let{patchFlag:ce,dynamicChildren:ge,slotScopeIds:we}=N;we&&(ne=ne?ne.concat(we):we),_==null?(r(te,Y,Q),r(Se,Y,Q),j(N.children||[],Y,Se,J,q,se,ne,ie)):ce>0&&ce&64&&ge&&_.dynamicChildren?(x(_.dynamicChildren,ge,Y,J,q,se,ne),(N.key!=null||J&&N===J.subTree)&&Pf(_,N,!0)):ee(_,N,Y,Se,J,q,se,ne,ie)},V=(_,N,Y,Q,J,q,se,ne,ie)=>{N.slotScopeIds=ne,_==null?N.shapeFlag&512?J.ctx.activate(N,Y,Q,se,ie):$(N,Y,Q,J,q,se,ie):Z(_,N,ie)},$=(_,N,Y,Q,J,q,se)=>{const ne=_.component=xg(_,Q,J);if(Ro(_)&&(ne.ctx.renderer=ut),Ig(ne,!1,se),ne.asyncDep){if(J&&J.registerDep(ne,U,se),!_.el){const ie=ne.subTree=yt(Rt);w(null,ie,N,Y)}}else U(ne,_,N,Y,J,q,se)},Z=(_,N,Y)=>{const Q=N.component=_.component;if(b0(_,N,Y))if(Q.asyncDep&&!Q.asyncResolved){W(Q,N,Y);return}else Q.next=N,Q.update();else N.el=_.el,Q.vnode=N},U=(_,N,Y,Q,J,q,se)=>{const ne=()=>{if(_.isMounted){let{next:ce,bu:ge,u:we,parent:Fe,vnode:Xe}=_;{const F=gg(_);if(F){ce&&(ce.el=Xe.el,W(_,ce,se)),F.asyncDep.then(()=>{_.isUnmounted||ne()});return}}let L=ce,H;ui(_,!1),ce?(ce.el=Xe.el,W(_,ce,se)):ce=Xe,ge&&wi(ge),(H=ce.props&&ce.props.onVnodeBeforeUpdate)&&_n(H,Fe,ce,Xe),ui(_,!0);const T=Aa(_),O=_.subTree;_.subTree=T,g(O,T,f(O.el),me(O),_,J,q),ce.el=T.el,L===null&&Kl(_,T.el),we&&Bt(we,J),(H=ce.props&&ce.props.onVnodeUpdated)&&Bt(()=>_n(H,Fe,ce,Xe),J)}else{let ce;const{el:ge,props:we}=N,{bm:Fe,m:Xe,parent:L,root:H,type:T}=_,O=ni(N);if(ui(_,!1),Fe&&wi(Fe),!O&&(ce=we&&we.onVnodeBeforeMount)&&_n(ce,L,N),ui(_,!0),ge&&Qe){const F=()=>{_.subTree=Aa(_),Qe(ge,_.subTree,_,J,null)};O&&T.__asyncHydrate?T.__asyncHydrate(ge,_,F):F()}else{H.ce&&H.ce._injectChildStyle(T);const F=_.subTree=Aa(_);g(null,F,Y,Q,_,J,q),N.el=F.el}if(Xe&&Bt(Xe,J),!O&&(ce=we&&we.onVnodeMounted)){const F=N;Bt(()=>_n(ce,L,F),J)}(N.shapeFlag&256||L&&ni(L.vnode)&&L.vnode.shapeFlag&256)&&_.a&&Bt(_.a,J),_.isMounted=!0,N=Y=Q=null}};_.scope.on();const ie=_.effect=new uo(ne);_.scope.off();const te=_.update=ie.run.bind(ie),Se=_.job=ie.runIfDirty.bind(ie);Se.i=_,Se.id=_.uid,ie.scheduler=()=>Sf(Se),ui(_,!0),te()},W=(_,N,Y)=>{N.component=_;const Q=_.vnode.props;_.vnode=N,_.next=null,o0(_,N.props,Q,Y),u0(_,N.children,Y),Lr(),Ld(_),kr()},ee=(_,N,Y,Q,J,q,se,ne,ie=!1)=>{const te=_&&_.children,Se=_?_.shapeFlag:0,ce=N.children,{patchFlag:ge,shapeFlag:we}=N;if(ge>0){if(ge&128){ze(te,ce,Y,Q,J,q,se,ne,ie);return}else if(ge&256){Te(te,ce,Y,Q,J,q,se,ne,ie);return}}we&8?(Se&16&&re(te,J,q),ce!==te&&u(Y,ce)):Se&16?we&16?ze(te,ce,Y,Q,J,q,se,ne,ie):re(te,J,q,!0):(Se&8&&u(Y,""),we&16&&j(ce,Y,Q,J,q,se,ne,ie))},Te=(_,N,Y,Q,J,q,se,ne,ie)=>{_=_||bi,N=N||bi;const te=_.length,Se=N.length,ce=Math.min(te,Se);let ge;for(ge=0;geSe?re(_,J,q,!0,!1,ce):j(N,Y,Q,J,q,se,ne,ie,ce)},ze=(_,N,Y,Q,J,q,se,ne,ie)=>{let te=0;const Se=N.length;let ce=_.length-1,ge=Se-1;for(;te<=ce&&te<=ge;){const we=_[te],Fe=N[te]=ie?Xr(N[te]):xn(N[te]);if(or(we,Fe))g(we,Fe,Y,null,J,q,se,ne,ie);else break;te++}for(;te<=ce&&te<=ge;){const we=_[ce],Fe=N[ge]=ie?Xr(N[ge]):xn(N[ge]);if(or(we,Fe))g(we,Fe,Y,null,J,q,se,ne,ie);else break;ce--,ge--}if(te>ce){if(te<=ge){const we=ge+1,Fe=wege)for(;te<=ce;)$e(_[te],J,q,!0),te++;else{const we=te,Fe=te,Xe=new Map;for(te=Fe;te<=ge;te++){const oe=N[te]=ie?Xr(N[te]):xn(N[te]);oe.key!=null&&Xe.set(oe.key,te)}let L,H=0;const T=ge-Fe+1;let O=!1,F=0;const G=new Array(T);for(te=0;te=T){$e(oe,J,q,!0);continue}let ae;if(oe.key!=null)ae=Xe.get(oe.key);else for(L=Fe;L<=ge;L++)if(G[L-Fe]===0&&or(oe,N[L])){ae=L;break}ae===void 0?$e(oe,J,q,!0):(G[ae-Fe]=te+1,ae>=F?F=ae:O=!0,g(oe,N[ae],Y,null,J,q,se,ne,ie),H++)}const X=O?f0(G):bi;for(L=X.length-1,te=T-1;te>=0;te--){const oe=Fe+te,ae=N[oe],he=oe+1{const{el:q,type:se,transition:ne,children:ie,shapeFlag:te}=_;if(te&6){De(_.component.subTree,N,Y,Q);return}if(te&128){_.suspense.move(N,Y,Q);return}if(te&64){se.move(_,N,Y,ut);return}if(se===Wt){r(q,N,Y);for(let ce=0;cene.enter(q),J);else{const{leave:ce,delayLeave:ge,afterLeave:we}=ne,Fe=()=>{_.ctx.isUnmounted?i(q):r(q,N,Y)},Xe=()=>{ce(q,()=>{Fe(),we&&we()})};ge?ge(q,Fe,Xe):Xe()}else r(q,N,Y)},$e=(_,N,Y,Q=!1,J=!1)=>{const{type:q,props:se,ref:ne,children:ie,dynamicChildren:te,shapeFlag:Se,patchFlag:ce,dirs:ge,cacheIndex:we}=_;if(ce===-2&&(J=!1),ne!=null&&(Lr(),vo(ne,null,Y,_,!0),kr()),we!=null&&(N.renderCache[we]=void 0),Se&256){N.ctx.deactivate(_);return}const Fe=Se&1&&ge,Xe=!ni(_);let L;if(Xe&&(L=se&&se.onVnodeBeforeUnmount)&&_n(L,N,_),Se&6)Ie(_.component,Y,Q);else{if(Se&128){_.suspense.unmount(Y,Q);return}Fe&&pr(_,null,N,"beforeUnmount"),Se&64?_.type.remove(_,N,Y,ut,Q):te&&!te.hasOnce&&(q!==Wt||ce>0&&ce&64)?re(te,N,Y,!1,!0):(q===Wt&&ce&384||!J&&Se&16)&&re(ie,N,Y),Q&&Ve(_)}(Xe&&(L=se&&se.onVnodeUnmounted)||Fe)&&Bt(()=>{L&&_n(L,N,_),Fe&&pr(_,null,N,"unmounted")},Y)},Ve=_=>{const{type:N,el:Y,anchor:Q,transition:J}=_;if(N===Wt){Ke(Y,Q);return}if(N===Oi){E(_);return}const q=()=>{i(Y),J&&!J.persisted&&J.afterLeave&&J.afterLeave()};if(_.shapeFlag&1&&J&&!J.persisted){const{leave:se,delayLeave:ne}=J,ie=()=>se(Y,q);ne?ne(_.el,q,ie):ie()}else q()},Ke=(_,N)=>{let Y;for(;_!==N;)Y=d(_),i(_),_=Y;i(N)},Ie=(_,N,Y)=>{const{bum:Q,scope:J,job:q,subTree:se,um:ne,m:ie,a:te,parent:Se,slots:{__:ce}}=_;qa(ie),qa(te),Q&&wi(Q),Se&&ye(ce)&&ce.forEach(ge=>{Se.renderCache[ge]=void 0}),J.stop(),q&&(q.flags|=8,$e(se,_,N,Y)),ne&&Bt(ne,N),Bt(()=>{_.isUnmounted=!0},N),N&&N.pendingBranch&&!N.isUnmounted&&_.asyncDep&&!_.asyncResolved&&_.suspenseId===N.pendingId&&(N.deps--,N.deps===0&&N.resolve())},re=(_,N,Y,Q=!1,J=!1,q=0)=>{for(let se=q;se<_.length;se++)$e(_[se],N,Y,Q,J)},me=_=>{if(_.shapeFlag&6)return me(_.component.subTree);if(_.shapeFlag&128)return _.suspense.next();const N=d(_.anchor||_.el),Y=N&&N[km];return Y?d(Y):N};let xe=!1;const Ne=(_,N,Y)=>{_==null?N._vnode&&$e(N._vnode,null,null,!0):g(N._vnode||null,_,N,null,null,null,Y),N._vnode=_,xe||(xe=!0,Ld(),Za(),xe=!1)},ut={p:g,um:$e,m:De,r:Ve,mt:$,mc:j,pc:ee,pbc:x,n:me,o:e};let dt,Qe;return t&&([dt,Qe]=t(ut)),{render:Ne,hydrate:dt,createApp:r0(Ne,dt)}}function Lc({type:e,props:t},n){return n==="svg"&&e==="foreignObject"||n==="mathml"&&e==="annotation-xml"&&t&&t.encoding&&t.encoding.includes("html")?void 0:n}function ui({effect:e,job:t},n){n?(e.flags|=32,t.flags|=4):(e.flags&=-33,t.flags&=-5)}function mg(e,t){return(!e||e&&!e.pendingBranch)&&t&&!t.persisted}function Pf(e,t,n=!1){const r=e.children,i=t.children;if(ye(r)&&ye(i))for(let s=0;s>1,e[n[a]]0&&(t[r]=n[s-1]),n[s]=r)}}for(s=n.length,o=n[s-1];s-- >0;)n[s]=o,o=t[o];return n}function gg(e){const t=e.subTree.component;if(t)return t.asyncDep&&!t.asyncResolved?t:gg(t)}function qa(e){if(e)for(let t=0;tqs(vg);function d0(e,t){return Lo(e,null,t)}function h0(e,t){return Lo(e,null,{flush:"post"})}function Eg(e,t){return Lo(e,null,{flush:"sync"})}function ts(e,t,n){return Lo(e,t,n)}function Lo(e,t,n=et){const{immediate:r,deep:i,flush:s,once:o}=n,a=Ze({},n),l=t&&r||!t&&s!=="post";let c;if(as){if(s==="sync"){const p=yg();c=p.__watcherHandles||(p.__watcherHandles=[])}else if(!l){const p=()=>{};return p.stop=Ft,p.resume=Ft,p.pause=Ft,p}}const u=jt;a.call=(p,m,g)=>Jn(p,u,m,g);let f=!1;s==="post"?a.scheduler=p=>{Bt(p,u&&u.suspense)}:s!=="sync"&&(f=!0,a.scheduler=(p,m)=>{m?p():Sf(p)}),a.augmentJob=p=>{t&&(p.flags|=4),f&&(p.flags|=2,u&&(p.id=u.uid,p.i=u))};const d=Zb(e,t,a);return as&&(c?c.push(d):l&&d()),d}function p0(e,t,n){const r=this.proxy,i=ke(e)?e.includes(".")?bg(r,e):()=>r[e]:e.bind(r,r);let s;Pe(t)?s=t:(s=t.handler,n=t);const o=Ni(this),a=Lo(i,s.bind(r),n);return o(),a}function bg(e,t){const n=t.split(".");return()=>{let r=e;for(let i=0;i{let u,f=et,d;return Eg(()=>{const p=e[i];on(u,p)&&(u=p,c())}),{get(){return l(),n.get?n.get(u):u},set(p){const m=n.set?n.set(p):p;if(!on(m,u)&&!(f!==et&&on(p,f)))return;const g=r.vnode.props;g&&(t in g||i in g||s in g)&&(`onUpdate:${t}`in g||`onUpdate:${i}`in g||`onUpdate:${s}`in g)||(u=p,c()),r.emit(`update:${t}`,m),on(p,m)&&on(p,f)&&!on(m,d)&&c(),f=p,d=m}}});return a[Symbol.iterator]=()=>{let l=0;return{next(){return l<2?{value:l++?o||et:a,done:!1}:{done:!0}}}},a}const Sg=(e,t)=>t==="modelValue"||t==="model-value"?e.modelModifiers:e[`${t}Modifiers`]||e[`${gt(t)}Modifiers`]||e[`${ln(t)}Modifiers`];function g0(e,t,...n){if(e.isUnmounted)return;const r=e.vnode.props||et;let i=n;const s=t.startsWith("update:"),o=s&&Sg(r,t.slice(7));o&&(o.trim&&(i=n.map(u=>ke(u)?u.trim():u)),o.number&&(i=n.map(lo)));let a,l=r[a=Ti(t)]||r[a=Ti(gt(t))];!l&&s&&(l=r[a=Ti(ln(t))]),l&&Jn(l,e,6,i);const c=r[a+"Once"];if(c){if(!e.emitted)e.emitted={};else if(e.emitted[a])return;e.emitted[a]=!0,Jn(c,e,6,i)}}function Tg(e,t,n=!1){const r=t.emitsCache,i=r.get(e);if(i!==void 0)return i;const s=e.emits;let o={},a=!1;if(!Pe(e)){const l=c=>{const u=Tg(c,t,!0);u&&(a=!0,Ze(o,u))};!n&&t.mixins.length&&t.mixins.forEach(l),e.extends&&l(e.extends),e.mixins&&e.mixins.forEach(l)}return!s&&!a?(ct(e)&&r.set(e,null),null):(ye(s)?s.forEach(l=>o[l]=null):Ze(o,s),ct(e)&&r.set(e,o),o)}function zl(e,t){return!e||!ai(t)?!1:(t=t.slice(2).replace(/Once$/,""),st(e,t[0].toLowerCase()+t.slice(1))||st(e,ln(t))||st(e,t))}function Aa(e){const{type:t,vnode:n,proxy:r,withProxy:i,propsOptions:[s],slots:o,attrs:a,emit:l,render:c,renderCache:u,props:f,data:d,setupState:p,ctx:m,inheritAttrs:g}=e,v=go(e);let w,S;try{if(n.shapeFlag&4){const E=i||r,C=E;w=xn(c.call(C,E,u,f,p,d,m)),S=a}else{const E=t;w=xn(E.length>1?E(f,{attrs:a,slots:o,emit:l}):E(f,null)),S=t.props?a:y0(a)}}catch(E){eo.length=0,Bi(E,e,1),w=yt(Rt)}let y=w;if(S&&g!==!1){const E=Object.keys(S),{shapeFlag:C}=y;E.length&&C&7&&(s&&E.some(Al)&&(S=E0(S,s)),y=gr(y,S,!1,!0))}return n.dirs&&(y=gr(y,null,!1,!0),y.dirs=y.dirs?y.dirs.concat(n.dirs):n.dirs),n.transition&&Vr(y,n.transition),w=y,go(v),w}function v0(e,t=!0){let n;for(let r=0;r{let t;for(const n in e)(n==="class"||n==="style"||ai(n))&&((t||(t={}))[n]=e[n]);return t},E0=(e,t)=>{const n={};for(const r in e)(!Al(r)||!(r.slice(9)in t))&&(n[r]=e[r]);return n};function b0(e,t,n){const{props:r,children:i,component:s}=e,{props:o,children:a,patchFlag:l}=t,c=s.emitsOptions;if(t.dirs||t.transition)return!0;if(n&&l>=0){if(l&1024)return!0;if(l&16)return r?Kd(r,o,c):!!o;if(l&8){const u=t.dynamicProps;for(let f=0;fe.__isSuspense;let Cu=0;const S0={name:"Suspense",__isSuspense:!0,process(e,t,n,r,i,s,o,a,l,c){if(e==null)w0(t,n,r,i,s,o,a,l,c);else{if(s&&s.deps>0&&!e.suspense.isInFallback){t.suspense=e.suspense,t.suspense.vnode=t,t.el=e.el;return}D0(e,t,n,r,i,o,a,l,c)}},hydrate:C0,normalize:O0},T0=S0;function Eo(e,t){const n=e.props&&e.props[t];Pe(n)&&n()}function w0(e,t,n,r,i,s,o,a,l){const{p:c,o:{createElement:u}}=l,f=u("div"),d=e.suspense=wg(e,i,r,t,f,n,s,o,a,l);c(null,d.pendingBranch=e.ssContent,f,null,r,d,s,o),d.deps>0?(Eo(e,"onPending"),Eo(e,"onFallback"),c(null,e.ssFallback,t,n,r,null,s,o),ns(d,e.ssFallback)):d.resolve(!1,!0)}function D0(e,t,n,r,i,s,o,a,{p:l,um:c,o:{createElement:u}}){const f=t.suspense=e.suspense;f.vnode=t,t.el=e.el;const d=t.ssContent,p=t.ssFallback,{activeBranch:m,pendingBranch:g,isInFallback:v,isHydrating:w}=f;if(g)f.pendingBranch=d,or(d,g)?(l(g,d,f.hiddenContainer,null,i,f,s,o,a),f.deps<=0?f.resolve():v&&(w||(l(m,p,n,r,i,null,s,o,a),ns(f,p)))):(f.pendingId=Cu++,w?(f.isHydrating=!1,f.activeBranch=g):c(g,i,f),f.deps=0,f.effects.length=0,f.hiddenContainer=u("div"),v?(l(null,d,f.hiddenContainer,null,i,f,s,o,a),f.deps<=0?f.resolve():(l(m,p,n,r,i,null,s,o,a),ns(f,p))):m&&or(d,m)?(l(m,d,n,r,i,f,s,o,a),f.resolve(!0)):(l(null,d,f.hiddenContainer,null,i,f,s,o,a),f.deps<=0&&f.resolve()));else if(m&&or(d,m))l(m,d,n,r,i,f,s,o,a),ns(f,d);else if(Eo(t,"onPending"),f.pendingBranch=d,d.shapeFlag&512?f.pendingId=d.component.suspenseId:f.pendingId=Cu++,l(null,d,f.hiddenContainer,null,i,f,s,o,a),f.deps<=0)f.resolve();else{const{timeout:S,pendingId:y}=f;S>0?setTimeout(()=>{f.pendingId===y&&f.fallback(p)},S):S===0&&f.fallback(p)}}function wg(e,t,n,r,i,s,o,a,l,c,u=!1){const{p:f,m:d,um:p,n:m,o:{parentNode:g,remove:v}}=c;let w;const S=A0(e);S&&t&&t.pendingBranch&&(w=t.pendingId,t.deps++);const y=e.props?co(e.props.timeout):void 0,E=s,C={vnode:e,parent:t,parentComponent:n,namespace:o,container:r,hiddenContainer:i,deps:0,pendingId:Cu++,timeout:typeof y=="number"?y:-1,activeBranch:null,pendingBranch:null,isInFallback:!u,isHydrating:u,isUnmounted:!1,effects:[],resolve(M=!1,B=!1){const{vnode:j,activeBranch:A,pendingBranch:x,pendingId:k,effects:P,parentComponent:V,container:$}=C;let Z=!1;C.isHydrating?C.isHydrating=!1:M||(Z=A&&x.transition&&x.transition.mode==="out-in",Z&&(A.transition.afterLeave=()=>{k===C.pendingId&&(d(x,$,s===E?m(A):s,0),po(P))}),A&&(g(A.el)===$&&(s=m(A)),p(A,V,C,!0)),Z||d(x,$,s,0)),ns(C,x),C.pendingBranch=null,C.isInFallback=!1;let U=C.parent,W=!1;for(;U;){if(U.pendingBranch){U.effects.push(...P),W=!0;break}U=U.parent}!W&&!Z&&po(P),C.effects=[],S&&t&&t.pendingBranch&&w===t.pendingId&&(t.deps--,t.deps===0&&!B&&t.resolve()),Eo(j,"onResolve")},fallback(M){if(!C.pendingBranch)return;const{vnode:B,activeBranch:j,parentComponent:A,container:x,namespace:k}=C;Eo(B,"onFallback");const P=m(j),V=()=>{C.isInFallback&&(f(null,M,x,P,A,null,k,a,l),ns(C,M))},$=M.transition&&M.transition.mode==="out-in";$&&(j.transition.afterLeave=V),C.isInFallback=!0,p(j,A,null,!0),$||V()},move(M,B,j){C.activeBranch&&d(C.activeBranch,M,B,j),C.container=M},next(){return C.activeBranch&&m(C.activeBranch)},registerDep(M,B,j){const A=!!C.pendingBranch;A&&C.deps++;const x=M.vnode.el;M.asyncDep.catch(k=>{Bi(k,M,0)}).then(k=>{if(M.isUnmounted||C.isUnmounted||C.pendingId!==M.suspenseId)return;M.asyncResolved=!0;const{vnode:P}=M;Mu(M,k,!1),x&&(P.el=x);const V=!x&&M.subTree.el;B(M,P,g(x||M.subTree.el),x?null:m(M.subTree),C,o,j),V&&v(V),Kl(M,P.el),A&&--C.deps===0&&C.resolve()})},unmount(M,B){C.isUnmounted=!0,C.activeBranch&&p(C.activeBranch,n,M,B),C.pendingBranch&&p(C.pendingBranch,n,M,B)}};return C}function C0(e,t,n,r,i,s,o,a,l){const c=t.suspense=wg(t,r,n,e.parentNode,document.createElement("div"),null,i,s,o,a,!0),u=l(e,c.pendingBranch=t.ssContent,n,c,s,o);return c.deps===0&&c.resolve(!1,!0),u}function O0(e){const{shapeFlag:t,children:n}=e,r=t&32;e.ssContent=Gd(r?n.default:n),e.ssFallback=r?Gd(n.fallback):yt(Rt)}function Gd(e){let t;if(Pe(e)){const n=xi&&e._c;n&&(e._d=!1,bo()),e=e(),n&&(e._d=!0,t=fn,Cg())}return ye(e)&&(e=v0(e)),e=xn(e),t&&!e.dynamicChildren&&(e.dynamicChildren=t.filter(n=>n!==e)),e}function Dg(e,t){t&&t.pendingBranch?ye(e)?t.effects.push(...e):t.effects.push(e):po(e)}function ns(e,t){e.activeBranch=t;const{vnode:n,parentComponent:r}=e;let i=t.el;for(;!i&&t.component;)t=t.component.subTree,i=t.el;n.el=i,r&&r.subTree===n&&(r.vnode.el=i,Kl(r,i))}function A0(e){const t=e.props&&e.props.suspensible;return t!=null&&t!==!1}const Wt=Symbol.for("v-fgt"),ri=Symbol.for("v-txt"),Rt=Symbol.for("v-cmt"),Oi=Symbol.for("v-stc"),eo=[];let fn=null;function bo(e=!1){eo.push(fn=e?null:[])}function Cg(){eo.pop(),fn=eo[eo.length-1]||null}let xi=1;function Ou(e,t=!1){xi+=e,e<0&&fn&&t&&(fn.hasOnce=!0)}function Og(e){return e.dynamicChildren=xi>0?fn||bi:null,Cg(),xi>0&&fn&&fn.push(e),e}function _0(e,t,n,r,i,s){return Og(Lf(e,t,n,r,i,s,!0))}function tl(e,t,n,r,i){return Og(yt(e,t,n,r,i,!0))}function Br(e){return e?e.__v_isVNode===!0:!1}function or(e,t){return e.type===t.type&&e.key===t.key}function M0(e){}const Ag=({key:e})=>e??null,_a=({ref:e,ref_key:t,ref_for:n})=>(typeof e=="number"&&(e=""+e),e!=null?ke(e)||Ut(e)||Pe(e)?{i:Ht,r:e,k:t,f:!!n}:e:null);function Lf(e,t=null,n=null,r=0,i=null,s=e===Wt?0:1,o=!1,a=!1){const l={__v_isVNode:!0,__v_skip:!0,type:e,props:t,key:t&&Ag(t),ref:t&&_a(t),scopeId:jl,slotScopeIds:null,children:n,component:null,suspense:null,ssContent:null,ssFallback:null,dirs:null,transition:null,el:null,anchor:null,target:null,targetStart:null,targetAnchor:null,staticCount:0,shapeFlag:s,patchFlag:r,dynamicProps:i,dynamicChildren:null,appContext:null,ctx:Ht};return a?(Ff(l,n),s&128&&e.normalize(l)):n&&(l.shapeFlag|=ke(n)?8:16),xi>0&&!o&&fn&&(l.patchFlag>0||s&6)&&l.patchFlag!==32&&fn.push(l),l}const yt=x0;function x0(e,t=null,n=null,r=0,i=null,s=!1){if((!e||e===qm)&&(e=Rt),Br(e)){const a=gr(e,t,!0);return n&&Ff(a,n),xi>0&&!s&&fn&&(a.shapeFlag&6?fn[fn.indexOf(e)]=a:fn.push(a)),a.patchFlag=-2,a}if(V0(e)&&(e=e.__vccOpts),t){t=_g(t);let{class:a,style:l}=t;a&&!ke(a)&&(t.class=ys(a)),ct(l)&&(Fl(l)&&!ye(l)&&(l=Ze({},l)),t.style=vs(l))}const o=ke(e)?1:el(e)?128:Fm(e)?64:ct(e)?4:Pe(e)?2:0;return Lf(e,t,n,r,i,o,s,!0)}function _g(e){return e?Fl(e)||og(e)?Ze({},e):e:null}function gr(e,t,n=!1,r=!1){const{props:i,ref:s,patchFlag:o,children:a,transition:l}=e,c=t?Mg(i||{},t):i,u={__v_isVNode:!0,__v_skip:!0,type:e.type,props:c,key:c&&Ag(c),ref:t&&t.ref?n&&s?ye(s)?s.concat(_a(t)):[s,_a(t)]:_a(t):s,scopeId:e.scopeId,slotScopeIds:e.slotScopeIds,children:a,target:e.target,targetStart:e.targetStart,targetAnchor:e.targetAnchor,staticCount:e.staticCount,shapeFlag:e.shapeFlag,patchFlag:t&&e.type!==Wt?o===-1?16:o|16:o,dynamicProps:e.dynamicProps,dynamicChildren:e.dynamicChildren,appContext:e.appContext,dirs:e.dirs,transition:l,component:e.component,suspense:e.suspense,ssContent:e.ssContent&&gr(e.ssContent),ssFallback:e.ssFallback&&gr(e.ssFallback),el:e.el,anchor:e.anchor,ctx:e.ctx,ce:e.ce};return l&&r&&Vr(u,l.clone(u)),u}function kf(e=" ",t=0){return yt(ri,null,e,t)}function N0(e,t){const n=yt(Oi,null,e);return n.staticCount=t,n}function I0(e="",t=!1){return t?(bo(),tl(Rt,null,e)):yt(Rt,null,e)}function xn(e){return e==null||typeof e=="boolean"?yt(Rt):ye(e)?yt(Wt,null,e.slice()):Br(e)?Xr(e):yt(ri,null,String(e))}function Xr(e){return e.el===null&&e.patchFlag!==-1||e.memo?e:gr(e)}function Ff(e,t){let n=0;const{shapeFlag:r}=e;if(t==null)t=null;else if(ye(t))n=16;else if(typeof t=="object")if(r&65){const i=t.default;i&&(i._c&&(i._d=!1),Ff(e,i()),i._c&&(i._d=!0));return}else{n=32;const i=t._;!i&&!og(t)?t._ctx=Ht:i===3&&Ht&&(Ht.slots._===1?t._=1:(t._=2,e.patchFlag|=1024))}else Pe(t)?(t={default:t,_ctx:Ht},n=32):(t=String(t),r&64?(n=16,t=[kf(t)]):n=8);e.children=t,e.shapeFlag|=n}function Mg(...e){const t={};for(let n=0;njt||Ht;let nl,Au;{const e=No(),t=(n,r)=>{let i;return(i=e[n])||(i=e[n]=[]),i.push(r),s=>{i.length>1?i.forEach(o=>o(s)):i[0](s)}};nl=t("__VUE_INSTANCE_SETTERS__",n=>jt=n),Au=t("__VUE_SSR_SETTERS__",n=>as=n)}const Ni=e=>{const t=jt;return nl(e),e.scope.on(),()=>{e.scope.off(),nl(t)}},_u=()=>{jt&&jt.scope.off(),nl(null)};function Ng(e){return e.vnode.shapeFlag&4}let as=!1;function Ig(e,t=!1,n=!1){t&&Au(t);const{props:r,children:i}=e.vnode,s=Ng(e);s0(e,r,s,t),c0(e,i,n||t);const o=s?L0(e,t):void 0;return t&&Au(!1),o}function L0(e,t){const n=e.type;e.accessCache=Object.create(null),e.proxy=new Proxy(e.ctx,Su);const{setup:r}=n;if(r){Lr();const i=e.setupContext=r.length>1?Lg(e):null,s=Ni(e),o=Es(r,e,0,[e.props,i]),a=Ml(o);if(kr(),s(),(a||e.sp)&&!ni(e)&&Of(e),a){if(o.then(_u,_u),t)return o.then(l=>{Mu(e,l,t)}).catch(l=>{Bi(l,e,0)});e.asyncDep=o}else Mu(e,o,t)}else Pg(e,t)}function Mu(e,t,n){Pe(t)?e.type.__ssrInlineRender?e.ssrRender=t:e.render=t:ct(t)&&(e.setupState=bf(t)),Pg(e,n)}let rl,xu;function Rg(e){rl=e,xu=t=>{t.render._rc&&(t.withProxy=new Proxy(t.ctx,FS))}}const k0=()=>!rl;function Pg(e,t,n){const r=e.type;if(!e.render){if(!t&&rl&&!r.render){const i=r.template||Nf(e).template;if(i){const{isCustomElement:s,compilerOptions:o}=e.appContext.config,{delimiters:a,compilerOptions:l}=r,c=Ze(Ze({isCustomElement:s,delimiters:a},o),l);r.render=rl(i,c)}}e.render=r.render||Ft,xu&&xu(e)}{const i=Ni(e);Lr();try{ZS(e)}finally{kr(),i()}}}const F0={get(e,t){return an(e,"get",""),e[t]}};function Lg(e){const t=n=>{e.exposed=n||{}};return{attrs:new Proxy(e.attrs,F0),slots:e.slots,emit:e.emit,expose:t}}function ko(e){return e.exposed?e.exposeProxy||(e.exposeProxy=new Proxy(bf(Cm(e.exposed)),{get(t,n){if(n in t)return t[n];if(n in Qs)return Qs[n](e)},has(t,n){return n in t||n in Qs}})):e.proxy}function Nu(e,t=!0){return Pe(e)?e.displayName||e.name:e.name||t&&e.__name}function V0(e){return Pe(e)&&"__vccOpts"in e}const kg=(e,t)=>Kb(e,t,as);function Vf(e,t,n){const r=arguments.length;return r===2?ct(t)&&!ye(t)?Br(t)?yt(e,null,[t]):yt(e,t):yt(e,null,t):(r>3?n=Array.prototype.slice.call(arguments,2):r===3&&Br(n)&&(n=[n]),yt(e,t,n))}function B0(){}function j0(e,t,n,r){const i=n[r];if(i&&Fg(i,e))return i;const s=t();return s.memo=e.slice(),s.cacheIndex=r,n[r]=s}function Fg(e,t){const n=e.memo;if(n.length!=t.length)return!1;for(let r=0;r0&&fn&&fn.push(e),!0}const Vg="3.5.16",H0=Ft,U0=nS,$0=Xi,W0=Lm,Y0={createComponentInstance:xg,setupComponent:Ig,renderComponentRoot:Aa,setCurrentRenderingInstance:go,isVNode:Br,normalizeVNode:xn,getComponentPublicInstance:ko,ensureValidVNode:xf,pushWarningContext:Qb,popWarningContext:qb},z0=Y0,K0=null,G0=null,X0=null;/** * @vue/runtime-dom v3.5.16 * (c) 2018-present Yuxi (Evan) You and Vue contributors * @license MIT **/let Iu;const Xd=typeof window<"u"&&window.trustedTypes;if(Xd)try{Iu=Xd.createPolicy("vue",{createHTML:e=>e})}catch{}const Bg=Iu?e=>Iu.createHTML(e):e=>e,J0="http://www.w3.org/2000/svg",Z0="http://www.w3.org/1998/Math/MathML",Dr=typeof document<"u"?document:null,Jd=Dr&&Dr.createElement("template"),Q0={insert:(e,t,n)=>{t.insertBefore(e,n||null)},remove:e=>{const t=e.parentNode;t&&t.removeChild(e)},createElement:(e,t,n,r)=>{const i=t==="svg"?Dr.createElementNS(J0,e):t==="mathml"?Dr.createElementNS(Z0,e):n?Dr.createElement(e,{is:n}):Dr.createElement(e);return e==="select"&&r&&r.multiple!=null&&i.setAttribute("multiple",r.multiple),i},createText:e=>Dr.createTextNode(e),createComment:e=>Dr.createComment(e),setText:(e,t)=>{e.nodeValue=t},setElementText:(e,t)=>{e.textContent=t},parentNode:e=>e.parentNode,nextSibling:e=>e.nextSibling,querySelector:e=>Dr.querySelector(e),setScopeId(e,t){e.setAttribute(t,"")},insertStaticContent(e,t,n,r,i,s){const o=n?n.previousSibling:t.lastChild;if(i&&(i===s||i.nextSibling))for(;t.insertBefore(i.cloneNode(!0),n),!(i===s||!(i=i.nextSibling)););else{Jd.innerHTML=Bg(r==="svg"?`${e}`:r==="mathml"?`${e}`:e);const a=Jd.content;if(r==="svg"||r==="mathml"){const l=a.firstChild;for(;l.firstChild;)a.appendChild(l.firstChild);a.removeChild(l)}t.insertBefore(a,n)}return[o?o.nextSibling:t.firstChild,n?n.previousSibling:t.lastChild]}},$r="transition",As="animation",ls=Symbol("_vtc"),jg={name:String,type:String,css:{type:Boolean,default:!0},duration:[String,Number,Object],enterFromClass:String,enterActiveClass:String,enterToClass:String,appearFromClass:String,appearActiveClass:String,appearToClass:String,leaveFromClass:String,leaveActiveClass:String,leaveToClass:String},Hg=Ze({},Df,jg),q0=e=>(e.displayName="Transition",e.props=Hg,e),eT=q0((e,{slots:t})=>Vf(Um,Ug(e),t)),fi=(e,t=[])=>{ye(e)?e.forEach(n=>n(...t)):e&&e(...t)},Zd=e=>e?ye(e)?e.some(t=>t.length>1):e.length>1:!1;function Ug(e){const t={};for(const P in e)P in jg||(t[P]=e[P]);if(e.css===!1)return t;const{name:n="v",type:r,duration:i,enterFromClass:s=`${n}-enter-from`,enterActiveClass:o=`${n}-enter-active`,enterToClass:a=`${n}-enter-to`,appearFromClass:l=s,appearActiveClass:c=o,appearToClass:u=a,leaveFromClass:f=`${n}-leave-from`,leaveActiveClass:d=`${n}-leave-active`,leaveToClass:p=`${n}-leave-to`}=e,m=tT(i),g=m&&m[0],v=m&&m[1],{onBeforeEnter:w,onEnter:S,onEnterCancelled:y,onLeave:E,onLeaveCancelled:C,onBeforeAppear:M=w,onAppear:B=S,onAppearCancelled:j=y}=t,A=(P,V,$,Z)=>{P._enterCancelled=Z,Yr(P,V?u:a),Yr(P,V?c:o),$&&$()},x=(P,V)=>{P._isLeaving=!1,Yr(P,f),Yr(P,p),Yr(P,d),V&&V()},k=P=>(V,$)=>{const Z=P?B:S,U=()=>A(V,P,$);fi(Z,[V,U]),Qd(()=>{Yr(V,P?l:s),fr(V,P?u:a),Zd(Z)||qd(V,r,g,U)})};return Ze(t,{onBeforeEnter(P){fi(w,[P]),fr(P,s),fr(P,o)},onBeforeAppear(P){fi(M,[P]),fr(P,l),fr(P,c)},onEnter:k(!1),onAppear:k(!0),onLeave(P,V){P._isLeaving=!0;const $=()=>x(P,V);fr(P,f),P._enterCancelled?(fr(P,d),Ru()):(Ru(),fr(P,d)),Qd(()=>{P._isLeaving&&(Yr(P,f),fr(P,p),Zd(E)||qd(P,r,v,$))}),fi(E,[P,$])},onEnterCancelled(P){A(P,!1,void 0,!0),fi(y,[P])},onAppearCancelled(P){A(P,!0,void 0,!0),fi(j,[P])},onLeaveCancelled(P){x(P),fi(C,[P])}})}function tT(e){if(e==null)return null;if(ct(e))return[kc(e.enter),kc(e.leave)];{const t=kc(e);return[t,t]}}function kc(e){return co(e)}function fr(e,t){t.split(/\s+/).forEach(n=>n&&e.classList.add(n)),(e[ls]||(e[ls]=new Set)).add(t)}function Yr(e,t){t.split(/\s+/).forEach(r=>r&&e.classList.remove(r));const n=e[ls];n&&(n.delete(t),n.size||(e[ls]=void 0))}function Qd(e){requestAnimationFrame(()=>{requestAnimationFrame(e)})}let nT=0;function qd(e,t,n,r){const i=e._endId=++nT,s=()=>{i===e._endId&&r()};if(n!=null)return setTimeout(s,n);const{type:o,timeout:a,propCount:l}=$g(e,t);if(!o)return r();const c=o+"end";let u=0;const f=()=>{e.removeEventListener(c,d),s()},d=p=>{p.target===e&&++u>=l&&f()};setTimeout(()=>{u(n[m]||"").split(", "),i=r(`${$r}Delay`),s=r(`${$r}Duration`),o=eh(i,s),a=r(`${As}Delay`),l=r(`${As}Duration`),c=eh(a,l);let u=null,f=0,d=0;t===$r?o>0&&(u=$r,f=o,d=s.length):t===As?c>0&&(u=As,f=c,d=l.length):(f=Math.max(o,c),u=f>0?o>c?$r:As:null,d=u?u===$r?s.length:l.length:0);const p=u===$r&&/\b(transform|all)(,|$)/.test(r(`${$r}Property`).toString());return{type:u,timeout:f,propCount:d,hasTransform:p}}function eh(e,t){for(;e.lengthth(n)+th(e[r])))}function th(e){return e==="auto"?0:Number(e.slice(0,-1).replace(",","."))*1e3}function Ru(){return document.body.offsetHeight}function rT(e,t,n){const r=e[ls];r&&(t=(t?[t,...r]:[...r]).join(" ")),t==null?e.removeAttribute("class"):n?e.setAttribute("class",t):e.className=t}const il=Symbol("_vod"),Wg=Symbol("_vsh"),Yg={beforeMount(e,{value:t},{transition:n}){e[il]=e.style.display==="none"?"":e.style.display,n&&t?n.beforeEnter(e):_s(e,t)},mounted(e,{value:t},{transition:n}){n&&t&&n.enter(e)},updated(e,{value:t,oldValue:n},{transition:r}){!t!=!n&&(r?t?(r.beforeEnter(e),_s(e,!0),r.enter(e)):r.leave(e,()=>{_s(e,!1)}):_s(e,t))},beforeUnmount(e,{value:t}){_s(e,t)}};function _s(e,t){e.style.display=t?e[il]:"none",e[Wg]=!t}function iT(){Yg.getSSRProps=({value:e})=>{if(!e)return{style:{display:"none"}}}}const zg=Symbol("");function sT(e){const t=qn();if(!t)return;const n=t.ut=(i=e(t.proxy))=>{Array.from(document.querySelectorAll(`[data-v-owner="${t.uid}"]`)).forEach(s=>sl(s,i))},r=()=>{const i=e(t.proxy);t.ce?sl(t.ce,i):Pu(t.subTree,i),n(i)};Af(()=>{po(r)}),Po(()=>{ts(r,Ft,{flush:"post"});const i=new MutationObserver(r);i.observe(t.subTree.el.parentNode,{childList:!0}),Yl(()=>i.disconnect())})}function Pu(e,t){if(e.shapeFlag&128){const n=e.suspense;e=n.activeBranch,n.pendingBranch&&!n.isHydrating&&n.effects.push(()=>{Pu(n.activeBranch,t)})}for(;e.component;)e=e.component.subTree;if(e.shapeFlag&1&&e.el)sl(e.el,t);else if(e.type===Wt)e.children.forEach(n=>Pu(n,t));else if(e.type===Oi){let{el:n,anchor:r}=e;for(;n&&(sl(n,t),n!==r);)n=n.nextSibling}}function sl(e,t){if(e.nodeType===1){const n=e.style;let r="";for(const i in t)n.setProperty(`--${i}`,t[i]),r+=`--${i}: ${t[i]};`;n[zg]=r}}const oT=/(^|;)\s*display\s*:/;function aT(e,t,n){const r=e.style,i=ke(n);let s=!1;if(n&&!i){if(t)if(ke(t))for(const o of t.split(";")){const a=o.slice(0,o.indexOf(":")).trim();n[a]==null&&Ma(r,a,"")}else for(const o in t)n[o]==null&&Ma(r,o,"");for(const o in n)o==="display"&&(s=!0),Ma(r,o,n[o])}else if(i){if(t!==n){const o=r[zg];o&&(n+=";"+o),r.cssText=n,s=oT.test(n)}}else t&&e.removeAttribute("style");il in e&&(e[il]=s?r.display:"",e[Wg]&&(r.display="none"))}const nh=/\s*!important$/;function Ma(e,t,n){if(ye(n))n.forEach(r=>Ma(e,t,r));else if(n==null&&(n=""),t.startsWith("--"))e.setProperty(t,n);else{const r=lT(e,t);nh.test(n)?e.setProperty(ln(r),n.replace(nh,""),"important"):e[r]=n}}const rh=["Webkit","Moz","ms"],Fc={};function lT(e,t){const n=Fc[t];if(n)return n;let r=gt(t);if(r!=="filter"&&r in e)return Fc[t]=r;r=ci(r);for(let i=0;iVc||(dT.then(()=>Vc=0),Vc=Date.now());function pT(e,t){const n=r=>{if(!r._vts)r._vts=Date.now();else if(r._vts<=n.attached)return;Jn(mT(r,n.value),t,5,[r])};return n.value=e,n.attached=hT(),n}function mT(e,t){if(ye(t)){const n=e.stopImmediatePropagation;return e.stopImmediatePropagation=()=>{n.call(e),e._stopped=!0},t.map(r=>i=>!i._stopped&&r&&r(i))}else return t}const ch=e=>e.charCodeAt(0)===111&&e.charCodeAt(1)===110&&e.charCodeAt(2)>96&&e.charCodeAt(2)<123,gT=(e,t,n,r,i,s)=>{const o=i==="svg";t==="class"?rT(e,r,o):t==="style"?aT(e,n,r):ai(t)?Al(t)||uT(e,t,n,r,s):(t[0]==="."?(t=t.slice(1),!0):t[0]==="^"?(t=t.slice(1),!1):vT(e,t,r,o))?(oh(e,t,r),!e.tagName.includes("-")&&(t==="value"||t==="checked"||t==="selected")&&sh(e,t,r,o,s,t!=="value")):e._isVueCE&&(/[A-Z]/.test(t)||!ke(r))?oh(e,gt(t),r,s,t):(t==="true-value"?e._trueValue=r:t==="false-value"&&(e._falseValue=r),sh(e,t,r,o))};function vT(e,t,n,r){if(r)return!!(t==="innerHTML"||t==="textContent"||t in e&&ch(t)&&Pe(n));if(t==="spellcheck"||t==="draggable"||t==="translate"||t==="autocorrect"||t==="form"||t==="list"&&e.tagName==="INPUT"||t==="type"&&e.tagName==="TEXTAREA")return!1;if(t==="width"||t==="height"){const i=e.tagName;if(i==="IMG"||i==="VIDEO"||i==="CANVAS"||i==="SOURCE")return!1}return ch(t)&&ke(n)?!1:t in e}const uh={};/*! #__NO_SIDE_EFFECTS__ */function Kg(e,t,n){const r=Cf(e,t);xo(r)&&Ze(r,t);class i extends Gl{constructor(o){super(r,o,n)}}return i.def=r,i}/*! #__NO_SIDE_EFFECTS__ */const yT=(e,t)=>Kg(e,t,sv),ET=typeof HTMLElement<"u"?HTMLElement:class{};class Gl extends ET{constructor(t,n={},r=ll){super(),this._def=t,this._props=n,this._createApp=r,this._isVueCE=!0,this._instance=null,this._app=null,this._nonce=this._def.nonce,this._connected=!1,this._resolved=!1,this._numberProps=null,this._styleChildren=new WeakSet,this._ob=null,this.shadowRoot&&r!==ll?this._root=this.shadowRoot:t.shadowRoot!==!1?(this.attachShadow({mode:"open"}),this._root=this.shadowRoot):this._root=this}connectedCallback(){if(!this.isConnected)return;!this.shadowRoot&&!this._resolved&&this._parseSlots(),this._connected=!0;let t=this;for(;t=t&&(t.parentNode||t.host);)if(t instanceof Gl){this._parent=t;break}this._instance||(this._resolved?this._mount(this._def):t&&t._pendingResolve?this._pendingResolve=t._pendingResolve.then(()=>{this._pendingResolve=void 0,this._resolveDef()}):this._resolveDef())}_setParent(t=this._parent){t&&(this._instance.parent=t._instance,this._inheritParentContext(t))}_inheritParentContext(t=this._parent){t&&this._app&&Object.setPrototypeOf(this._app._context.provides,t._instance.provides)}disconnectedCallback(){this._connected=!1,Bl(()=>{this._connected||(this._ob&&(this._ob.disconnect(),this._ob=null),this._app&&this._app.unmount(),this._instance&&(this._instance.ce=void 0),this._app=this._instance=null)})}_resolveDef(){if(this._pendingResolve)return;for(let r=0;r{for(const i of r)this._setAttr(i.attributeName)}),this._ob.observe(this,{attributes:!0});const t=(r,i=!1)=>{this._resolved=!0,this._pendingResolve=void 0;const{props:s,styles:o}=r;let a;if(s&&!ye(s))for(const l in s){const c=s[l];(c===Number||c&&c.type===Number)&&(l in this._props&&(this._props[l]=co(this._props[l])),(a||(a=Object.create(null)))[gt(l)]=!0)}this._numberProps=a,this._resolveProps(r),this.shadowRoot&&this._applyStyles(o),this._mount(r)},n=this._def.__asyncLoader;n?this._pendingResolve=n().then(r=>t(this._def=r,!0)):t(this._def)}_mount(t){this._app=this._createApp(t),this._inheritParentContext(),t.configureApp&&t.configureApp(this._app),this._app._ceVNode=this._createVNode(),this._app.mount(this._root);const n=this._instance&&this._instance.exposed;if(n)for(const r in n)st(this,r)||Object.defineProperty(this,r,{get:()=>Vl(n[r])})}_resolveProps(t){const{props:n}=t,r=ye(n)?n:Object.keys(n||{});for(const i of Object.keys(this))i[0]!=="_"&&r.includes(i)&&this._setProp(i,this[i]);for(const i of r.map(gt))Object.defineProperty(this,i,{get(){return this._getProp(i)},set(s){this._setProp(i,s,!0,!0)}})}_setAttr(t){if(t.startsWith("data-v-"))return;const n=this.hasAttribute(t);let r=n?this.getAttribute(t):uh;const i=gt(t);n&&this._numberProps&&this._numberProps[i]&&(r=co(r)),this._setProp(i,r,!1,!0)}_getProp(t){return this._props[t]}_setProp(t,n,r=!0,i=!1){if(n!==this._props[t]&&(n===uh?delete this._props[t]:(this._props[t]=n,t==="key"&&this._app&&(this._app._ceVNode.key=n)),i&&this._instance&&this._update(),r)){const s=this._ob;s&&s.disconnect(),n===!0?this.setAttribute(ln(t),""):typeof n=="string"||typeof n=="number"?this.setAttribute(ln(t),n+""):n||this.removeAttribute(ln(t)),s&&s.observe(this,{attributes:!0})}}_update(){const t=this._createVNode();this._app&&(t.appContext=this._app._context),iv(t,this._root)}_createVNode(){const t={};this.shadowRoot||(t.onVnodeMounted=t.onVnodeUpdated=this._renderSlots.bind(this));const n=yt(this._def,Ze(t,this._props));return this._instance||(n.ce=r=>{this._instance=r,r.ce=this,r.isCE=!0;const i=(s,o)=>{this.dispatchEvent(new CustomEvent(s,xo(o[0])?Ze({detail:o},o[0]):{detail:o}))};r.emit=(s,...o)=>{i(s,o),ln(s)!==s&&i(ln(s),o)},this._setParent()}),n}_applyStyles(t,n){if(!t)return;if(n){if(n===this._def||this._styleChildren.has(n))return;this._styleChildren.add(n)}const r=this._nonce;for(let i=t.length-1;i>=0;i--){const s=document.createElement("style");r&&s.setAttribute("nonce",r),s.textContent=t[i],this.shadowRoot.prepend(s)}}_parseSlots(){const t=this._slots={};let n;for(;n=this.firstChild;){const r=n.nodeType===1&&n.getAttribute("slot")||"default";(t[r]||(t[r]=[])).push(n),this.removeChild(n)}}_renderSlots(){const t=(this._teleportTarget||this).querySelectorAll("slot"),n=this._instance.type.__scopeId;for(let r=0;r(delete e.props.mode,e),wT=TT({name:"TransitionGroup",props:Ze({},Hg,{tag:String,moveClass:String}),setup(e,{slots:t}){const n=qn(),r=wf();let i,s;return $l(()=>{if(!i.length)return;const o=e.moveClass||`${e.name||"v"}-move`;if(!_T(i[0].el,n.vnode.el,o)){i=[];return}i.forEach(CT),i.forEach(OT);const a=i.filter(AT);Ru(),a.forEach(l=>{const c=l.el,u=c.style;fr(c,o),u.transform=u.webkitTransform=u.transitionDuration="";const f=c[ol]=d=>{d&&d.target!==c||(!d||/transform$/.test(d.propertyName))&&(c.removeEventListener("transitionend",f),c[ol]=null,Yr(c,o))};c.addEventListener("transitionend",f)}),i=[]}),()=>{const o=rt(e),a=Ug(o);let l=o.tag||Wt;if(i=[],s)for(let c=0;c{a.split(/\s+/).forEach(l=>l&&r.classList.remove(l))}),n.split(/\s+/).forEach(a=>a&&r.classList.add(a)),r.style.display="none";const s=t.nodeType===1?t:t.parentNode;s.appendChild(r);const{hasTransform:o}=$g(r);return s.removeChild(r),o}const si=e=>{const t=e.props["onUpdate:modelValue"]||!1;return ye(t)?n=>wi(t,n):t};function MT(e){e.target.composing=!0}function dh(e){const t=e.target;t.composing&&(t.composing=!1,t.dispatchEvent(new Event("input")))}const Gn=Symbol("_assign"),al={created(e,{modifiers:{lazy:t,trim:n,number:r}},i){e[Gn]=si(i);const s=r||i.props&&i.props.type==="number";Ar(e,t?"change":"input",o=>{if(o.target.composing)return;let a=e.value;n&&(a=a.trim()),s&&(a=lo(a)),e[Gn](a)}),n&&Ar(e,"change",()=>{e.value=e.value.trim()}),t||(Ar(e,"compositionstart",MT),Ar(e,"compositionend",dh),Ar(e,"change",dh))},mounted(e,{value:t}){e.value=t??""},beforeUpdate(e,{value:t,oldValue:n,modifiers:{lazy:r,trim:i,number:s}},o){if(e[Gn]=si(o),e.composing)return;const a=(s||e.type==="number")&&!/^0\d/.test(e.value)?lo(e.value):e.value,l=t??"";a!==l&&(document.activeElement===e&&e.type!=="range"&&(r&&t===n||i&&e.value.trim()===l)||(e.value=l))}},Bf={deep:!0,created(e,t,n){e[Gn]=si(n),Ar(e,"change",()=>{const r=e._modelValue,i=cs(e),s=e.checked,o=e[Gn];if(ye(r)){const a=Io(r,i),l=a!==-1;if(s&&!l)o(r.concat(i));else if(!s&&l){const c=[...r];c.splice(a,1),o(c)}}else if(li(r)){const a=new Set(r);s?a.add(i):a.delete(i),o(a)}else o(Qg(e,s))})},mounted:hh,beforeUpdate(e,t,n){e[Gn]=si(n),hh(e,t,n)}};function hh(e,{value:t,oldValue:n},r){e._modelValue=t;let i;if(ye(t))i=Io(t,r.props.value)>-1;else if(li(t))i=t.has(r.props.value);else{if(t===n)return;i=Pr(t,Qg(e,!0))}e.checked!==i&&(e.checked=i)}const jf={created(e,{value:t},n){e.checked=Pr(t,n.props.value),e[Gn]=si(n),Ar(e,"change",()=>{e[Gn](cs(e))})},beforeUpdate(e,{value:t,oldValue:n},r){e[Gn]=si(r),t!==n&&(e.checked=Pr(t,r.props.value))}},Zg={deep:!0,created(e,{value:t,modifiers:{number:n}},r){const i=li(t);Ar(e,"change",()=>{const s=Array.prototype.filter.call(e.options,o=>o.selected).map(o=>n?lo(cs(o)):cs(o));e[Gn](e.multiple?i?new Set(s):s:s[0]),e._assigning=!0,Bl(()=>{e._assigning=!1})}),e[Gn]=si(r)},mounted(e,{value:t}){ph(e,t)},beforeUpdate(e,t,n){e[Gn]=si(n)},updated(e,{value:t}){e._assigning||ph(e,t)}};function ph(e,t){const n=e.multiple,r=ye(t);if(!(n&&!r&&!li(t))){for(let i=0,s=e.options.length;iString(c)===String(a)):o.selected=Io(t,a)>-1}else o.selected=t.has(a);else if(Pr(cs(o),t)){e.selectedIndex!==i&&(e.selectedIndex=i);return}}!n&&e.selectedIndex!==-1&&(e.selectedIndex=-1)}}function cs(e){return"_value"in e?e._value:e.value}function Qg(e,t){const n=t?"_trueValue":"_falseValue";return n in e?e[n]:t}const qg={created(e,t,n){oa(e,t,n,null,"created")},mounted(e,t,n){oa(e,t,n,null,"mounted")},beforeUpdate(e,t,n,r){oa(e,t,n,r,"beforeUpdate")},updated(e,t,n,r){oa(e,t,n,r,"updated")}};function ev(e,t){switch(e){case"SELECT":return Zg;case"TEXTAREA":return al;default:switch(t){case"checkbox":return Bf;case"radio":return jf;default:return al}}}function oa(e,t,n,r,i){const o=ev(e.tagName,n.props&&n.props.type)[i];o&&o(e,t,n,r)}function xT(){al.getSSRProps=({value:e})=>({value:e}),jf.getSSRProps=({value:e},t)=>{if(t.props&&Pr(t.props.value,e))return{checked:!0}},Bf.getSSRProps=({value:e},t)=>{if(ye(e)){if(t.props&&Io(e,t.props.value)>-1)return{checked:!0}}else if(li(e)){if(t.props&&e.has(t.props.value))return{checked:!0}}else if(e)return{checked:!0}},qg.getSSRProps=(e,t)=>{if(typeof t.type!="string")return;const n=ev(t.type.toUpperCase(),t.props&&t.props.type);if(n.getSSRProps)return n.getSSRProps(e,t)}}const NT=["ctrl","shift","alt","meta"],IT={stop:e=>e.stopPropagation(),prevent:e=>e.preventDefault(),self:e=>e.target!==e.currentTarget,ctrl:e=>!e.ctrlKey,shift:e=>!e.shiftKey,alt:e=>!e.altKey,meta:e=>!e.metaKey,left:e=>"button"in e&&e.button!==0,middle:e=>"button"in e&&e.button!==1,right:e=>"button"in e&&e.button!==2,exact:(e,t)=>NT.some(n=>e[`${n}Key`]&&!t.includes(n))},RT=(e,t)=>{const n=e._withMods||(e._withMods={}),r=t.join(".");return n[r]||(n[r]=(i,...s)=>{for(let o=0;o{const n=e._withKeys||(e._withKeys={}),r=t.join(".");return n[r]||(n[r]=i=>{if(!("key"in i))return;const s=ln(i.key);if(t.some(o=>o===s||PT[o]===s))return e(i)})},tv=Ze({patchProp:gT},Q0);let to,mh=!1;function nv(){return to||(to=dg(tv))}function rv(){return to=mh?to:hg(tv),mh=!0,to}const iv=(...e)=>{nv().render(...e)},kT=(...e)=>{rv().hydrate(...e)},ll=(...e)=>{const t=nv().createApp(...e),{mount:n}=t;return t.mount=r=>{const i=av(r);if(!i)return;const s=t._component;!Pe(s)&&!s.render&&!s.template&&(s.template=i.innerHTML),i.nodeType===1&&(i.textContent="");const o=n(i,!1,ov(i));return i instanceof Element&&(i.removeAttribute("v-cloak"),i.setAttribute("data-v-app","")),o},t},sv=(...e)=>{const t=rv().createApp(...e),{mount:n}=t;return t.mount=r=>{const i=av(r);if(i)return n(i,!0,ov(i))},t};function ov(e){if(e instanceof SVGElement)return"svg";if(typeof MathMLElement=="function"&&e instanceof MathMLElement)return"mathml"}function av(e){return ke(e)?document.querySelector(e):e}let gh=!1;const FT=()=>{gh||(gh=!0,xT(),iT())},lv=Object.freeze(Object.defineProperty({__proto__:null,BaseTransition:Um,BaseTransitionPropsValidators:Df,Comment:Rt,DeprecationTypes:X0,EffectScope:mf,ErrorCodes:tS,ErrorTypeStrings:U0,Fragment:Wt,KeepAlive:_S,ReactiveEffect:uo,Static:Oi,Suspense:T0,Teleport:uS,Text:ri,TrackOpTypes:Gb,Transition:eT,TransitionGroup:DT,TriggerOpTypes:Xb,VueElement:Gl,assertNumber:eS,callWithAsyncErrorHandling:Jn,callWithErrorHandling:Es,camelize:gt,capitalize:ci,cloneVNode:gr,compatUtils:G0,computed:kg,createApp:ll,createBlock:tl,createCommentVNode:I0,createElementBlock:_0,createElementVNode:Lf,createHydrationRenderer:hg,createPropsRestProxy:XS,createRenderer:dg,createSSRApp:sv,createSlots:PS,createStaticVNode:N0,createTextVNode:kf,createVNode:yt,customRef:_m,defineAsyncComponent:OS,defineComponent:Cf,defineCustomElement:Kg,defineEmits:BS,defineExpose:jS,defineModel:$S,defineOptions:HS,defineProps:VS,defineSSRCustomElement:yT,defineSlots:US,devtools:$0,effect:gb,effectScope:hb,getCurrentInstance:qn,getCurrentScope:lm,getCurrentWatcher:Jb,getTransitionRawChildren:Hl,guardReactiveProps:_g,h:Vf,handleError:Bi,hasInjectionContext:i0,hydrate:kT,hydrateOnIdle:bS,hydrateOnInteraction:DS,hydrateOnMediaQuery:wS,hydrateOnVisible:TS,initCustomFormatter:B0,initDirectivesForSSR:FT,inject:qs,isMemoSame:Fg,isProxy:Fl,isReactive:ti,isReadonly:Fr,isRef:Ut,isRuntimeOnly:k0,isShallow:$n,isVNode:Br,markRaw:Cm,mergeDefaults:KS,mergeModels:GS,mergeProps:Mg,nextTick:Bl,normalizeClass:ys,normalizeProps:Qp,normalizeStyle:vs,onActivated:Wm,onBeforeMount:Km,onBeforeUnmount:Wl,onBeforeUpdate:Af,onDeactivated:Ym,onErrorCaptured:Zm,onMounted:Po,onRenderTracked:Jm,onRenderTriggered:Xm,onScopeDispose:pb,onServerPrefetch:Gm,onUnmounted:Yl,onUpdated:$l,onWatcherCleanup:xm,openBlock:bo,popScopeId:oS,provide:rg,proxyRefs:bf,pushScopeId:sS,queuePostFlushCb:po,reactive:Ll,readonly:Ef,ref:Js,registerRuntimeCompiler:Rg,render:iv,renderList:RS,renderSlot:LS,resolveComponent:Qm,resolveDirective:IS,resolveDynamicComponent:NS,resolveFilter:K0,resolveTransitionHooks:os,setBlockTracking:Ou,setDevtoolsHook:W0,setTransitionHooks:Vr,shallowReactive:Dm,shallowReadonly:kb,shallowRef:Om,ssrContextKey:vg,ssrUtils:z0,stop:vb,toDisplayString:pf,toHandlerKey:Ti,toHandlers:kS,toRaw:rt,toRef:Yb,toRefs:Ub,toValue:Bb,transformVNodeArgs:M0,triggerRef:Vb,unref:Vl,useAttrs:zS,useCssModule:ST,useCssVars:sT,useHost:Gg,useId:dS,useModel:m0,useSSRContext:yg,useShadowRoot:bT,useSlots:YS,useTemplateRef:hS,useTransitionState:wf,vModelCheckbox:Bf,vModelDynamic:qg,vModelRadio:jf,vModelSelect:Zg,vModelText:al,vShow:Yg,version:Vg,warn:H0,watch:ts,watchEffect:d0,watchPostEffect:h0,watchSyncEffect:Eg,withAsyncContext:JS,withCtx:Tf,withDefaults:WS,withDirectives:lS,withKeys:LT,withMemo:j0,withModifiers:RT,withScopeId:aS},Symbol.toStringTag,{value:"Module"}));/** * @vue/compiler-core v3.5.16 * (c) 2018-present Yuxi (Evan) You and Vue contributors * @license MIT **/const us=Symbol(""),rs=Symbol(""),Xl=Symbol(""),So=Symbol(""),Hf=Symbol(""),oi=Symbol(""),Uf=Symbol(""),$f=Symbol(""),Jl=Symbol(""),Zl=Symbol(""),bs=Symbol(""),Ql=Symbol(""),Wf=Symbol(""),ql=Symbol(""),ec=Symbol(""),tc=Symbol(""),nc=Symbol(""),rc=Symbol(""),ic=Symbol(""),Yf=Symbol(""),zf=Symbol(""),Fo=Symbol(""),To=Symbol(""),sc=Symbol(""),oc=Symbol(""),fs=Symbol(""),Ss=Symbol(""),ac=Symbol(""),cl=Symbol(""),cv=Symbol(""),ul=Symbol(""),wo=Symbol(""),uv=Symbol(""),fv=Symbol(""),lc=Symbol(""),dv=Symbol(""),hv=Symbol(""),cc=Symbol(""),Kf=Symbol(""),Ii={[us]:"Fragment",[rs]:"Teleport",[Xl]:"Suspense",[So]:"KeepAlive",[Hf]:"BaseTransition",[oi]:"openBlock",[Uf]:"createBlock",[$f]:"createElementBlock",[Jl]:"createVNode",[Zl]:"createElementVNode",[bs]:"createCommentVNode",[Ql]:"createTextVNode",[Wf]:"createStaticVNode",[ql]:"resolveComponent",[ec]:"resolveDynamicComponent",[tc]:"resolveDirective",[nc]:"resolveFilter",[rc]:"withDirectives",[ic]:"renderList",[Yf]:"renderSlot",[zf]:"createSlots",[Fo]:"toDisplayString",[To]:"mergeProps",[sc]:"normalizeClass",[oc]:"normalizeStyle",[fs]:"normalizeProps",[Ss]:"guardReactiveProps",[ac]:"toHandlers",[cl]:"camelize",[cv]:"capitalize",[ul]:"toHandlerKey",[wo]:"setBlockTracking",[uv]:"pushScopeId",[fv]:"popScopeId",[lc]:"withCtx",[dv]:"unref",[hv]:"isRef",[cc]:"withMemo",[Kf]:"isMemoSame"};function pv(e){Object.getOwnPropertySymbols(e).forEach(t=>{Ii[t]=e[t]})}const VT={HTML:0,0:"HTML",SVG:1,1:"SVG",MATH_ML:2,2:"MATH_ML"},BT={ROOT:0,0:"ROOT",ELEMENT:1,1:"ELEMENT",TEXT:2,2:"TEXT",COMMENT:3,3:"COMMENT",SIMPLE_EXPRESSION:4,4:"SIMPLE_EXPRESSION",INTERPOLATION:5,5:"INTERPOLATION",ATTRIBUTE:6,6:"ATTRIBUTE",DIRECTIVE:7,7:"DIRECTIVE",COMPOUND_EXPRESSION:8,8:"COMPOUND_EXPRESSION",IF:9,9:"IF",IF_BRANCH:10,10:"IF_BRANCH",FOR:11,11:"FOR",TEXT_CALL:12,12:"TEXT_CALL",VNODE_CALL:13,13:"VNODE_CALL",JS_CALL_EXPRESSION:14,14:"JS_CALL_EXPRESSION",JS_OBJECT_EXPRESSION:15,15:"JS_OBJECT_EXPRESSION",JS_PROPERTY:16,16:"JS_PROPERTY",JS_ARRAY_EXPRESSION:17,17:"JS_ARRAY_EXPRESSION",JS_FUNCTION_EXPRESSION:18,18:"JS_FUNCTION_EXPRESSION",JS_CONDITIONAL_EXPRESSION:19,19:"JS_CONDITIONAL_EXPRESSION",JS_CACHE_EXPRESSION:20,20:"JS_CACHE_EXPRESSION",JS_BLOCK_STATEMENT:21,21:"JS_BLOCK_STATEMENT",JS_TEMPLATE_LITERAL:22,22:"JS_TEMPLATE_LITERAL",JS_IF_STATEMENT:23,23:"JS_IF_STATEMENT",JS_ASSIGNMENT_EXPRESSION:24,24:"JS_ASSIGNMENT_EXPRESSION",JS_SEQUENCE_EXPRESSION:25,25:"JS_SEQUENCE_EXPRESSION",JS_RETURN_STATEMENT:26,26:"JS_RETURN_STATEMENT"},jT={ELEMENT:0,0:"ELEMENT",COMPONENT:1,1:"COMPONENT",SLOT:2,2:"SLOT",TEMPLATE:3,3:"TEMPLATE"},HT={NOT_CONSTANT:0,0:"NOT_CONSTANT",CAN_SKIP_PATCH:1,1:"CAN_SKIP_PATCH",CAN_CACHE:2,2:"CAN_CACHE",CAN_STRINGIFY:3,3:"CAN_STRINGIFY"},$t={start:{line:1,column:1,offset:0},end:{line:1,column:1,offset:0},source:""};function mv(e,t=""){return{type:0,source:t,children:e,helpers:new Set,components:[],directives:[],hoists:[],imports:[],cached:[],temps:0,codegenNode:void 0,loc:$t}}function ds(e,t,n,r,i,s,o,a=!1,l=!1,c=!1,u=$t){return e&&(a?(e.helper(oi),e.helper(Li(e.inSSR,c))):e.helper(Pi(e.inSSR,c)),o&&e.helper(rc)),{type:13,tag:t,props:n,children:r,patchFlag:i,dynamicProps:s,directives:o,isBlock:a,disableTracking:l,isComponent:c,loc:u}}function ii(e,t=$t){return{type:17,loc:t,elements:e}}function Un(e,t=$t){return{type:15,loc:t,properties:e}}function Ct(e,t){return{type:16,loc:$t,key:ke(e)?Be(e,!0):e,value:t}}function Be(e,t=!1,n=$t,r=0){return{type:4,loc:n,content:e,isStatic:t,constType:t?3:r}}function UT(e,t){return{type:5,loc:t,content:ke(e)?Be(e,!1,t):e}}function Xn(e,t=$t){return{type:8,loc:t,children:e}}function kt(e,t=[],n=$t){return{type:14,loc:n,callee:e,arguments:t}}function Ri(e,t=void 0,n=!1,r=!1,i=$t){return{type:18,params:e,returns:t,newline:n,isSlot:r,loc:i}}function fl(e,t,n,r=!0){return{type:19,test:e,consequent:t,alternate:n,newline:r,loc:$t}}function gv(e,t,n=!1,r=!1){return{type:20,index:e,value:t,needPauseTracking:n,inVOnce:r,needArraySpread:!1,loc:$t}}function vv(e){return{type:21,body:e,loc:$t}}function $T(e){return{type:22,elements:e,loc:$t}}function WT(e,t,n){return{type:23,test:e,consequent:t,alternate:n,loc:$t}}function YT(e,t){return{type:24,left:e,right:t,loc:$t}}function zT(e){return{type:25,expressions:e,loc:$t}}function KT(e){return{type:26,returns:e,loc:$t}}function Pi(e,t){return e||t?Jl:Zl}function Li(e,t){return e||t?Uf:$f}function uc(e,{helper:t,removeHelper:n,inSSR:r}){e.isBlock||(e.isBlock=!0,n(Pi(r,e.isComponent)),t(oi),t(Li(r,e.isComponent)))}const vh=new Uint8Array([123,123]),yh=new Uint8Array([125,125]);function Eh(e){return e>=97&&e<=122||e>=65&&e<=90}function Hn(e){return e===32||e===10||e===9||e===12||e===13}function Wr(e){return e===47||e===62||Hn(e)}function dl(e){const t=new Uint8Array(e.length);for(let n=0;n=0;i--){const s=this.newlines[i];if(t>s){n=i+2,r=t-s;break}}return{column:r,line:n,offset:t}}peek(){return this.buffer.charCodeAt(this.index+1)}stateText(t){t===60?(this.index>this.sectionStart&&this.cbs.ontext(this.sectionStart,this.index),this.state=5,this.sectionStart=this.index):!this.inVPre&&t===this.delimiterOpen[0]&&(this.state=2,this.delimiterIndex=0,this.stateInterpolationOpen(t))}stateInterpolationOpen(t){if(t===this.delimiterOpen[this.delimiterIndex])if(this.delimiterIndex===this.delimiterOpen.length-1){const n=this.index+1-this.delimiterOpen.length;n>this.sectionStart&&this.cbs.ontext(this.sectionStart,n),this.state=3,this.sectionStart=n}else this.delimiterIndex++;else this.inRCDATA?(this.state=32,this.stateInRCDATA(t)):(this.state=1,this.stateText(t))}stateInterpolation(t){t===this.delimiterClose[0]&&(this.state=4,this.delimiterIndex=0,this.stateInterpolationClose(t))}stateInterpolationClose(t){t===this.delimiterClose[this.delimiterIndex]?this.delimiterIndex===this.delimiterClose.length-1?(this.cbs.oninterpolation(this.sectionStart,this.index+1),this.inRCDATA?this.state=32:this.state=1,this.sectionStart=this.index+1):this.delimiterIndex++:(this.state=3,this.stateInterpolation(t))}stateSpecialStartSequence(t){const n=this.sequenceIndex===this.currentSequence.length;if(!(n?Wr(t):(t|32)===this.currentSequence[this.sequenceIndex]))this.inRCDATA=!1;else if(!n){this.sequenceIndex++;return}this.sequenceIndex=0,this.state=6,this.stateInTagName(t)}stateInRCDATA(t){if(this.sequenceIndex===this.currentSequence.length){if(t===62||Hn(t)){const n=this.index-this.currentSequence.length;if(this.sectionStart=t||(this.state===28?this.currentSequence===en.CdataEnd?this.cbs.oncdata(this.sectionStart,t):this.cbs.oncomment(this.sectionStart,t):this.state===6||this.state===11||this.state===18||this.state===17||this.state===12||this.state===13||this.state===14||this.state===15||this.state===16||this.state===20||this.state===19||this.state===21||this.state===9||this.cbs.ontext(this.sectionStart,t))}emitCodePoint(t,n){}}const XT={COMPILER_IS_ON_ELEMENT:"COMPILER_IS_ON_ELEMENT",COMPILER_V_BIND_SYNC:"COMPILER_V_BIND_SYNC",COMPILER_V_BIND_OBJECT_ORDER:"COMPILER_V_BIND_OBJECT_ORDER",COMPILER_V_ON_NATIVE:"COMPILER_V_ON_NATIVE",COMPILER_V_IF_V_FOR_PRECEDENCE:"COMPILER_V_IF_V_FOR_PRECEDENCE",COMPILER_NATIVE_TEMPLATE:"COMPILER_NATIVE_TEMPLATE",COMPILER_INLINE_TEMPLATE:"COMPILER_INLINE_TEMPLATE",COMPILER_FILTERS:"COMPILER_FILTERS"},JT={COMPILER_IS_ON_ELEMENT:{message:'Platform-native elements with "is" prop will no longer be treated as components in Vue 3 unless the "is" value is explicitly prefixed with "vue:".',link:"https://v3-migration.vuejs.org/breaking-changes/custom-elements-interop.html"},COMPILER_V_BIND_SYNC:{message:e=>`.sync modifier for v-bind has been removed. Use v-model with argument instead. \`v-bind:${e}.sync\` should be changed to \`v-model:${e}\`.`,link:"https://v3-migration.vuejs.org/breaking-changes/v-model.html"},COMPILER_V_BIND_OBJECT_ORDER:{message:'v-bind="obj" usage is now order sensitive and behaves like JavaScript object spread: it will now overwrite an existing non-mergeable attribute that appears before v-bind in the case of conflict. To retain 2.x behavior, move v-bind to make it the first attribute. You can also suppress this warning if the usage is intended.',link:"https://v3-migration.vuejs.org/breaking-changes/v-bind.html"},COMPILER_V_ON_NATIVE:{message:".native modifier for v-on has been removed as is no longer necessary.",link:"https://v3-migration.vuejs.org/breaking-changes/v-on-native-modifier-removed.html"},COMPILER_V_IF_V_FOR_PRECEDENCE:{message:"v-if / v-for precedence when used on the same element has changed in Vue 3: v-if now takes higher precedence and will no longer have access to v-for scope variables. It is best to avoid the ambiguity with