Compare commits
31 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3ac72ad19b | ||
|
|
c26f0cb5bc | ||
|
|
3db9fc332b | ||
|
|
e9ce256c07 | ||
|
|
9fb838dcba | ||
|
|
b0dd72ec57 | ||
|
|
9d5c5ccf09 | ||
|
|
30be61f2c7 | ||
|
|
08c495943b | ||
|
|
6177dbcf24 | ||
|
|
87ba256bae | ||
|
|
9e64976963 | ||
|
|
130d35c377 | ||
|
|
8c54c6590c | ||
|
|
27ff19ca0d | ||
|
|
f57fbda2d6 | ||
|
|
3baa7def61 | ||
|
|
cf6ded1105 | ||
|
|
a7e8d7995b | ||
|
|
62f38a27a5 | ||
|
|
17df35102d | ||
|
|
203160fce0 | ||
|
|
4943f78f89 | ||
|
|
dd82d405ce | ||
|
|
cb4a74bf81 | ||
|
|
401db7bfef | ||
|
|
95b8b8e798 | ||
|
|
196b0dc24b | ||
|
|
52e52bb03c | ||
|
|
283fb2eba2 | ||
|
|
ac1ff56288 |
154 changed files with 5272 additions and 3927 deletions
711
.github/instructions/ai.instructions.md
vendored
Normal file
711
.github/instructions/ai.instructions.md
vendored
Normal file
|
|
@ -0,0 +1,711 @@
|
||||||
|
# Dynamic Low-Code Platform - AI Instruction and Operating Manual
|
||||||
|
|
||||||
|
## 1. Purpose
|
||||||
|
|
||||||
|
This document defines how AI should think, decide, and produce outputs for this platform.
|
||||||
|
|
||||||
|
Primary objective:
|
||||||
|
|
||||||
|
- Maximize delivery through runtime configuration.
|
||||||
|
- Minimize custom code.
|
||||||
|
- Preserve platform consistency, security, and tenant isolation.
|
||||||
|
|
||||||
|
Primary principle:
|
||||||
|
|
||||||
|
- Configuration first, code last.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Platform Scope
|
||||||
|
|
||||||
|
Core stack:
|
||||||
|
|
||||||
|
- Backend: C# .NET 9 + ABP 9.x
|
||||||
|
- Frontend: React 18 + DevExtreme
|
||||||
|
- Database: SQL Server (dynamic datasource support)
|
||||||
|
- Cache: Redis
|
||||||
|
- Background jobs: Hangfire + ABP Background Workers
|
||||||
|
|
||||||
|
System nature:
|
||||||
|
|
||||||
|
- Runtime configurable application engine
|
||||||
|
- Multi-tenant SaaS foundation
|
||||||
|
- Low-code / no-code first
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Non-Negotiable Rules
|
||||||
|
|
||||||
|
1. Do not propose new custom React component/page development for standard feature requests.
|
||||||
|
2. Build new screens using platform configuration mechanisms.
|
||||||
|
3. Every proposal must include tenant and permission design.
|
||||||
|
4. Never bypass platform authorization patterns.
|
||||||
|
5. Never hardcode secrets, tenant ids, or connection strings.
|
||||||
|
|
||||||
|
Exception policy:
|
||||||
|
|
||||||
|
- Code-level React or backend development can be considered only if user explicitly requests code implementation and configuration path is insufficient.
|
||||||
|
- If exception is required, AI must explain why configuration-based options are not enough.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Decision Flow (Mandatory)
|
||||||
|
|
||||||
|
For every request, apply this order:
|
||||||
|
|
||||||
|
1. Dynamic configuration with existing ListForm ecosystem
|
||||||
|
2. SQL Query Manager + Custom Endpoint
|
||||||
|
3. Dynamic Service
|
||||||
|
4. Code change (last resort, justification required)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Architecture Guardrails
|
||||||
|
|
||||||
|
Backend (ABP):
|
||||||
|
|
||||||
|
- Respect modular boundaries.
|
||||||
|
- Prefer application services over ad-hoc endpoints.
|
||||||
|
- Keep permissions explicit and auditable.
|
||||||
|
|
||||||
|
Frontend (React + DevExtreme):
|
||||||
|
|
||||||
|
- Use existing dynamic view infrastructure.
|
||||||
|
- Keep component behavior metadata-driven.
|
||||||
|
- Preserve route, menu, and permission coherence.
|
||||||
|
|
||||||
|
Data:
|
||||||
|
|
||||||
|
- SQL-first for shaping, filtering, aggregation, reporting.
|
||||||
|
- Parameterized query patterns only.
|
||||||
|
- Ensure tenant-safe data access.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Dynamic Menu and Routing
|
||||||
|
|
||||||
|
- Menus are database driven.
|
||||||
|
- Routes are generated/managed dynamically.
|
||||||
|
- Each menu item should map to:
|
||||||
|
- Target component mode
|
||||||
|
- Data source or query context
|
||||||
|
- Endpoint/service target
|
||||||
|
- Permission contract
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Dynamic List System
|
||||||
|
|
||||||
|
Driven by:
|
||||||
|
|
||||||
|
- ListForm
|
||||||
|
- ListFormFields
|
||||||
|
- ListFormCustomization (UserUiFilter, GridState, ServerJoin, ServerWhere)
|
||||||
|
- ListFormImport and ListFormImportExecute
|
||||||
|
- ListFormJsonRow operations
|
||||||
|
|
||||||
|
Capabilities:
|
||||||
|
|
||||||
|
- Dynamic columns, command column, banded columns
|
||||||
|
- Permission-aware visibility and action rendering
|
||||||
|
- Rich filtering, sorting, grouping, paging, searching
|
||||||
|
- Dynamic toolbar actions and URL/dialog/script command flows
|
||||||
|
- Lookup sources:
|
||||||
|
- StaticData
|
||||||
|
- Query
|
||||||
|
- WebService
|
||||||
|
- Cascading lookup parent-child behavior
|
||||||
|
- Dynamic validation, editor options, editor scripts
|
||||||
|
- Conditional formatting and style injection
|
||||||
|
- Grid state save/load/reset
|
||||||
|
- User filter save/apply/delete flows
|
||||||
|
- Import manager and export flows (xlsx, csv, pdf)
|
||||||
|
- SubForm integration from selected row context
|
||||||
|
- Remote operations and dynamic datasource rebinding
|
||||||
|
|
||||||
|
Supported editor types:
|
||||||
|
|
||||||
|
- dxAutocomplete
|
||||||
|
- dxCalendar
|
||||||
|
- dxCheckBox
|
||||||
|
- dxColorBox
|
||||||
|
- dxDateBox
|
||||||
|
- dxDateRangeBox
|
||||||
|
- dxDropDownBox
|
||||||
|
- dxGridBox
|
||||||
|
- dxHtmlEditor
|
||||||
|
- dxLookup
|
||||||
|
- dxNumberBox
|
||||||
|
- dxRadioGroup
|
||||||
|
- dxRangeSlider
|
||||||
|
- dxSelectBox
|
||||||
|
- dxSlider
|
||||||
|
- dxSwitch
|
||||||
|
- dxTagBox
|
||||||
|
- dxTextArea
|
||||||
|
- dxTextBox
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Dynamic Form System
|
||||||
|
|
||||||
|
- DevExtreme Form-based runtime field generation
|
||||||
|
- Metadata-driven item groups/tabs
|
||||||
|
- Validation and conditional behavior
|
||||||
|
- CRUD integration
|
||||||
|
- Default value and runtime script handling
|
||||||
|
- Lookup-enabled field model
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. Dynamic UI Components
|
||||||
|
|
||||||
|
Supported component families:
|
||||||
|
|
||||||
|
- DataGrid
|
||||||
|
- PivotGrid
|
||||||
|
- Form
|
||||||
|
- Chart
|
||||||
|
- Scheduler
|
||||||
|
- Gantt
|
||||||
|
- TreeList / Tree view
|
||||||
|
- SubForm tabs (List, Tree, Gantt, Scheduler, Form, Chart)
|
||||||
|
- Widget Group (dashboard KPI cards)
|
||||||
|
|
||||||
|
Runtime UI capabilities:
|
||||||
|
|
||||||
|
- Per-list layout switching:
|
||||||
|
- Grid
|
||||||
|
- Pivot
|
||||||
|
- Tree
|
||||||
|
- Chart
|
||||||
|
- Gantt
|
||||||
|
- Scheduler
|
||||||
|
- Per-user layout persistence
|
||||||
|
- Lazy loading and view preloading
|
||||||
|
- Shared dynamic datasource pipeline across layouts
|
||||||
|
- Shared filter/search/deep-link behavior
|
||||||
|
- Runtime refresh and query rebind
|
||||||
|
- Runtime state lifecycle:
|
||||||
|
- Save state
|
||||||
|
- Load state
|
||||||
|
- Reset state
|
||||||
|
- Runtime import/export actions from UI flows
|
||||||
|
- Popup and fullscreen editing scenarios
|
||||||
|
- SubForm relation-aware context propagation and tab navigation
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. Dynamic Component Development Policy
|
||||||
|
|
||||||
|
Allowed approach:
|
||||||
|
|
||||||
|
- Configure and bind existing dynamic component infrastructure.
|
||||||
|
|
||||||
|
Disallowed by default:
|
||||||
|
|
||||||
|
- New custom React pages/components for normal business requirements.
|
||||||
|
|
||||||
|
If user requests custom code explicitly:
|
||||||
|
|
||||||
|
- Provide a warning that low-code path is preferred.
|
||||||
|
- Offer configuration-first alternative first.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 11. Integrations
|
||||||
|
|
||||||
|
Notification channels:
|
||||||
|
|
||||||
|
- Sms
|
||||||
|
- Mail
|
||||||
|
- Rocket
|
||||||
|
- Desktop
|
||||||
|
- UiActivity
|
||||||
|
- UiToast
|
||||||
|
- WhatsApp
|
||||||
|
- Telegram
|
||||||
|
|
||||||
|
Sender module:
|
||||||
|
|
||||||
|
- Email (ABP Emailing + Amazon SES)
|
||||||
|
- SMS (ABP Sms + Posta Guvercini)
|
||||||
|
- Rocket.Chat (HTTP API)
|
||||||
|
- WhatsApp (HTTP API, template-based)
|
||||||
|
|
||||||
|
Notification routing model:
|
||||||
|
|
||||||
|
- Type + Channel routing
|
||||||
|
- Recipient targeting:
|
||||||
|
- All
|
||||||
|
- User
|
||||||
|
- Role
|
||||||
|
- OrganizationUnit
|
||||||
|
- Custom
|
||||||
|
|
||||||
|
MailQueue:
|
||||||
|
|
||||||
|
- Template-based body generation
|
||||||
|
- Attachment lifecycle
|
||||||
|
- File outputs (PDF, XLS, TXT)
|
||||||
|
- Queue execution and logging
|
||||||
|
|
||||||
|
AI workflow integration:
|
||||||
|
|
||||||
|
- n8n webhook chat endpoint
|
||||||
|
- LangChain agent flow + memory window
|
||||||
|
- Gemini chat model connector
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 12. Background Processing
|
||||||
|
|
||||||
|
Supported engines:
|
||||||
|
|
||||||
|
- Hangfire
|
||||||
|
- ABP Background Workers
|
||||||
|
|
||||||
|
Typical use cases:
|
||||||
|
|
||||||
|
- Scheduled SQL execution
|
||||||
|
- Notification dispatching
|
||||||
|
- Email and integration automation
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 13. Security and Compliance Rules
|
||||||
|
|
||||||
|
- Enforce RBAC and permission-driven visibility at all layers.
|
||||||
|
- Always include permission definitions in feature design.
|
||||||
|
- Never output real credentials, tokens, keys, or secrets.
|
||||||
|
- Use placeholders in examples.
|
||||||
|
- Maintain tenant isolation in every query and action.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 14. Required AI Output Contract
|
||||||
|
|
||||||
|
For each implementation proposal, AI output must include:
|
||||||
|
|
||||||
|
1. Goal
|
||||||
|
2. Decision flow result (which step used)
|
||||||
|
3. Artifacts to configure
|
||||||
|
4. SQL/query/endpoint design (if needed)
|
||||||
|
5. Menu + route + component mapping
|
||||||
|
6. Permission and role mapping
|
||||||
|
7. Tenant isolation notes
|
||||||
|
8. Validation and test checklist
|
||||||
|
9. Rollback strategy
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 15. Quality Gate Checklist
|
||||||
|
|
||||||
|
Before finalizing any answer, verify all:
|
||||||
|
|
||||||
|
- No unnecessary custom code suggestion
|
||||||
|
- No forbidden React component proposal
|
||||||
|
- Tenant awareness is explicit
|
||||||
|
- Permission strategy is explicit
|
||||||
|
- Menu-route binding is explicit
|
||||||
|
- Data safety and SQL parameterization considered
|
||||||
|
- Background processing considered when async/scheduled
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 16. Anti-Patterns (Do Not Suggest)
|
||||||
|
|
||||||
|
- Writing controllers for flows already covered by dynamic infrastructure
|
||||||
|
- Hardcoded endpoint mapping without permission model
|
||||||
|
- Static UI screens disconnected from list/menu/route model
|
||||||
|
- Direct non-tenant-safe query patterns
|
||||||
|
- Copying secrets into examples
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 17. Example Request Types AI Must Handle
|
||||||
|
|
||||||
|
- Create customer list screen
|
||||||
|
- Build purchase module
|
||||||
|
- Create dashboard with charts
|
||||||
|
- Generate reporting screen
|
||||||
|
- Add approval workflow
|
||||||
|
- Add integration-triggered notification process
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 18. Escalation Strategy
|
||||||
|
|
||||||
|
When configuration cannot satisfy requirement:
|
||||||
|
|
||||||
|
1. Try SQL-based design
|
||||||
|
2. Try Custom Endpoint
|
||||||
|
3. Try Dynamic Service
|
||||||
|
4. Propose minimal code change with explicit justification
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 19. Final Rule
|
||||||
|
|
||||||
|
This platform is not a traditional code-first app.
|
||||||
|
|
||||||
|
It is a runtime configurable low-code engine.
|
||||||
|
|
||||||
|
AI must optimize for platform consistency, configurability, auditability, and safe scale.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 20. Request Classification Matrix
|
||||||
|
|
||||||
|
Before proposing any solution, classify the request into one of these classes:
|
||||||
|
|
||||||
|
Class A - Pure configuration:
|
||||||
|
|
||||||
|
- Menu, route, list/form fields, validation, permissions, layout, filters
|
||||||
|
- Expected output: configuration-first plan only
|
||||||
|
|
||||||
|
Class B - Configuration + SQL:
|
||||||
|
|
||||||
|
- Reporting, complex filtering, aggregation, lookup dependencies
|
||||||
|
- Expected output: configuration + SQL + endpoint mapping
|
||||||
|
|
||||||
|
Class C - Configuration + Integration:
|
||||||
|
|
||||||
|
- Notification, sender, queue, webhook, workflow automation
|
||||||
|
- Expected output: configuration + integration contract + retry/fallback notes
|
||||||
|
|
||||||
|
Class D - Escalated code path:
|
||||||
|
|
||||||
|
- Only when A/B/C cannot satisfy requirement
|
||||||
|
- Expected output: explicit justification, minimal scope code plan, rollback plan
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 21. Response Playbooks
|
||||||
|
|
||||||
|
### 21.1 New Screen Playbook
|
||||||
|
|
||||||
|
1. Define business objective and actor roles.
|
||||||
|
2. Define ListForm and ListFormFields structure.
|
||||||
|
3. Define menu entry and route mapping.
|
||||||
|
4. Define permissions (R/C/U/D/E) and role mapping.
|
||||||
|
5. Define datasource and query strategy.
|
||||||
|
6. Define runtime filters, lookup, validations.
|
||||||
|
7. Define import/export and state requirements.
|
||||||
|
8. Define acceptance checklist.
|
||||||
|
|
||||||
|
### 21.2 Workflow/Approval Playbook
|
||||||
|
|
||||||
|
1. Define states and transition rules.
|
||||||
|
2. Define transition permissions by role.
|
||||||
|
3. Define transition side-effects (notification, queue, endpoint call).
|
||||||
|
4. Define audit log and failure handling.
|
||||||
|
5. Define rollback/compensation behavior.
|
||||||
|
|
||||||
|
### 21.3 Report/Dashboard Playbook
|
||||||
|
|
||||||
|
1. Define KPIs and dimensions.
|
||||||
|
2. Define SQL shaping and aggregation logic.
|
||||||
|
3. Define chart/pivot configuration.
|
||||||
|
4. Define date/tenant filters.
|
||||||
|
5. Define export needs and performance constraints.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 22. Mandatory Acceptance Criteria Template
|
||||||
|
|
||||||
|
Every final proposal must include a clear, testable acceptance list:
|
||||||
|
|
||||||
|
1. Feature can be enabled via configuration.
|
||||||
|
2. Tenant boundaries are preserved.
|
||||||
|
3. Required permissions block unauthorized access.
|
||||||
|
4. Menu-route-screen path is navigable.
|
||||||
|
5. Data operations are parameterized and safe.
|
||||||
|
6. Runtime filters and state behaviors work.
|
||||||
|
7. Integration events (if any) are observable and retry-safe.
|
||||||
|
8. No unnecessary custom code introduced.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 23. AI Output Templates
|
||||||
|
|
||||||
|
### 23.1 Configuration-only Output Template
|
||||||
|
|
||||||
|
1. Goal
|
||||||
|
2. Configuration artifacts
|
||||||
|
3. Menu/route mapping
|
||||||
|
4. Permission mapping
|
||||||
|
5. Validation checklist
|
||||||
|
|
||||||
|
### 23.2 Configuration + SQL Output Template
|
||||||
|
|
||||||
|
1. Goal
|
||||||
|
2. Required configuration artifacts
|
||||||
|
3. SQL design (inputs, outputs, filter parameters)
|
||||||
|
4. Endpoint/service mapping
|
||||||
|
5. Security and tenant notes
|
||||||
|
6. Validation checklist
|
||||||
|
|
||||||
|
### 23.3 Escalated Code Output Template
|
||||||
|
|
||||||
|
1. Why configuration is insufficient
|
||||||
|
2. Minimal code scope
|
||||||
|
3. Compatibility with existing dynamic architecture
|
||||||
|
4. Migration and rollback plan
|
||||||
|
5. Risk and test plan
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 24. Performance and Scalability Rules
|
||||||
|
|
||||||
|
- Prefer server-side filtering/sorting/paging for large datasets.
|
||||||
|
- Avoid unbounded result sets in dynamic queries.
|
||||||
|
- Use indexing-aware query patterns for reporting screens.
|
||||||
|
- Keep chart/pivot queries aggregation-focused.
|
||||||
|
- Separate interactive screen queries from heavy export queries.
|
||||||
|
- Use background jobs for long-running batch operations.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 25. Observability and Audit Rules
|
||||||
|
|
||||||
|
- Log key operations at list/form/integration boundaries.
|
||||||
|
- Preserve who/when/what audit metadata for critical changes.
|
||||||
|
- Capture integration failures with retry reason and payload context.
|
||||||
|
- Keep user-facing messages simple, logs detailed.
|
||||||
|
- Ensure state changes are diagnosable for support teams.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 26. Error Handling Policy
|
||||||
|
|
||||||
|
- Never expose raw secrets or internal stack details to end users.
|
||||||
|
- Return actionable, localized, user-level messages.
|
||||||
|
- Keep technical diagnostics in logs.
|
||||||
|
- For integration failures:
|
||||||
|
- Retry where safe
|
||||||
|
- Record dead-letter/failure state
|
||||||
|
- Provide manual replay strategy when needed
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 27. Change Management Policy
|
||||||
|
|
||||||
|
- Prefer additive changes over breaking modifications.
|
||||||
|
- Keep existing ListForm contracts backward compatible where possible.
|
||||||
|
- Document behavior changes in rollout notes.
|
||||||
|
- For destructive changes, require explicit migration path.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 28. Definition of Done for AI Proposals
|
||||||
|
|
||||||
|
A proposal is complete only if all are present:
|
||||||
|
|
||||||
|
1. Decision flow step selected and justified.
|
||||||
|
2. Configuration artifacts clearly listed.
|
||||||
|
3. Permission and tenant design included.
|
||||||
|
4. Menu-route-component mapping included.
|
||||||
|
5. Validation checklist included.
|
||||||
|
6. Risk/rollback notes included.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 29. Prohibited Suggestion Set
|
||||||
|
|
||||||
|
AI must not suggest:
|
||||||
|
|
||||||
|
- Quick custom React page as first solution
|
||||||
|
- Hardcoded tenant ids or environment secrets
|
||||||
|
- Direct SQL string concatenation with user input
|
||||||
|
- Endpoint exposure without permission definitions
|
||||||
|
- Bypassing platform menu-route model for business screens
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 30. Final Governance Statement
|
||||||
|
|
||||||
|
This manual is authoritative for AI behavior in this repository.
|
||||||
|
|
||||||
|
When in doubt, AI must choose the path that preserves:
|
||||||
|
|
||||||
|
- Low-code configurability
|
||||||
|
- Tenant safety
|
||||||
|
- Permission correctness
|
||||||
|
- Operational auditability
|
||||||
|
- Long-term maintainability
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 31. Seeder-Driven Low-Code Development Guide (Authoritative)
|
||||||
|
|
||||||
|
AI must learn and teach implementation flow primarily from these seed assets:
|
||||||
|
|
||||||
|
- api/src/Sozsoft.Platform.DbMigrator/Seeds/ListFormSeeder_Saas.cs
|
||||||
|
- api/src/Sozsoft.Platform.DbMigrator/Seeds/MenusData.json
|
||||||
|
- api/src/Sozsoft.Platform.DbMigrator/Seeds/PermissionsData.json
|
||||||
|
- api/src/Sozsoft.Platform.DbMigrator/Seeds/HostData.json
|
||||||
|
- api/src/Sozsoft.Platform.DbMigrator/Seeds/LanguagesData.json
|
||||||
|
|
||||||
|
If user asks "how to add a new module/screen", AI must answer with this exact operational sequence.
|
||||||
|
|
||||||
|
### 31.1 Step 1 - Create ListForm definition
|
||||||
|
|
||||||
|
Define a new `ListForm` with at least:
|
||||||
|
|
||||||
|
- `ListFormCode`, `Name`, `Title`
|
||||||
|
- `DataSourceCode`
|
||||||
|
- `SelectCommandType` + `SelectCommand`
|
||||||
|
- `KeyFieldName` + `KeyFieldDbSourceType`
|
||||||
|
- `PermissionJson` (create/read/update/delete/export/import/note)
|
||||||
|
- `EditingOptionJson` + `EditingFormJson`
|
||||||
|
- `FilterRowJson`, `HeaderFilterJson`, `SearchPanelJson`, `GroupPanelJson`
|
||||||
|
- `SelectionJson`, `ColumnOptionJson`, `PagerOptionJson`
|
||||||
|
- `InsertServiceAddress`, `UpdateServiceAddress`, `DeleteCommand`
|
||||||
|
|
||||||
|
For parent-child scenarios, define `SubFormsJson` relation mapping (ParentFieldName -> ChildFieldName, DbType).
|
||||||
|
|
||||||
|
### 31.2 Step 2 - Create ListFormFields
|
||||||
|
|
||||||
|
For each field, define runtime behavior through `ListFormField` records:
|
||||||
|
|
||||||
|
- Data binding: field name, db type, source
|
||||||
|
- UI behavior: visibility, order, width, grouping, fixed/band settings
|
||||||
|
- Editing behavior: editor type (`dxTextBox`, `dxSelectBox`, `dxNumberBox`, etc.), required, options
|
||||||
|
- Lookup behavior: data source type, display/value members, cascade rules
|
||||||
|
- Validation rules and default values
|
||||||
|
- Command/action columns where needed
|
||||||
|
|
||||||
|
AI must prioritize metadata-based field behavior instead of suggesting custom React forms.
|
||||||
|
|
||||||
|
### 31.3 Step 3 - Add menu and route mapping
|
||||||
|
|
||||||
|
From `MenusData.json` patterns, AI must define both:
|
||||||
|
|
||||||
|
- `Routes` entry:
|
||||||
|
- `key`, `path`, `componentPath`, `routeType`, `authority`
|
||||||
|
- `Menus` entry:
|
||||||
|
- `ParentCode`, `Code`, `DisplayName`, `Url`, `Icon`, `RequiredPermissionName`, `Order`
|
||||||
|
|
||||||
|
For dynamic list screens, base route pattern is:
|
||||||
|
|
||||||
|
- `/admin/list/{ListFormCode}`
|
||||||
|
|
||||||
|
AI must ensure menu URL and route path point to same screen contract.
|
||||||
|
|
||||||
|
### 31.4 Step 4 - Add permissions
|
||||||
|
|
||||||
|
From `PermissionsData.json` patterns, AI must define:
|
||||||
|
|
||||||
|
- Permission group (if missing)
|
||||||
|
- Permission definitions for feature root and actions
|
||||||
|
- Menu permission binding (`RequiredPermissionName`)
|
||||||
|
|
||||||
|
Minimum action set suggestion:
|
||||||
|
|
||||||
|
- `.Default`
|
||||||
|
- `.Create`
|
||||||
|
- `.Update`
|
||||||
|
- `.Delete`
|
||||||
|
- `.Export`
|
||||||
|
- `.Import`
|
||||||
|
- `.Note`
|
||||||
|
|
||||||
|
AI must never propose menu/route without permission contract.
|
||||||
|
|
||||||
|
### 31.5 Step 5 - Add settings/integration dependencies
|
||||||
|
|
||||||
|
From `HostData.json` patterns, AI must define required settings when feature depends on external services:
|
||||||
|
|
||||||
|
- Sender credentials and endpoints (sms/mail/whatsapp/rocket)
|
||||||
|
- AiBot integration endpoint and activation state
|
||||||
|
- Feature-specific setting keys, providers, encryption flags
|
||||||
|
|
||||||
|
If integration required and setting missing, AI must explicitly add "blocking prerequisite" note.
|
||||||
|
|
||||||
|
### 31.6 Step 6 - Data and service contract
|
||||||
|
|
||||||
|
AI must produce one of:
|
||||||
|
|
||||||
|
- Direct table/view select command
|
||||||
|
- Parameterized SQL via managed query
|
||||||
|
- Custom endpoint / dynamic service
|
||||||
|
|
||||||
|
AI must include tenant-safe filters and avoid string concatenation.
|
||||||
|
|
||||||
|
### 31.7 Step 7 - Delivery checklist
|
||||||
|
|
||||||
|
For every new low-code feature, AI output must list:
|
||||||
|
|
||||||
|
1. New `ListForm` code and purpose
|
||||||
|
2. `ListFormField` set (critical columns/editors/lookups)
|
||||||
|
3. Route record
|
||||||
|
4. Menu record
|
||||||
|
5. Permission records
|
||||||
|
6. Required settings/integrations
|
||||||
|
7. Validation and rollback notes
|
||||||
|
|
||||||
|
If this 7-item list is incomplete, proposal is not accepted.
|
||||||
|
|
||||||
|
### 31.8 AI response style for implementation requests
|
||||||
|
|
||||||
|
When user asks for a screen/module, AI must answer in this order:
|
||||||
|
|
||||||
|
1. Decision flow class (A/B/C/D)
|
||||||
|
2. Seeder-style artifacts to add (ListForm, Fields, Menu, Route, Permission, Setting)
|
||||||
|
3. If needed, SQL/endpoint contract
|
||||||
|
4. Test and rollback checklist
|
||||||
|
|
||||||
|
AI should produce practical, copy-adaptable artifact definitions and avoid abstract-only explanations.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 32. Mandatory Default Behaviors (Do Not Ask Repeatedly)
|
||||||
|
|
||||||
|
The following defaults are mandatory unless user explicitly overrides them.
|
||||||
|
|
||||||
|
### 32.1 SQL Table Designer default column behavior
|
||||||
|
|
||||||
|
When user requests creating a table and only specifies business columns (for example `FullName`, `Phone`), AI must still ensure platform-standard technical columns are included by default in SQL Query Manager table design flow:
|
||||||
|
|
||||||
|
- `TenantId` (multi-tenant compatibility)
|
||||||
|
- Full audited set:
|
||||||
|
- `Id`
|
||||||
|
- `CreationTime`
|
||||||
|
- `CreatorId`
|
||||||
|
- `LastModificationTime`
|
||||||
|
- `LastModifierId`
|
||||||
|
- `IsDeleted`
|
||||||
|
- `DeletionTime`
|
||||||
|
- `DeleterId`
|
||||||
|
|
||||||
|
Rules:
|
||||||
|
|
||||||
|
- Do not require user to explicitly request these columns each time.
|
||||||
|
- If user explicitly says "no tenant" or "no audit", then respect that override.
|
||||||
|
- Primary key strategy must remain compatible with existing index/key policy.
|
||||||
|
|
||||||
|
### 32.2 Wizard menu parent fallback behavior
|
||||||
|
|
||||||
|
When creating menu via Wizard/ListForm and user does not explicitly specify parent menu:
|
||||||
|
|
||||||
|
- Do not auto-place under `Definitions` by default.
|
||||||
|
- Create (or use) a dedicated new top-level parent menu for that feature/module.
|
||||||
|
- Assign top-level menu `Order` as next available order (`max(Order) + 1`) among root menus.
|
||||||
|
- Place the generated list/menu item under this newly created parent.
|
||||||
|
|
||||||
|
Rules:
|
||||||
|
|
||||||
|
- `Definitions` can be used only when user explicitly selects it.
|
||||||
|
- Permission contract must be created/bound for both parent and child menu items.
|
||||||
|
- Route/menu consistency remains mandatory (`Url` and screen contract must match).
|
||||||
|
|
||||||
|
### 32.3 AI enforcement requirement
|
||||||
|
|
||||||
|
AI must proactively apply these defaults in:
|
||||||
|
|
||||||
|
- implementation proposals,
|
||||||
|
- code/seed changes,
|
||||||
|
- migration and wizard behavior recommendations.
|
||||||
|
|
||||||
|
If AI output omits these defaults without explicit user override, output is non-compliant.
|
||||||
149
.github/instructions/list.instructions.md
vendored
Normal file
149
.github/instructions/list.instructions.md
vendored
Normal file
|
|
@ -0,0 +1,149 @@
|
||||||
|
|
||||||
|
# Sozsoft Platform Module Implementation Rules & Instructions
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
This document summarizes the rules, standards, and step-by-step instructions for implementing any module (e.g., CRM, MRP, HR) with a sample list (e.g., Opportunity, Order) in the Sozsoft Platform. Replace all placeholders (e.g., {Modul}, {Liste}, {Entity}) with your actual module, list, or entity names. Use as a reference for future development and as a prompt guide for similar tasks.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
|
||||||
|
## General Principles
|
||||||
|
- **Configuration First:** Always prefer platform configuration (menus, permissions, forms, localization) over custom code.
|
||||||
|
- **Modularization:** Each module (e.g., {Modul}) must have its own seeder, permission group, and localization entries.
|
||||||
|
- **Naming Conventions:**
|
||||||
|
- Table names: `{Modul}_T_{Entity}` (e.g., `Mrp_T_Order`)
|
||||||
|
- Menu/permission keys: `App.{Modul}`, `App.{Modul}.{Liste}`
|
||||||
|
- **Tenant Isolation:** All data, forms, and permissions must be tenant-aware.
|
||||||
|
- **Localization:** Every menu, list, and field must have a corresponding entry in `LanguagesData.json`.
|
||||||
|
- **No Redundant Code:** Avoid duplicating permission grants or seeding logic across modules.
|
||||||
|
- **SeedConsts Rule:** For every new module/entity, add a constant to `SeedConsts` (e.g., `public static class {Modul}` and `public const string {Liste}`) if it does not already exist. Never duplicate existing constants.
|
||||||
|
- **DeleteCommand Rule:** In all `ListFormSeeder_{Modul}.cs` files, the `DeleteCommand` property **must** use the `DefaultDeleteCommand` function, e.g., `DeleteCommand = DefaultDeleteCommand("{TabloAdı}")`. Never use a raw SQL string directly for `DeleteCommand`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Special File Handling Rules
|
||||||
|
|
||||||
|
- **MenusData.json:**
|
||||||
|
- Never modify the `Routes` section directly. Only `MenuGroups` and `Menus` sections can be changed for menu additions or updates. All menu additions must use the platform's configuration mechanisms or be added to `MenuGroups` and `Menus` as required by platform design.
|
||||||
|
|
||||||
|
- **LanguagesData.json:**
|
||||||
|
- When adding a new key, always check if the key already exists. Only add the key if it does not exist to prevent duplicates.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## File-by-File Change Summary
|
||||||
|
|
||||||
|
|
||||||
|
### 1. MenusData.json
|
||||||
|
- **{Modul} menu** added as a top-level menu with `ShortName: {Modul}`.
|
||||||
|
- **{Liste}** added as a child menu under {Modul}.
|
||||||
|
|
||||||
|
### 2. PermissionsData.json
|
||||||
|
- **{Modul} permissions** grouped under `App.{Modul}`.
|
||||||
|
- **{Liste} permissions** nested under {Modul} group.
|
||||||
|
|
||||||
|
### 3. ListFormSeeder_Administration.cs
|
||||||
|
- **Removed** {Liste} seeding logic (moved to {Modul} seeder).
|
||||||
|
|
||||||
|
### 4. ListFormSeeder_{Modul}.cs
|
||||||
|
- **Created** new seeder file for {Modul}.
|
||||||
|
- **Seeds** {Liste} list-form, referencing `{Modul}_T_{Entity}`.
|
||||||
|
|
||||||
|
### 5. SqlTables.sql
|
||||||
|
- **Renamed** {Liste} table to `{Modul}_T_{Entity}`.
|
||||||
|
- **Updated** all related constraints and references.
|
||||||
|
|
||||||
|
### 6. PlatformIdentityDataSeeder.cs
|
||||||
|
- **Removed** redundant {Modul} permission grant logic (now handled by PermissionsData.json).
|
||||||
|
|
||||||
|
### 7. LanguagesData.json
|
||||||
|
- **Added** localization entries for:
|
||||||
|
- {Modul} menu and {Liste} list
|
||||||
|
- {Liste} list fields (e.g., `FullName`, `Phone`)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step-by-Step Instructions (Prompt Examples)
|
||||||
|
|
||||||
|
|
||||||
|
### 1. Add a New Module (e.g., {Modul})
|
||||||
|
```
|
||||||
|
Yeni bir modül ekle (ör: {Modul}):
|
||||||
|
- MenusData.json'a kök menü olarak ekle
|
||||||
|
- PermissionsData.json'da ayrı bir PermissionGroup oluştur
|
||||||
|
- ListFormSeeder_{Modul}.cs dosyası oluştur ve list-form seed'ini buraya taşı
|
||||||
|
- SqlTables.sql'de tabloyu {Modul}_T_{Entity} olarak adlandır
|
||||||
|
- LanguagesData.json'a menü, liste ve alan çevirilerini ekle
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Add a New List Under a Module
|
||||||
|
```
|
||||||
|
Yeni bir liste ekle (ör: {Liste}):
|
||||||
|
- {Modul} ana menüsünün altına ekle
|
||||||
|
- ListFormSeeder_{Modul}.cs dosyasına seed kodunu ekle
|
||||||
|
- SqlTables.sql'de tabloyu {Modul}_T_{Entity} olarak oluştur
|
||||||
|
- PermissionsData.json'da ilgili izinleri {Modul} grubuna ekle
|
||||||
|
- LanguagesData.json'a liste ve alan çevirilerini ekle
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Enforce Tenant Isolation and Full Audit
|
||||||
|
```
|
||||||
|
Tablo ve list-form için tenant izolasyonu ve full-audit alanlarını ekle:
|
||||||
|
- Tabloya TenantId, CreationTime, CreatorId, LastModificationTime, LastModifierId, IsDeleted, DeleterId, DeletionTime alanlarını ekle
|
||||||
|
- List-form seed'inde tenant-aware ayarları kontrol et
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### 4. Add/Update Localization
|
||||||
|
```
|
||||||
|
Yeni menü, liste veya alan eklediğinde LanguagesData.json'a şu şekilde ekle:
|
||||||
|
- App.{Modul}
|
||||||
|
- App.{Modul}.{Liste}
|
||||||
|
- App.Listform.ListformField.{Alan}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. SeedConsts and DeleteCommand Rules
|
||||||
|
```
|
||||||
|
- SeedConsts.cs dosyasına, yeni modül veya entity için (ör: public static class {Modul} ve altında public const string {Liste}) sabit ekle. Eğer zaten varsa tekrar ekleme.
|
||||||
|
- ListFormSeeder_{Modul}.cs dosyalarında DeleteCommand satırı **her zaman** DefaultDeleteCommand fonksiyonu ile olmalı:
|
||||||
|
DeleteCommand = DefaultDeleteCommand("{TabloAdı}")
|
||||||
|
- DeleteCommand'da doğrudan SQL stringi **kullanma**.
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. Remove Redundant or Incorrect Code
|
||||||
|
```
|
||||||
|
- Farklı modüllerde aynı izin veya seed kodu varsa, sadece ilgili modülde bırak
|
||||||
|
- PlatformIdentityDataSeeder.cs'de Permission grant kodunu kaldır, PermissionsData.json'dan yönet
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
|
||||||
|
## Validation Checklist
|
||||||
|
- [ ] Menü ve izinler doğru hiyerarşide mi?
|
||||||
|
- [ ] List-form seed'leri doğru modül dosyasında mı?
|
||||||
|
- [ ] Tablo isimleri ve referansları standartlara uygun mu? (örn: {Modul}_T_{Entity})
|
||||||
|
- [ ] LanguagesData.json'da tüm yeni menü, liste ve alanlar var mı?
|
||||||
|
- [ ] Redundant kod veya izin grant'ı var mı?
|
||||||
|
- [ ] Seed ve migration sonrası UI'da çeviriler doğru görünüyor mu?
|
||||||
|
- [ ] SeedConsts.cs dosyasında ilgili sabitler var mı, tekrar eklenmemiş mi?
|
||||||
|
- [ ] ListFormSeeder dosyalarında DeleteCommand satırı DefaultDeleteCommand fonksiyonu ile mi atanmış?
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Rollback Strategy
|
||||||
|
- Değişiklikleri modül bazında geri al (örn: sadece {Modul} ile ilgili dosyaları revert et)
|
||||||
|
- JSON dosyalarında eski anahtarları ve çevirileri sil
|
||||||
|
- Seeder ve migration dosyalarını eski haline getir
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Quick Prompts for Future Use
|
||||||
|
- "Yeni bir modül ekle ve tüm standartlara göre yapılandır."
|
||||||
|
- "Yeni bir liste ekle, tenant izolasyonu ve çevirileriyle birlikte."
|
||||||
|
- "Mevcut bir modülde eksik olan çeviri veya izinleri tamamla."
|
||||||
|
- "Tüm {Modul} ile ilgili seed ve izinleri modüler yapıya uygun şekilde güncelle."
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
> **Not:** Tüm işlemlerden sonra seed'leri çalıştırıp UI'da menü ve çevirileri kontrol etmeyi unutma.
|
||||||
|
|
@ -44,6 +44,7 @@ COPY "src/Sozsoft.Platform.EntityFrameworkCore/Sozsoft.Platform.EntityFrameworkC
|
||||||
COPY "src/Sozsoft.Platform.HttpApi/Sozsoft.Platform.HttpApi.csproj" "src/Sozsoft.Platform.HttpApi/"
|
COPY "src/Sozsoft.Platform.HttpApi/Sozsoft.Platform.HttpApi.csproj" "src/Sozsoft.Platform.HttpApi/"
|
||||||
COPY "src/Sozsoft.Platform.HttpApi.Client/Sozsoft.Platform.HttpApi.Client.csproj" "src/Sozsoft.Platform.HttpApi.Client/"
|
COPY "src/Sozsoft.Platform.HttpApi.Client/Sozsoft.Platform.HttpApi.Client.csproj" "src/Sozsoft.Platform.HttpApi.Client/"
|
||||||
COPY "src/Sozsoft.Platform.HttpApi.Host/Sozsoft.Platform.HttpApi.Host.csproj" "src/Sozsoft.Platform.HttpApi.Host/"
|
COPY "src/Sozsoft.Platform.HttpApi.Host/Sozsoft.Platform.HttpApi.Host.csproj" "src/Sozsoft.Platform.HttpApi.Host/"
|
||||||
|
COPY "src/Sozsoft.Platform.DbMigrator/Sozsoft.Platform.DbMigrator.csproj" "src/Sozsoft.Platform.DbMigrator/"
|
||||||
COPY "test/Sozsoft.Platform.EntityFrameworkCore.Tests/Sozsoft.Platform.EntityFrameworkCore.Tests.csproj" "test/Sozsoft.Platform.EntityFrameworkCore.Tests/"
|
COPY "test/Sozsoft.Platform.EntityFrameworkCore.Tests/Sozsoft.Platform.EntityFrameworkCore.Tests.csproj" "test/Sozsoft.Platform.EntityFrameworkCore.Tests/"
|
||||||
COPY "test/Sozsoft.Platform.TestBase/Sozsoft.Platform.TestBase.csproj" "test/Sozsoft.Platform.TestBase/"
|
COPY "test/Sozsoft.Platform.TestBase/Sozsoft.Platform.TestBase.csproj" "test/Sozsoft.Platform.TestBase/"
|
||||||
RUN dotnet restore "src/Sozsoft.Platform.HttpApi.Host/Sozsoft.Platform.HttpApi.Host.csproj"
|
RUN dotnet restore "src/Sozsoft.Platform.HttpApi.Host/Sozsoft.Platform.HttpApi.Host.csproj"
|
||||||
|
|
@ -51,6 +52,7 @@ RUN dotnet restore "src/Sozsoft.Platform.HttpApi.Host/Sozsoft.Platform.HttpApi.H
|
||||||
COPY . .
|
COPY . .
|
||||||
RUN mkdir -p publish
|
RUN mkdir -p publish
|
||||||
RUN dotnet publish "src/Sozsoft.Platform.HttpApi.Host/Sozsoft.Platform.HttpApi.Host.csproj" -c Release -o /app/publish --no-restore
|
RUN dotnet publish "src/Sozsoft.Platform.HttpApi.Host/Sozsoft.Platform.HttpApi.Host.csproj" -c Release -o /app/publish --no-restore
|
||||||
|
RUN dotnet publish "src/Sozsoft.Platform.DbMigrator/Sozsoft.Platform.DbMigrator.csproj" -c Release -o /app/migrator
|
||||||
|
|
||||||
FROM mcr.microsoft.com/dotnet/aspnet:9.0-alpine AS final
|
FROM mcr.microsoft.com/dotnet/aspnet:9.0-alpine AS final
|
||||||
|
|
||||||
|
|
@ -97,4 +99,8 @@ EXPOSE 443
|
||||||
|
|
||||||
WORKDIR /srv/app
|
WORKDIR /srv/app
|
||||||
COPY --from=build /app/publish .
|
COPY --from=build /app/publish .
|
||||||
|
|
||||||
|
# Migrator publish çıktısını Setup modunun çağırabilmesi için kopyala
|
||||||
|
COPY --from=build /app/migrator /srv/Sozsoft.Platform.DbMigrator
|
||||||
|
|
||||||
ENTRYPOINT ["./Sozsoft.Platform.HttpApi.Host"]
|
ENTRYPOINT ["./Sozsoft.Platform.HttpApi.Host"]
|
||||||
|
|
|
||||||
|
|
@ -6,5 +6,6 @@ namespace Sozsoft.Platform.AiBots;
|
||||||
public class AiBotDto : FullAuditedEntityDto<Guid>
|
public class AiBotDto : FullAuditedEntityDto<Guid>
|
||||||
{
|
{
|
||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
|
public string ApiUrl { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,8 @@ public class AuditLogDto : EntityDto<Guid>
|
||||||
public string ApplicationName { get; set; }
|
public string ApplicationName { get; set; }
|
||||||
public Guid? UserId { get; protected set; }
|
public Guid? UserId { get; protected set; }
|
||||||
public string UserName { get; protected set; }
|
public string UserName { get; protected set; }
|
||||||
|
public Guid? TenantId { get; protected set; }
|
||||||
|
public string TenantName { get; protected set; }
|
||||||
public DateTime ExecutionTime { get; protected set; }
|
public DateTime ExecutionTime { get; protected set; }
|
||||||
public int ExecutionDuration { get; protected set; }
|
public int ExecutionDuration { get; protected set; }
|
||||||
public string ClientIpAddress { get; protected set; }
|
public string ClientIpAddress { get; protected set; }
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ using System.Collections.Generic;
|
||||||
using System.Data;
|
using System.Data;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
using Sozsoft.Platform.Enums;
|
|
||||||
using Volo.Abp.Application.Dtos;
|
using Volo.Abp.Application.Dtos;
|
||||||
|
|
||||||
namespace Sozsoft.Platform.ListForms;
|
namespace Sozsoft.Platform.ListForms;
|
||||||
|
|
@ -14,6 +13,7 @@ public class ColumnFormatDto : AuditedEntityDto<Guid>
|
||||||
|
|
||||||
public string FieldName { get; set; }
|
public string FieldName { get; set; }
|
||||||
public string CaptionName { get; set; }
|
public string CaptionName { get; set; }
|
||||||
|
public string PlaceHolder { get; set; }
|
||||||
public bool ReadOnly { get; set; }
|
public bool ReadOnly { get; set; }
|
||||||
public bool Visible { get; set; } // select sorgusuna dahildir fakat ekranda gosterilmez, kolon secicinin icerisinde bulunur
|
public bool Visible { get; set; } // select sorgusuna dahildir fakat ekranda gosterilmez, kolon secicinin icerisinde bulunur
|
||||||
public bool IsActive { get; set; } // sadece isActive olan alanlar sorguya dahil edilir
|
public bool IsActive { get; set; } // sadece isActive olan alanlar sorguya dahil edilir
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ public class GridEditingDto
|
||||||
public bool AllowDeleting { get; set; } = false;
|
public bool AllowDeleting { get; set; } = false;
|
||||||
public bool AllowAllDeleting { get; set; } = false;
|
public bool AllowAllDeleting { get; set; } = false;
|
||||||
public bool AllowAdding { get; set; } = false;
|
public bool AllowAdding { get; set; } = false;
|
||||||
|
public bool AllowDuplicate { get; set; } = false;
|
||||||
public bool UseIcons { get; set; } = false;
|
public bool UseIcons { get; set; } = false;
|
||||||
public bool ConfirmDelete { get; set; } = true;
|
public bool ConfirmDelete { get; set; } = true;
|
||||||
/// <summary>Accepted Values: 'first' | 'last' | 'pageBottom' | 'pageTop' | 'viewportBottom' | 'viewportTop'
|
/// <summary>Accepted Values: 'first' | 'last' | 'pageBottom' | 'pageTop' | 'viewportBottom' | 'viewportTop'
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ public interface IAuditLogAppService
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Authorize(AppCodes.AuditLogs)]
|
[Authorize(AppCodes.IdentityManagement.AuditLogs)]
|
||||||
public class AuditLogAppService
|
public class AuditLogAppService
|
||||||
: CrudAppService<AuditLog, AuditLogDto, Guid>
|
: CrudAppService<AuditLog, AuditLogDto, Guid>
|
||||||
, IAuditLogAppService
|
, IAuditLogAppService
|
||||||
|
|
|
||||||
|
|
@ -25,17 +25,16 @@ public class FileManagementAppService : ApplicationService, IFileManagementAppSe
|
||||||
private readonly BlobManager _blobContainer;
|
private readonly BlobManager _blobContainer;
|
||||||
private readonly IConfiguration _configuration;
|
private readonly IConfiguration _configuration;
|
||||||
|
|
||||||
private const string FileMetadataSuffix = ".metadata.json";
|
|
||||||
private const string FolderMarkerSuffix = ".folder";
|
private const string FolderMarkerSuffix = ".folder";
|
||||||
private const string IndexFileName = "index.json";
|
private const string IndexFileName = "index.json";
|
||||||
|
|
||||||
// Protected system folders that cannot be deleted, renamed, or moved
|
// Protected system folders that cannot be deleted, renamed, or moved
|
||||||
private static readonly HashSet<string> ProtectedFolders = new(StringComparer.OrdinalIgnoreCase)
|
private static readonly HashSet<string> ProtectedFolders = new(StringComparer.OrdinalIgnoreCase)
|
||||||
{
|
{
|
||||||
BlobContainerNames.Avatar,
|
// BlobContainerNames.Avatar,
|
||||||
BlobContainerNames.Import,
|
// BlobContainerNames.Import,
|
||||||
BlobContainerNames.Note,
|
// BlobContainerNames.Note,
|
||||||
BlobContainerNames.Intranet
|
// BlobContainerNames.Intranet
|
||||||
};
|
};
|
||||||
|
|
||||||
public FileManagementAppService(
|
public FileManagementAppService(
|
||||||
|
|
@ -49,8 +48,52 @@ public class FileManagementAppService : ApplicationService, IFileManagementAppSe
|
||||||
_configuration = configuration;
|
_configuration = configuration;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private const string HostFolderName = "host";
|
||||||
|
private const string TenantsFolderName = "tenants";
|
||||||
|
|
||||||
private string GetTenantPrefix(string tenantId) => $"tenants/{tenantId}/";
|
private string GetTenantPrefix(string tenantId) => $"tenants/{tenantId}/";
|
||||||
|
|
||||||
|
private static bool IsHostTenant(string tenantId) =>
|
||||||
|
tenantId.Equals(HostFolderName, StringComparison.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
private static string ToSystemPath(string path) =>
|
||||||
|
path.Replace('\\', Path.DirectorySeparatorChar).Replace('/', Path.DirectorySeparatorChar);
|
||||||
|
|
||||||
|
private static string GetCdnTenantRootPath(string cdnBasePath, string effectiveTenantId)
|
||||||
|
{
|
||||||
|
return IsHostTenant(effectiveTenantId)
|
||||||
|
? Path.Combine(cdnBasePath, HostFolderName)
|
||||||
|
: Path.Combine(cdnBasePath, TenantsFolderName, effectiveTenantId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string NormalizeExtension(string? extensionOrFileName)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(extensionOrFileName))
|
||||||
|
{
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
var ext = extensionOrFileName.Trim();
|
||||||
|
|
||||||
|
// If a path was provided, extract extension from the file name
|
||||||
|
if (ext.Contains('/') || ext.Contains('\\'))
|
||||||
|
{
|
||||||
|
ext = Path.GetExtension(ext);
|
||||||
|
}
|
||||||
|
else if (!ext.StartsWith('.') && ext.Contains('.'))
|
||||||
|
{
|
||||||
|
// File name like "photo.jpg" (but not an extension like ".jpg")
|
||||||
|
ext = Path.GetExtension(ext);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(ext))
|
||||||
|
{
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ext.Trim().TrimStart('.').ToLowerInvariant();
|
||||||
|
}
|
||||||
|
|
||||||
private string GetEffectiveTenantId(string? inputTenantId) =>
|
private string GetEffectiveTenantId(string? inputTenantId) =>
|
||||||
!string.IsNullOrEmpty(inputTenantId) ? inputTenantId : (_currentTenant.Id?.ToString() ?? "host");
|
!string.IsNullOrEmpty(inputTenantId) ? inputTenantId : (_currentTenant.Id?.ToString() ?? "host");
|
||||||
|
|
||||||
|
|
@ -75,16 +118,12 @@ public class FileManagementAppService : ApplicationService, IFileManagementAppSe
|
||||||
var rootFolder = pathParts[0];
|
var rootFolder = pathParts[0];
|
||||||
var isProtected = ProtectedFolders.Contains(rootFolder);
|
var isProtected = ProtectedFolders.Contains(rootFolder);
|
||||||
|
|
||||||
Logger.LogInformation($"IsProtectedFolder - Path: '{path}', RootFolder: '{rootFolder}', IsProtected: {isProtected}");
|
|
||||||
Logger.LogInformation($"Protected folders: {string.Join(", ", ProtectedFolders)}");
|
|
||||||
|
|
||||||
return isProtected;
|
return isProtected;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ValidateNotProtectedFolder(string id, string operation)
|
private void ValidateNotProtectedFolder(string id, string operation)
|
||||||
{
|
{
|
||||||
var decodedPath = DecodeIdAsPath(id);
|
var decodedPath = DecodeIdAsPath(id);
|
||||||
Logger.LogInformation($"ValidateNotProtectedFolder - ID: {id}, DecodedPath: {decodedPath}, Operation: {operation}");
|
|
||||||
|
|
||||||
if (IsProtectedFolder(decodedPath))
|
if (IsProtectedFolder(decodedPath))
|
||||||
{
|
{
|
||||||
|
|
@ -92,8 +131,6 @@ public class FileManagementAppService : ApplicationService, IFileManagementAppSe
|
||||||
Logger.LogWarning($"Blocked {operation} operation on protected folder: {folderName}");
|
Logger.LogWarning($"Blocked {operation} operation on protected folder: {folderName}");
|
||||||
throw new UserFriendlyException($"Cannot {operation} system folder '{folderName}'. This folder is protected.");
|
throw new UserFriendlyException($"Cannot {operation} system folder '{folderName}'. This folder is protected.");
|
||||||
}
|
}
|
||||||
|
|
||||||
Logger.LogInformation($"Folder {decodedPath} is not protected, allowing {operation}");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<List<FileMetadata>> GetFolderIndexAsync(string? parentId, string tenantId)
|
private async Task<List<FileMetadata>> GetFolderIndexAsync(string? parentId, string tenantId)
|
||||||
|
|
@ -112,11 +149,11 @@ public class FileManagementAppService : ApplicationService, IFileManagementAppSe
|
||||||
return items;
|
return items;
|
||||||
}
|
}
|
||||||
|
|
||||||
var fullPath = Path.Combine(cdnBasePath, tenantId);
|
var fullPath = GetCdnTenantRootPath(cdnBasePath, tenantId);
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(folderPath))
|
if (!string.IsNullOrEmpty(folderPath))
|
||||||
{
|
{
|
||||||
fullPath = Path.Combine(fullPath, folderPath);
|
fullPath = Path.Combine(fullPath, ToSystemPath(folderPath));
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
|
|
@ -170,6 +207,8 @@ public class FileManagementAppService : ApplicationService, IFileManagementAppSe
|
||||||
var fileInfo = new FileInfo(file);
|
var fileInfo = new FileInfo(file);
|
||||||
var relativePath = string.IsNullOrEmpty(folderPath) ? fileInfo.Name : $"{folderPath}/{fileInfo.Name}";
|
var relativePath = string.IsNullOrEmpty(folderPath) ? fileInfo.Name : $"{folderPath}/{fileInfo.Name}";
|
||||||
|
|
||||||
|
var normalizedExtension = NormalizeExtension(fileInfo.Extension);
|
||||||
|
|
||||||
items.Add(new FileMetadata
|
items.Add(new FileMetadata
|
||||||
{
|
{
|
||||||
Id = EncodePathAsId(relativePath),
|
Id = EncodePathAsId(relativePath),
|
||||||
|
|
@ -181,7 +220,9 @@ public class FileManagementAppService : ApplicationService, IFileManagementAppSe
|
||||||
Path = relativePath,
|
Path = relativePath,
|
||||||
ParentId = folderPath ?? "",
|
ParentId = folderPath ?? "",
|
||||||
IsReadOnly = false,
|
IsReadOnly = false,
|
||||||
TenantId = tenantId == "host" ? null : tenantId
|
TenantId = tenantId == "host" ? null : tenantId,
|
||||||
|
Extension = normalizedExtension,
|
||||||
|
MimeType = GetMimeType(normalizedExtension)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -264,13 +305,13 @@ public class FileManagementAppService : ApplicationService, IFileManagementAppSe
|
||||||
}
|
}
|
||||||
|
|
||||||
var tenantId = GetEffectiveTenantId(input.TenantId);
|
var tenantId = GetEffectiveTenantId(input.TenantId);
|
||||||
var parentPath = Path.Combine(cdnBasePath, tenantId);
|
var parentPath = GetCdnTenantRootPath(cdnBasePath, tenantId);
|
||||||
|
|
||||||
string? decodedParentId = null;
|
string? decodedParentId = null;
|
||||||
if (!string.IsNullOrEmpty(input.ParentId))
|
if (!string.IsNullOrEmpty(input.ParentId))
|
||||||
{
|
{
|
||||||
decodedParentId = DecodeIdAsPath(input.ParentId);
|
decodedParentId = DecodeIdAsPath(input.ParentId);
|
||||||
parentPath = Path.Combine(parentPath, decodedParentId);
|
parentPath = Path.Combine(parentPath, ToSystemPath(decodedParentId));
|
||||||
}
|
}
|
||||||
|
|
||||||
var folderPath = Path.Combine(parentPath, input.Name);
|
var folderPath = Path.Combine(parentPath, input.Name);
|
||||||
|
|
@ -351,11 +392,11 @@ public class FileManagementAppService : ApplicationService, IFileManagementAppSe
|
||||||
throw new UserFriendlyException("CDN path is not configured");
|
throw new UserFriendlyException("CDN path is not configured");
|
||||||
}
|
}
|
||||||
|
|
||||||
var fullCdnPath = Path.Combine(cdnBasePath, tenantId);
|
var fullCdnPath = GetCdnTenantRootPath(cdnBasePath, tenantId);
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(decodedParentId))
|
if (!string.IsNullOrEmpty(decodedParentId))
|
||||||
{
|
{
|
||||||
fullCdnPath = Path.Combine(fullCdnPath, decodedParentId);
|
fullCdnPath = Path.Combine(fullCdnPath, ToSystemPath(decodedParentId));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dizini oluştur
|
// Dizini oluştur
|
||||||
|
|
@ -379,6 +420,7 @@ public class FileManagementAppService : ApplicationService, IFileManagementAppSe
|
||||||
}
|
}
|
||||||
|
|
||||||
var fileInfo = new FileInfo(uniqueFileName);
|
var fileInfo = new FileInfo(uniqueFileName);
|
||||||
|
var normalizedExtension = NormalizeExtension(fileInfo.Extension);
|
||||||
|
|
||||||
var metadata = new FileMetadata
|
var metadata = new FileMetadata
|
||||||
{
|
{
|
||||||
|
|
@ -386,8 +428,8 @@ public class FileManagementAppService : ApplicationService, IFileManagementAppSe
|
||||||
Name = uniqueFileName,
|
Name = uniqueFileName,
|
||||||
Type = "file",
|
Type = "file",
|
||||||
Size = fileSize,
|
Size = fileSize,
|
||||||
Extension = fileInfo.Extension,
|
Extension = normalizedExtension,
|
||||||
MimeType = GetMimeType(fileInfo.Extension),
|
MimeType = GetMimeType(normalizedExtension),
|
||||||
CreatedAt = DateTime.UtcNow,
|
CreatedAt = DateTime.UtcNow,
|
||||||
ModifiedAt = DateTime.UtcNow,
|
ModifiedAt = DateTime.UtcNow,
|
||||||
Path = filePath,
|
Path = filePath,
|
||||||
|
|
@ -565,7 +607,7 @@ public class FileManagementAppService : ApplicationService, IFileManagementAppSe
|
||||||
|
|
||||||
var effectiveTenantId = GetEffectiveTenantId(tenantId);
|
var effectiveTenantId = GetEffectiveTenantId(tenantId);
|
||||||
var actualPath = DecodeIdAsPath(id);
|
var actualPath = DecodeIdAsPath(id);
|
||||||
var fullPath = Path.Combine(cdnBasePath, effectiveTenantId, actualPath);
|
var fullPath = Path.Combine(GetCdnTenantRootPath(cdnBasePath, effectiveTenantId), ToSystemPath(actualPath));
|
||||||
|
|
||||||
if (Directory.Exists(fullPath))
|
if (Directory.Exists(fullPath))
|
||||||
{
|
{
|
||||||
|
|
@ -608,7 +650,7 @@ public class FileManagementAppService : ApplicationService, IFileManagementAppSe
|
||||||
ValidateNotProtectedFolder(itemId, "delete");
|
ValidateNotProtectedFolder(itemId, "delete");
|
||||||
|
|
||||||
var actualPath = DecodeIdAsPath(itemId);
|
var actualPath = DecodeIdAsPath(itemId);
|
||||||
var fullPath = Path.Combine(cdnBasePath, tenantId, actualPath);
|
var fullPath = Path.Combine(GetCdnTenantRootPath(cdnBasePath, tenantId), ToSystemPath(actualPath));
|
||||||
|
|
||||||
if (Directory.Exists(fullPath))
|
if (Directory.Exists(fullPath))
|
||||||
{
|
{
|
||||||
|
|
@ -652,7 +694,7 @@ public class FileManagementAppService : ApplicationService, IFileManagementAppSe
|
||||||
}
|
}
|
||||||
|
|
||||||
var tenantId = GetEffectiveTenantId(input.TenantId);
|
var tenantId = GetEffectiveTenantId(input.TenantId);
|
||||||
var basePath = Path.Combine(cdnBasePath, tenantId);
|
var basePath = GetCdnTenantRootPath(cdnBasePath, tenantId);
|
||||||
|
|
||||||
string? targetPath = null;
|
string? targetPath = null;
|
||||||
if (!string.IsNullOrEmpty(input.TargetFolderId))
|
if (!string.IsNullOrEmpty(input.TargetFolderId))
|
||||||
|
|
@ -668,14 +710,14 @@ public class FileManagementAppService : ApplicationService, IFileManagementAppSe
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var sourcePath = DecodeIdAsPath(itemId);
|
var sourcePath = DecodeIdAsPath(itemId);
|
||||||
var sourceFullPath = Path.Combine(basePath, sourcePath);
|
var sourceFullPath = Path.Combine(basePath, ToSystemPath(sourcePath));
|
||||||
|
|
||||||
// Get source item name
|
// Get source item name
|
||||||
var sourceItemName = Path.GetFileName(sourcePath);
|
var sourceItemName = Path.GetFileName(sourcePath);
|
||||||
|
|
||||||
// Generate unique name if item already exists in target
|
// Generate unique name if item already exists in target
|
||||||
var targetItemPath = string.IsNullOrEmpty(targetPath) ? sourceItemName : $"{targetPath}/{sourceItemName}";
|
var targetItemPath = string.IsNullOrEmpty(targetPath) ? sourceItemName : $"{targetPath}/{sourceItemName}";
|
||||||
var targetFullPath = Path.Combine(basePath, targetItemPath);
|
var targetFullPath = Path.Combine(basePath, ToSystemPath(targetItemPath));
|
||||||
|
|
||||||
var uniqueTargetPath = GetUniqueItemPath(targetFullPath, sourceItemName);
|
var uniqueTargetPath = GetUniqueItemPath(targetFullPath, sourceItemName);
|
||||||
var finalTargetPath = uniqueTargetPath.Replace(basePath + Path.DirectorySeparatorChar, "").Replace(Path.DirectorySeparatorChar, '/');
|
var finalTargetPath = uniqueTargetPath.Replace(basePath + Path.DirectorySeparatorChar, "").Replace(Path.DirectorySeparatorChar, '/');
|
||||||
|
|
@ -711,7 +753,7 @@ public class FileManagementAppService : ApplicationService, IFileManagementAppSe
|
||||||
File.Copy(sourceFullPath, uniqueTargetPath);
|
File.Copy(sourceFullPath, uniqueTargetPath);
|
||||||
|
|
||||||
var fileInfo = new FileInfo(uniqueTargetPath);
|
var fileInfo = new FileInfo(uniqueTargetPath);
|
||||||
var extension = fileInfo.Extension;
|
var extension = NormalizeExtension(fileInfo.Extension);
|
||||||
|
|
||||||
copiedItems.Add(new FileItemDto
|
copiedItems.Add(new FileItemDto
|
||||||
{
|
{
|
||||||
|
|
@ -763,7 +805,7 @@ public class FileManagementAppService : ApplicationService, IFileManagementAppSe
|
||||||
}
|
}
|
||||||
|
|
||||||
var tenantId = GetEffectiveTenantId(input.TenantId);
|
var tenantId = GetEffectiveTenantId(input.TenantId);
|
||||||
var basePath = Path.Combine(cdnBasePath, tenantId);
|
var basePath = GetCdnTenantRootPath(cdnBasePath, tenantId);
|
||||||
|
|
||||||
string? targetPath = null;
|
string? targetPath = null;
|
||||||
if (!string.IsNullOrEmpty(input.TargetFolderId))
|
if (!string.IsNullOrEmpty(input.TargetFolderId))
|
||||||
|
|
@ -782,14 +824,14 @@ public class FileManagementAppService : ApplicationService, IFileManagementAppSe
|
||||||
ValidateNotProtectedFolder(itemId, "move");
|
ValidateNotProtectedFolder(itemId, "move");
|
||||||
|
|
||||||
var sourcePath = DecodeIdAsPath(itemId);
|
var sourcePath = DecodeIdAsPath(itemId);
|
||||||
var sourceFullPath = Path.Combine(basePath, sourcePath);
|
var sourceFullPath = Path.Combine(basePath, ToSystemPath(sourcePath));
|
||||||
|
|
||||||
// Get source item name
|
// Get source item name
|
||||||
var sourceItemName = Path.GetFileName(sourcePath);
|
var sourceItemName = Path.GetFileName(sourcePath);
|
||||||
|
|
||||||
// Generate target path
|
// Generate target path
|
||||||
var targetItemPath = string.IsNullOrEmpty(targetPath) ? sourceItemName : $"{targetPath}/{sourceItemName}";
|
var targetItemPath = string.IsNullOrEmpty(targetPath) ? sourceItemName : $"{targetPath}/{sourceItemName}";
|
||||||
var targetFullPath = Path.Combine(basePath, targetItemPath);
|
var targetFullPath = Path.Combine(basePath, ToSystemPath(targetItemPath));
|
||||||
|
|
||||||
// Check if moving to same location
|
// Check if moving to same location
|
||||||
if (Path.GetFullPath(sourceFullPath) == Path.GetFullPath(targetFullPath))
|
if (Path.GetFullPath(sourceFullPath) == Path.GetFullPath(targetFullPath))
|
||||||
|
|
@ -839,7 +881,7 @@ public class FileManagementAppService : ApplicationService, IFileManagementAppSe
|
||||||
File.Move(sourceFullPath, uniqueTargetPath);
|
File.Move(sourceFullPath, uniqueTargetPath);
|
||||||
|
|
||||||
var fileInfo = new FileInfo(uniqueTargetPath);
|
var fileInfo = new FileInfo(uniqueTargetPath);
|
||||||
var extension = fileInfo.Extension;
|
var extension = NormalizeExtension(fileInfo.Extension);
|
||||||
|
|
||||||
movedItems.Add(new FileItemDto
|
movedItems.Add(new FileItemDto
|
||||||
{
|
{
|
||||||
|
|
@ -887,7 +929,7 @@ public class FileManagementAppService : ApplicationService, IFileManagementAppSe
|
||||||
|
|
||||||
var effectiveTenantId = GetEffectiveTenantId(tenantId);
|
var effectiveTenantId = GetEffectiveTenantId(tenantId);
|
||||||
var actualPath = DecodeIdAsPath(id);
|
var actualPath = DecodeIdAsPath(id);
|
||||||
var fullFilePath = Path.Combine(cdnBasePath, effectiveTenantId, actualPath);
|
var fullFilePath = Path.Combine(GetCdnTenantRootPath(cdnBasePath, effectiveTenantId), ToSystemPath(actualPath));
|
||||||
|
|
||||||
if (!File.Exists(fullFilePath))
|
if (!File.Exists(fullFilePath))
|
||||||
{
|
{
|
||||||
|
|
@ -1013,7 +1055,16 @@ public class FileManagementAppService : ApplicationService, IFileManagementAppSe
|
||||||
|
|
||||||
private string GetMimeType(string extension)
|
private string GetMimeType(string extension)
|
||||||
{
|
{
|
||||||
return extension.ToLowerInvariant() switch
|
if (string.IsNullOrWhiteSpace(extension))
|
||||||
|
{
|
||||||
|
return "application/octet-stream";
|
||||||
|
}
|
||||||
|
|
||||||
|
var normalized = extension.Trim();
|
||||||
|
normalized = normalized.StartsWith('.') ? normalized : "." + normalized;
|
||||||
|
normalized = normalized.ToLowerInvariant();
|
||||||
|
|
||||||
|
return normalized switch
|
||||||
{
|
{
|
||||||
".txt" => "text/plain",
|
".txt" => "text/plain",
|
||||||
".pdf" => "application/pdf",
|
".pdf" => "application/pdf",
|
||||||
|
|
@ -1066,7 +1117,6 @@ public class FileManagementAppService : ApplicationService, IFileManagementAppSe
|
||||||
|
|
||||||
// Decode the folderId to get the actual path
|
// Decode the folderId to get the actual path
|
||||||
var decodedPath = DecodeIdAsPath(folderId);
|
var decodedPath = DecodeIdAsPath(folderId);
|
||||||
Logger.LogInformation($"GetFolderPath - FolderId: {folderId}, DecodedPath: {decodedPath}");
|
|
||||||
|
|
||||||
// Split path into parts and build breadcrumb
|
// Split path into parts and build breadcrumb
|
||||||
var pathParts = decodedPath.Split('/', StringSplitOptions.RemoveEmptyEntries);
|
var pathParts = decodedPath.Split('/', StringSplitOptions.RemoveEmptyEntries);
|
||||||
|
|
@ -1078,8 +1128,6 @@ public class FileManagementAppService : ApplicationService, IFileManagementAppSe
|
||||||
var pathUpToCurrent = string.Join("/", pathParts.Take(i + 1));
|
var pathUpToCurrent = string.Join("/", pathParts.Take(i + 1));
|
||||||
currentEncodedPath = EncodePathAsId(pathUpToCurrent);
|
currentEncodedPath = EncodePathAsId(pathUpToCurrent);
|
||||||
|
|
||||||
Logger.LogInformation($"PathItem {i}: Name='{pathParts[i]}', Id='{currentEncodedPath}', PathUpToCurrent='{pathUpToCurrent}'");
|
|
||||||
|
|
||||||
pathItems.Add(new PathItemDto
|
pathItems.Add(new PathItemDto
|
||||||
{
|
{
|
||||||
Id = currentEncodedPath,
|
Id = currentEncodedPath,
|
||||||
|
|
@ -1087,7 +1135,6 @@ public class FileManagementAppService : ApplicationService, IFileManagementAppSe
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Logger.LogInformation($"Returning {pathItems.Count} breadcrumb items");
|
|
||||||
return new FolderPathDto { Path = pathItems };
|
return new FolderPathDto { Path = pathItems };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ using Volo.Abp.Domain.Repositories;
|
||||||
|
|
||||||
namespace Sozsoft.Platform.GlobalSearchs;
|
namespace Sozsoft.Platform.GlobalSearchs;
|
||||||
|
|
||||||
[Authorize(PlatformConsts.AppCodes.Settings.GlobalSearch)]
|
[Authorize(PlatformConsts.AppCodes.Definitions.GlobalSearch)]
|
||||||
public class GlobalSearchAppService : PlatformAppService
|
public class GlobalSearchAppService : PlatformAppService
|
||||||
{
|
{
|
||||||
private readonly IRepository<GlobalSearch, int> repo;
|
private readonly IRepository<GlobalSearch, int> repo;
|
||||||
|
|
|
||||||
|
|
@ -52,6 +52,7 @@ public class ListFormFieldsAppService : CrudAppService<
|
||||||
entity.FieldName = updateInput.FieldName;
|
entity.FieldName = updateInput.FieldName;
|
||||||
entity.CultureName = updateInput.CultureName;
|
entity.CultureName = updateInput.CultureName;
|
||||||
entity.CaptionName = updateInput.CaptionName;
|
entity.CaptionName = updateInput.CaptionName;
|
||||||
|
entity.PlaceHolder = updateInput.PlaceHolder;
|
||||||
entity.BandName = updateInput.BandName;
|
entity.BandName = updateInput.BandName;
|
||||||
entity.IsActive = updateInput.IsActive;
|
entity.IsActive = updateInput.IsActive;
|
||||||
entity.Visible = updateInput.Visible;
|
entity.Visible = updateInput.Visible;
|
||||||
|
|
@ -128,6 +129,7 @@ public class ListFormFieldsAppService : CrudAppService<
|
||||||
{
|
{
|
||||||
item.FieldName = input.FieldName;
|
item.FieldName = input.FieldName;
|
||||||
item.CaptionName = input.CaptionName;
|
item.CaptionName = input.CaptionName;
|
||||||
|
item.PlaceHolder = input.PlaceHolder;
|
||||||
item.BandName = input.BandName;
|
item.BandName = input.BandName;
|
||||||
item.SourceDbType = input.SourceDbType;
|
item.SourceDbType = input.SourceDbType;
|
||||||
item.Alignment = input.Alignment;
|
item.Alignment = input.Alignment;
|
||||||
|
|
@ -251,6 +253,7 @@ public class ListFormFieldsAppService : CrudAppService<
|
||||||
{
|
{
|
||||||
field.BandName = sourceField.BandName;
|
field.BandName = sourceField.BandName;
|
||||||
field.CaptionName = sourceField.CaptionName;
|
field.CaptionName = sourceField.CaptionName;
|
||||||
|
field.PlaceHolder = sourceField.PlaceHolder;
|
||||||
field.SourceDbType = sourceField.SourceDbType;
|
field.SourceDbType = sourceField.SourceDbType;
|
||||||
}
|
}
|
||||||
if (input.CopiedFields.All || input.CopiedFields.Options)
|
if (input.CopiedFields.All || input.CopiedFields.Options)
|
||||||
|
|
|
||||||
|
|
@ -83,6 +83,7 @@ public class ListFormQueryPreviewAppService : PlatformAppService
|
||||||
{
|
{
|
||||||
var authType = op switch
|
var authType = op switch
|
||||||
{
|
{
|
||||||
|
OperationEnum.Duplicate => AuthorizationTypeEnum.Create,
|
||||||
OperationEnum.Insert => AuthorizationTypeEnum.Create,
|
OperationEnum.Insert => AuthorizationTypeEnum.Create,
|
||||||
OperationEnum.Update => AuthorizationTypeEnum.Update,
|
OperationEnum.Update => AuthorizationTypeEnum.Update,
|
||||||
OperationEnum.Delete => AuthorizationTypeEnum.Delete,
|
OperationEnum.Delete => AuthorizationTypeEnum.Delete,
|
||||||
|
|
@ -104,7 +105,7 @@ public class ListFormQueryPreviewAppService : PlatformAppService
|
||||||
|
|
||||||
var (_, _, dataSourceType) = await dynamicDataManager.GetAsync(listForm.IsTenant, listForm.DataSourceCode);
|
var (_, _, dataSourceType) = await dynamicDataManager.GetAsync(listForm.IsTenant, listForm.DataSourceCode);
|
||||||
|
|
||||||
return qManager.GenerateQuery(listForm, parameters, op, dataSourceType);
|
return qManager.GenerateQuery(listForm, listFormFields, parameters, op, dataSourceType);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,8 @@ using Sozsoft.Platform.Localization;
|
||||||
using Sozsoft.Platform.Queries;
|
using Sozsoft.Platform.Queries;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using static Sozsoft.Platform.PlatformConsts;
|
using static Sozsoft.Platform.PlatformConsts;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text.Json;
|
||||||
|
|
||||||
namespace Sozsoft.Platform.ListForms.Select;
|
namespace Sozsoft.Platform.ListForms.Select;
|
||||||
|
|
||||||
|
|
@ -14,15 +16,18 @@ public class ListFormDataAppService : PlatformAppService
|
||||||
private readonly IListFormAuthorizationManager authManager;
|
private readonly IListFormAuthorizationManager authManager;
|
||||||
private readonly IQueryManager qManager;
|
private readonly IQueryManager qManager;
|
||||||
private readonly IHttpContextAccessor httpContextAccessor;
|
private readonly IHttpContextAccessor httpContextAccessor;
|
||||||
|
private readonly IListFormSelectAppService listFormSelectAppService;
|
||||||
|
|
||||||
public ListFormDataAppService(
|
public ListFormDataAppService(
|
||||||
IListFormAuthorizationManager authManager,
|
IListFormAuthorizationManager authManager,
|
||||||
IQueryManager qManager,
|
IQueryManager qManager,
|
||||||
IHttpContextAccessor httpContextAccessor)
|
IHttpContextAccessor httpContextAccessor,
|
||||||
|
IListFormSelectAppService listFormSelectAppService)
|
||||||
{
|
{
|
||||||
this.authManager = authManager;
|
this.authManager = authManager;
|
||||||
this.qManager = qManager;
|
this.qManager = qManager;
|
||||||
this.httpContextAccessor = httpContextAccessor;
|
this.httpContextAccessor = httpContextAccessor;
|
||||||
|
this.listFormSelectAppService = listFormSelectAppService;
|
||||||
LocalizationResource = typeof(PlatformResource);
|
LocalizationResource = typeof(PlatformResource);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -39,6 +44,39 @@ public class ListFormDataAppService : PlatformAppService
|
||||||
return await qManager.GenerateAndRunQueryAsync<dynamic>(input.ListFormCode, OperationEnum.Insert, input.Data, queryParameters: queryParameters);
|
return await qManager.GenerateAndRunQueryAsync<dynamic>(input.ListFormCode, OperationEnum.Insert, input.Data, queryParameters: queryParameters);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<dynamic> PostDuplicateAsync(DataRequestDto input)
|
||||||
|
{
|
||||||
|
// Izin logic process
|
||||||
|
if (!await authManager.CanAccess(input.ListFormCode, AuthorizationTypeEnum.Create))
|
||||||
|
throw new Volo.Abp.UserFriendlyException(L[AppErrorCodes.NoAuth]);
|
||||||
|
|
||||||
|
var httpContext = httpContextAccessor.HttpContext
|
||||||
|
?? throw new InvalidOperationException("HTTP Context bulunamadı.");
|
||||||
|
|
||||||
|
var queryParameters = httpContext.Request.Query.ToDictionary(x => x.Key, x => x.Value);
|
||||||
|
|
||||||
|
object filter = new object[] { input.Data[0], "=", input.Keys[0] };
|
||||||
|
|
||||||
|
var selectRequest = new SelectRequestDto
|
||||||
|
{
|
||||||
|
ListFormCode = input.ListFormCode,
|
||||||
|
Filter = filter.ToString(),
|
||||||
|
Skip = 0,
|
||||||
|
Take = 1,
|
||||||
|
RequireTotalCount = false,
|
||||||
|
RequireGroupCount = false,
|
||||||
|
};
|
||||||
|
var selectResult = await listFormSelectAppService.GetSelectAsync(selectRequest);
|
||||||
|
var record = ((selectResult?.Data as System.Collections.IEnumerable)?.Cast<object>()?.FirstOrDefault()) ?? throw new Volo.Abp.UserFriendlyException("Kopyalanacak kayıt bulunamadı.");
|
||||||
|
|
||||||
|
if (record is not IDictionary<string, object> dict)
|
||||||
|
throw new Exception("DapperRow IDictionary'e çevrilemedi.");
|
||||||
|
|
||||||
|
input.Data = JsonSerializer.Serialize(dict);
|
||||||
|
|
||||||
|
return await qManager.GenerateAndRunQueryAsync<dynamic>(input.ListFormCode, OperationEnum.Duplicate, input.Data, null, queryParameters);
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<int> PostUpdateAsync(DataRequestDto input)
|
public async Task<int> PostUpdateAsync(DataRequestDto input)
|
||||||
{
|
{
|
||||||
// Izin logic process
|
// Izin logic process
|
||||||
|
|
|
||||||
|
|
@ -306,7 +306,7 @@ public class ListFormSelectAppService : PlatformAppService, IListFormSelectAppSe
|
||||||
};
|
};
|
||||||
|
|
||||||
var queryParameters = httpContextAccessor.HttpContext.Request.Query.ToDictionary(x => x.Key, x => x.Value);
|
var queryParameters = httpContextAccessor.HttpContext.Request.Query.ToDictionary(x => x.Key, x => x.Value);
|
||||||
var defaultFields = await defaultValueManager.GenerateDefaultValuesAsync(listForm, Enums.OperationEnum.Select, queryParameters: queryParameters);
|
var defaultFields = await defaultValueManager.GenerateDefaultValuesAsync(listForm, fields, Enums.OperationEnum.Select, queryParameters: queryParameters);
|
||||||
|
|
||||||
// Performans: Dictionary ile hızlı lookup
|
// Performans: Dictionary ile hızlı lookup
|
||||||
var columnFormatsDict = result.ColumnFormats.ToDictionary(c => c.FieldName, c => c);
|
var columnFormatsDict = result.ColumnFormats.ToDictionary(c => c.FieldName, c => c);
|
||||||
|
|
|
||||||
|
|
@ -61,9 +61,6 @@ public class ListFormWizardAppService(
|
||||||
var descLangKey = WizardConsts.WizardKeyDesc(wizardName);
|
var descLangKey = WizardConsts.WizardKeyDesc(wizardName);
|
||||||
var code = WizardConsts.WizardKey(wizardName);
|
var code = WizardConsts.WizardKey(wizardName);
|
||||||
|
|
||||||
// Clear Redis Cache
|
|
||||||
await _languageTextAppService.ClearRedisCacheAsync();
|
|
||||||
|
|
||||||
//Dil - Language Keys
|
//Dil - Language Keys
|
||||||
await CreateLangKey(nameLangKey, input.LanguageTextMenuEn, input.LanguageTextMenuTr);
|
await CreateLangKey(nameLangKey, input.LanguageTextMenuEn, input.LanguageTextMenuTr);
|
||||||
await CreateLangKey(titleLangKey, input.LanguageTextTitleEn, input.LanguageTextTitleTr);
|
await CreateLangKey(titleLangKey, input.LanguageTextTitleEn, input.LanguageTextTitleTr);
|
||||||
|
|
@ -256,6 +253,10 @@ public class ListFormWizardAppService(
|
||||||
await CreateLangKey(captionName, item.EnglishCaption, item.TurkishCaption);
|
await CreateLangKey(captionName, item.EnglishCaption, item.TurkishCaption);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Clear Redis Cache
|
||||||
|
await _languageTextAppService.ClearRedisCacheAsync();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<LanguageKey> CreateLangKey(string key, string textEn, string textTr)
|
private async Task<LanguageKey> CreateLangKey(string key, string textEn, string textTr)
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
{
|
{
|
||||||
"AiBots": [
|
"AiBots": [
|
||||||
{
|
{
|
||||||
"Name": "Chat Bot",
|
"Name": "Chat",
|
||||||
"Description": "A general purpose chat bot that can answer questions and have conversations.",
|
"Description": "Sözsoft Chat Bot",
|
||||||
"ApiUrl": "https://api.openai.com/v1/chat/completions",
|
"ApiUrl": "https://ai.sozsoft.com/webhook/chat",
|
||||||
"IsActive": true
|
"IsActive": true
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
@ -289,7 +289,7 @@
|
||||||
{
|
{
|
||||||
"code": "App.Sender.Sms.PostaGuvercini.Url",
|
"code": "App.Sender.Sms.PostaGuvercini.Url",
|
||||||
"nameKey": "App.Sender.Sms.PostaGuvercini.Url",
|
"nameKey": "App.Sender.Sms.PostaGuvercini.Url",
|
||||||
"descriptionKey": "App.Sender.Sms.PostaGuvercini.Url.Description",
|
"descriptionKey": "App.Sender.Url.Description",
|
||||||
"defaultValue": "https://www.postaguvercini.com/api_http",
|
"defaultValue": "https://www.postaguvercini.com/api_http",
|
||||||
"isVisibleToClients": false,
|
"isVisibleToClients": false,
|
||||||
"providers": "T|G|D",
|
"providers": "T|G|D",
|
||||||
|
|
@ -337,7 +337,7 @@
|
||||||
{
|
{
|
||||||
"code": "App.Sender.WhatsApp.Url",
|
"code": "App.Sender.WhatsApp.Url",
|
||||||
"nameKey": "App.Sender.WhatsApp.Url",
|
"nameKey": "App.Sender.WhatsApp.Url",
|
||||||
"descriptionKey": "App.Sender.WhatsApp.Url.Description",
|
"descriptionKey": "App.Sender.Url.Description",
|
||||||
"defaultValue": "https://graph.facebook.com/v21.0",
|
"defaultValue": "https://graph.facebook.com/v21.0",
|
||||||
"isVisibleToClients": false,
|
"isVisibleToClients": false,
|
||||||
"providers": "T|G|D",
|
"providers": "T|G|D",
|
||||||
|
|
@ -401,7 +401,7 @@
|
||||||
{
|
{
|
||||||
"code": "App.Sender.Rocket.Url",
|
"code": "App.Sender.Rocket.Url",
|
||||||
"nameKey": "App.Sender.Rocket.Url",
|
"nameKey": "App.Sender.Rocket.Url",
|
||||||
"descriptionKey": "App.Sender.Rocket.Url.Description",
|
"descriptionKey": "App.Sender.Url.Description",
|
||||||
"defaultValue": "https://chat.sozsoft.com/api/v1",
|
"defaultValue": "https://chat.sozsoft.com/api/v1",
|
||||||
"isVisibleToClients": false,
|
"isVisibleToClients": false,
|
||||||
"providers": "G|D",
|
"providers": "G|D",
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -903,7 +903,7 @@ public class ListFormSeeder_Administration : IDataSeedContributor, ITransientDep
|
||||||
CultureName = LanguageCodes.En,
|
CultureName = LanguageCodes.En,
|
||||||
SourceDbType = DbType.String,
|
SourceDbType = DbType.String,
|
||||||
FieldName = "Email",
|
FieldName = "Email",
|
||||||
CaptionName = "App.Listform.ListformField.Email",
|
CaptionName = "Abp.Account.EmailAddress",
|
||||||
Width = 300,
|
Width = 300,
|
||||||
ListOrderNo = 2,
|
ListOrderNo = 2,
|
||||||
Visible = true,
|
Visible = true,
|
||||||
|
|
@ -963,7 +963,7 @@ public class ListFormSeeder_Administration : IDataSeedContributor, ITransientDep
|
||||||
CultureName = LanguageCodes.En,
|
CultureName = LanguageCodes.En,
|
||||||
SourceDbType = DbType.String,
|
SourceDbType = DbType.String,
|
||||||
FieldName = "PhoneNumber",
|
FieldName = "PhoneNumber",
|
||||||
CaptionName = "App.Listform.ListformField.PhoneNumber",
|
CaptionName = "Abp.Identity.User.UserInformation.PhoneNumber",
|
||||||
Width = 150,
|
Width = 150,
|
||||||
ListOrderNo = 5,
|
ListOrderNo = 5,
|
||||||
Visible = true,
|
Visible = true,
|
||||||
|
|
@ -1016,152 +1016,8 @@ public class ListFormSeeder_Administration : IDataSeedContributor, ITransientDep
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Ip Restriction
|
|
||||||
listFormName = AppCodes.IdentityManagement.IpRestrictions;
|
|
||||||
if (!await _listFormRepository.AnyAsync(a => a.ListFormCode == listFormName))
|
|
||||||
{
|
|
||||||
var listForm = await _listFormRepository.InsertAsync(
|
|
||||||
new ListForm()
|
|
||||||
{
|
|
||||||
ListFormType = ListFormTypeEnum.List,
|
|
||||||
PageSize = 10,
|
|
||||||
ExportJson = DefaultExportJson,
|
|
||||||
IsSubForm = false,
|
|
||||||
ShowNote = true,
|
|
||||||
LayoutJson = DefaultLayoutJson(),
|
|
||||||
CultureName = LanguageCodes.En,
|
|
||||||
ListFormCode = listFormName,
|
|
||||||
Name = listFormName,
|
|
||||||
Title = listFormName,
|
|
||||||
DataSourceCode = SeedConsts.DataSources.DefaultCode,
|
|
||||||
IsTenant = true,
|
|
||||||
IsBranch = false,
|
|
||||||
IsOrganizationUnit = false,
|
|
||||||
Description = listFormName,
|
|
||||||
SelectCommandType = SelectCommandTypeEnum.Table,
|
|
||||||
SelectCommand = TableNameResolver.GetFullTableName(nameof(TableNameEnum.IpRestriction)),
|
|
||||||
KeyFieldName = "Id",
|
|
||||||
KeyFieldDbSourceType = DbType.Guid,
|
|
||||||
DefaultFilter = DefaultFilterJson,
|
|
||||||
SortMode = GridOptions.SortModeSingle,
|
|
||||||
FilterRowJson = DefaultFilterRowJson,
|
|
||||||
HeaderFilterJson = DefaultHeaderFilterJson,
|
|
||||||
SearchPanelJson = DefaultSearchPanelJson,
|
|
||||||
GroupPanelJson = DefaultGroupPanelJson,
|
|
||||||
SelectionJson = DefaultSelectionSingleJson,
|
|
||||||
ColumnOptionJson = DefaultColumnOptionJson(),
|
|
||||||
PermissionJson = DefaultPermissionJson(listFormName),
|
|
||||||
DeleteCommand = $"UPDATE \"{FullNameTable(TableNameEnum.IpRestriction)}\" SET \"DeleterId\"=@DeleterId, \"DeletionTime\"=CURRENT_TIMESTAMP, \"IsDeleted\"='true' WHERE \"Id\"=@Id",
|
|
||||||
DeleteFieldsDefaultValueJson = DefaultDeleteFieldsDefaultValueJson(),
|
|
||||||
PagerOptionJson = DefaultPagerOptionJson,
|
|
||||||
EditingOptionJson = DefaultEditingOptionJson(listFormName, 500, 350, true, true, true, true, false),
|
|
||||||
EditingFormJson = JsonSerializer.Serialize(new List<EditingFormDto>() {
|
|
||||||
new() {
|
|
||||||
Order=1, ColCount=1, ColSpan=1, ItemType="group", Items=
|
|
||||||
[
|
|
||||||
new EditingFormItemDto { Order = 1, DataField = "ResourceType", ColSpan = 1, IsRequired = true, EditorType2=EditorTypes.dxSelectBox, EditorOptions=EditorOptionValues.ShowClearButton },
|
|
||||||
new EditingFormItemDto { Order = 2, DataField = "ResourceId", ColSpan = 1, EditorType2=EditorTypes.dxSelectBox, EditorOptions=EditorOptionValues.ShowClearButton },
|
|
||||||
new EditingFormItemDto { Order = 3, DataField = "IP", ColSpan = 1, IsRequired = true, EditorType2=EditorTypes.dxTextBox },
|
|
||||||
]}
|
|
||||||
}),
|
|
||||||
InsertFieldsDefaultValueJson = DefaultInsertFieldsDefaultValueJson(),
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
#region Ip Restriction Fields
|
|
||||||
await _listFormFieldRepository.InsertManyAsync([
|
|
||||||
new() {
|
|
||||||
ListFormCode = listForm.ListFormCode,
|
|
||||||
CultureName = LanguageCodes.En,
|
|
||||||
SourceDbType = DbType.Guid,
|
|
||||||
FieldName = "Id",
|
|
||||||
CaptionName = "App.Listform.ListformField.Id",
|
|
||||||
Width = 100,
|
|
||||||
ListOrderNo = 1,
|
|
||||||
Visible = false,
|
|
||||||
IsActive = true,
|
|
||||||
IsDeleted = false,
|
|
||||||
ColumnCustomizationJson = DefaultColumnCustomizationJson,
|
|
||||||
PermissionJson = DefaultFieldPermissionJson(listForm.Name),
|
|
||||||
PivotSettingsJson = DefaultPivotSettingsJson
|
|
||||||
},
|
|
||||||
new() {
|
|
||||||
ListFormCode = listForm.ListFormCode,
|
|
||||||
CultureName = LanguageCodes.En,
|
|
||||||
SourceDbType = DbType.String,
|
|
||||||
FieldName = "ResourceType",
|
|
||||||
CaptionName = "App.Listform.ListformField.ResourceType",
|
|
||||||
Width = 400,
|
|
||||||
ListOrderNo = 2,
|
|
||||||
Visible = true,
|
|
||||||
IsActive = true,
|
|
||||||
IsDeleted = false,
|
|
||||||
SortIndex = 1,
|
|
||||||
SortDirection = GridColumnOptions.SortOrderAsc,
|
|
||||||
AllowSearch = true,
|
|
||||||
LookupJson = JsonSerializer.Serialize(new LookupDto
|
|
||||||
{
|
|
||||||
DataSourceType = UiLookupDataSourceTypeEnum.StaticData,
|
|
||||||
DisplayExpr = "name",
|
|
||||||
ValueExpr = "key",
|
|
||||||
LookupQuery = JsonSerializer.Serialize(new LookupDataDto[] {
|
|
||||||
new () { Key="User", Name="User" },
|
|
||||||
new () { Key="Role", Name="Role" },
|
|
||||||
new () { Key="Global", Name="Global" },
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
ValidationRuleJson = DefaultValidationRuleRequiredJson,
|
|
||||||
ColumnCustomizationJson = DefaultColumnCustomizationJson,
|
|
||||||
PermissionJson = DefaultFieldPermissionJson(listForm.Name),
|
|
||||||
PivotSettingsJson = DefaultPivotSettingsJson
|
|
||||||
},
|
|
||||||
new() {
|
|
||||||
ListFormCode = listForm.ListFormCode,
|
|
||||||
CultureName = LanguageCodes.En,
|
|
||||||
SourceDbType = DbType.String,
|
|
||||||
FieldName = "ResourceId",
|
|
||||||
CaptionName = "App.Listform.ListformField.ResourceId",
|
|
||||||
Width = 400,
|
|
||||||
ListOrderNo = 3,
|
|
||||||
Visible = true,
|
|
||||||
IsActive = true,
|
|
||||||
IsDeleted = false,
|
|
||||||
AllowSearch = true,
|
|
||||||
LookupJson = JsonSerializer.Serialize(new LookupDto {
|
|
||||||
DataSourceType = UiLookupDataSourceTypeEnum.Query,
|
|
||||||
DisplayExpr = "Name",
|
|
||||||
ValueExpr = "Key",
|
|
||||||
LookupQuery = $"SELECT \"UserName\" AS \"Key\", \"UserName\" AS \"Name\" FROM \"AbpUsers\" UNION SELECT \"Name\" AS \"Key\", \"Name\" AS \"Name\" FROM \"AbpRoles\"",
|
|
||||||
}),
|
|
||||||
ColumnCustomizationJson = DefaultColumnCustomizationJson,
|
|
||||||
PermissionJson = DefaultFieldPermissionJson(listForm.Name),
|
|
||||||
PivotSettingsJson = DefaultPivotSettingsJson
|
|
||||||
},
|
|
||||||
new()
|
|
||||||
{
|
|
||||||
ListFormCode = listForm.ListFormCode,
|
|
||||||
CultureName = LanguageCodes.En,
|
|
||||||
SourceDbType = DbType.String,
|
|
||||||
FieldName = "IP",
|
|
||||||
CaptionName = "App.Listform.ListformField.IP",
|
|
||||||
Width = 100,
|
|
||||||
ListOrderNo = 4,
|
|
||||||
Visible = true,
|
|
||||||
IsActive = true,
|
|
||||||
IsDeleted = false,
|
|
||||||
AllowSearch = true,
|
|
||||||
ValidationRuleJson = DefaultValidationRuleRequiredJson,
|
|
||||||
ColumnCustomizationJson = DefaultColumnCustomizationJson,
|
|
||||||
PermissionJson = DefaultFieldPermissionJson(listForm.Name),
|
|
||||||
PivotSettingsJson = DefaultPivotSettingsJson
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
#endregion
|
|
||||||
}
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Audit Logs
|
#region Audit Logs
|
||||||
listFormName = AppCodes.AuditLogs;
|
listFormName = AppCodes.IdentityManagement.AuditLogs;
|
||||||
if (!await _listFormRepository.AnyAsync(a => a.ListFormCode == listFormName))
|
if (!await _listFormRepository.AnyAsync(a => a.ListFormCode == listFormName))
|
||||||
{
|
{
|
||||||
var listForm = await _listFormRepository.InsertAsync(
|
var listForm = await _listFormRepository.InsertAsync(
|
||||||
|
|
@ -1709,7 +1565,7 @@ public class ListFormSeeder_Administration : IDataSeedContributor, ITransientDep
|
||||||
SelectCommandType = SelectCommandTypeEnum.Table,
|
SelectCommandType = SelectCommandTypeEnum.Table,
|
||||||
SelectCommand = TableNameResolver.GetFullTableName(nameof(TableNameEnum.Sector)),
|
SelectCommand = TableNameResolver.GetFullTableName(nameof(TableNameEnum.Sector)),
|
||||||
KeyFieldName = "Id",
|
KeyFieldName = "Id",
|
||||||
KeyFieldDbSourceType = DbType.String,
|
KeyFieldDbSourceType = DbType.Guid,
|
||||||
DefaultFilter = DefaultFilterJson,
|
DefaultFilter = DefaultFilterJson,
|
||||||
SortMode = GridOptions.SortModeSingle,
|
SortMode = GridOptions.SortModeSingle,
|
||||||
FilterRowJson = DefaultFilterRowJson,
|
FilterRowJson = DefaultFilterRowJson,
|
||||||
|
|
@ -1720,9 +1576,9 @@ public class ListFormSeeder_Administration : IDataSeedContributor, ITransientDep
|
||||||
ColumnOptionJson = DefaultColumnOptionJson(),
|
ColumnOptionJson = DefaultColumnOptionJson(),
|
||||||
PermissionJson = DefaultPermissionJson(listFormName),
|
PermissionJson = DefaultPermissionJson(listFormName),
|
||||||
DeleteCommand = DefaultDeleteCommand(nameof(TableNameEnum.Sector)),
|
DeleteCommand = DefaultDeleteCommand(nameof(TableNameEnum.Sector)),
|
||||||
DeleteFieldsDefaultValueJson = DefaultDeleteFieldsDefaultValueJson(DbType.String),
|
DeleteFieldsDefaultValueJson = DefaultDeleteFieldsDefaultValueJson(),
|
||||||
PagerOptionJson = DefaultPagerOptionJson,
|
PagerOptionJson = DefaultPagerOptionJson,
|
||||||
InsertFieldsDefaultValueJson = DefaultInsertFieldsDefaultValueJson(DbType.String),
|
InsertFieldsDefaultValueJson = DefaultInsertFieldsDefaultValueJson(),
|
||||||
EditingOptionJson = DefaultEditingOptionJson(listFormName, 400, 200, true, true, true, true, false),
|
EditingOptionJson = DefaultEditingOptionJson(listFormName, 400, 200, true, true, true, true, false),
|
||||||
EditingFormJson = JsonSerializer.Serialize(new List<EditingFormDto>
|
EditingFormJson = JsonSerializer.Serialize(new List<EditingFormDto>
|
||||||
{
|
{
|
||||||
|
|
@ -1779,7 +1635,7 @@ public class ListFormSeeder_Administration : IDataSeedContributor, ITransientDep
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region WorkHour
|
#region WorkHour
|
||||||
listFormName = AppCodes.Definitions.WorkHour;
|
listFormName = AppCodes.Restrictions.WorkHour;
|
||||||
if (!await _listFormRepository.AnyAsync(a => a.ListFormCode == listFormName))
|
if (!await _listFormRepository.AnyAsync(a => a.ListFormCode == listFormName))
|
||||||
{
|
{
|
||||||
var listForm = await _listFormRepository.InsertAsync(
|
var listForm = await _listFormRepository.InsertAsync(
|
||||||
|
|
@ -1803,7 +1659,7 @@ public class ListFormSeeder_Administration : IDataSeedContributor, ITransientDep
|
||||||
SelectCommandType = SelectCommandTypeEnum.Table,
|
SelectCommandType = SelectCommandTypeEnum.Table,
|
||||||
SelectCommand = TableNameResolver.GetFullTableName(nameof(TableNameEnum.WorkHour)),
|
SelectCommand = TableNameResolver.GetFullTableName(nameof(TableNameEnum.WorkHour)),
|
||||||
KeyFieldName = "Id",
|
KeyFieldName = "Id",
|
||||||
KeyFieldDbSourceType = DbType.String,
|
KeyFieldDbSourceType = DbType.Guid,
|
||||||
DefaultFilter = DefaultFilterJson,
|
DefaultFilter = DefaultFilterJson,
|
||||||
SortMode = GridOptions.SortModeSingle,
|
SortMode = GridOptions.SortModeSingle,
|
||||||
FilterRowJson = DefaultFilterRowJson,
|
FilterRowJson = DefaultFilterRowJson,
|
||||||
|
|
@ -1814,8 +1670,8 @@ public class ListFormSeeder_Administration : IDataSeedContributor, ITransientDep
|
||||||
ColumnOptionJson = DefaultColumnOptionJson(),
|
ColumnOptionJson = DefaultColumnOptionJson(),
|
||||||
PermissionJson = DefaultPermissionJson(listFormName),
|
PermissionJson = DefaultPermissionJson(listFormName),
|
||||||
DeleteCommand = DefaultDeleteCommand(nameof(TableNameEnum.WorkHour)),
|
DeleteCommand = DefaultDeleteCommand(nameof(TableNameEnum.WorkHour)),
|
||||||
DeleteFieldsDefaultValueJson = DefaultDeleteFieldsDefaultValueJson(DbType.String),
|
DeleteFieldsDefaultValueJson = DefaultDeleteFieldsDefaultValueJson(),
|
||||||
InsertFieldsDefaultValueJson = DefaultInsertFieldsDefaultValueJson(DbType.String),
|
InsertFieldsDefaultValueJson = DefaultInsertFieldsDefaultValueJson(),
|
||||||
PagerOptionJson = DefaultPagerOptionJson,
|
PagerOptionJson = DefaultPagerOptionJson,
|
||||||
EditingOptionJson = DefaultEditingOptionJson(listFormName, 600, 600, true, true, true, true, false),
|
EditingOptionJson = DefaultEditingOptionJson(listFormName, 600, 600, true, true, true, true, false),
|
||||||
EditingFormJson = JsonSerializer.Serialize(new List<EditingFormDto>() {
|
EditingFormJson = JsonSerializer.Serialize(new List<EditingFormDto>() {
|
||||||
|
|
@ -2034,6 +1890,150 @@ public class ListFormSeeder_Administration : IDataSeedContributor, ITransientDep
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
#region Ip Restriction
|
||||||
|
listFormName = AppCodes.Restrictions.IpRestrictions;
|
||||||
|
if (!await _listFormRepository.AnyAsync(a => a.ListFormCode == listFormName))
|
||||||
|
{
|
||||||
|
var listForm = await _listFormRepository.InsertAsync(
|
||||||
|
new ListForm()
|
||||||
|
{
|
||||||
|
ListFormType = ListFormTypeEnum.List,
|
||||||
|
PageSize = 10,
|
||||||
|
ExportJson = DefaultExportJson,
|
||||||
|
IsSubForm = false,
|
||||||
|
ShowNote = true,
|
||||||
|
LayoutJson = DefaultLayoutJson(),
|
||||||
|
CultureName = LanguageCodes.En,
|
||||||
|
ListFormCode = listFormName,
|
||||||
|
Name = listFormName,
|
||||||
|
Title = listFormName,
|
||||||
|
DataSourceCode = SeedConsts.DataSources.DefaultCode,
|
||||||
|
IsTenant = true,
|
||||||
|
IsBranch = false,
|
||||||
|
IsOrganizationUnit = false,
|
||||||
|
Description = listFormName,
|
||||||
|
SelectCommandType = SelectCommandTypeEnum.Table,
|
||||||
|
SelectCommand = TableNameResolver.GetFullTableName(nameof(TableNameEnum.IpRestriction)),
|
||||||
|
KeyFieldName = "Id",
|
||||||
|
KeyFieldDbSourceType = DbType.Guid,
|
||||||
|
DefaultFilter = DefaultFilterJson,
|
||||||
|
SortMode = GridOptions.SortModeSingle,
|
||||||
|
FilterRowJson = DefaultFilterRowJson,
|
||||||
|
HeaderFilterJson = DefaultHeaderFilterJson,
|
||||||
|
SearchPanelJson = DefaultSearchPanelJson,
|
||||||
|
GroupPanelJson = DefaultGroupPanelJson,
|
||||||
|
SelectionJson = DefaultSelectionSingleJson,
|
||||||
|
ColumnOptionJson = DefaultColumnOptionJson(),
|
||||||
|
PermissionJson = DefaultPermissionJson(listFormName),
|
||||||
|
DeleteCommand = $"UPDATE \"{FullNameTable(TableNameEnum.IpRestriction)}\" SET \"DeleterId\"=@DeleterId, \"DeletionTime\"=CURRENT_TIMESTAMP, \"IsDeleted\"='true' WHERE \"Id\"=@Id",
|
||||||
|
DeleteFieldsDefaultValueJson = DefaultDeleteFieldsDefaultValueJson(),
|
||||||
|
PagerOptionJson = DefaultPagerOptionJson,
|
||||||
|
EditingOptionJson = DefaultEditingOptionJson(listFormName, 500, 350, true, true, true, true, false),
|
||||||
|
EditingFormJson = JsonSerializer.Serialize(new List<EditingFormDto>() {
|
||||||
|
new() {
|
||||||
|
Order=1, ColCount=1, ColSpan=1, ItemType="group", Items=
|
||||||
|
[
|
||||||
|
new EditingFormItemDto { Order = 1, DataField = "ResourceType", ColSpan = 1, IsRequired = true, EditorType2=EditorTypes.dxSelectBox, EditorOptions=EditorOptionValues.ShowClearButton },
|
||||||
|
new EditingFormItemDto { Order = 2, DataField = "ResourceId", ColSpan = 1, EditorType2=EditorTypes.dxSelectBox, EditorOptions=EditorOptionValues.ShowClearButton },
|
||||||
|
new EditingFormItemDto { Order = 3, DataField = "IP", ColSpan = 1, IsRequired = true, EditorType2=EditorTypes.dxTextBox },
|
||||||
|
]}
|
||||||
|
}),
|
||||||
|
InsertFieldsDefaultValueJson = DefaultInsertFieldsDefaultValueJson(),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
#region Ip Restriction Fields
|
||||||
|
await _listFormFieldRepository.InsertManyAsync([
|
||||||
|
new() {
|
||||||
|
ListFormCode = listForm.ListFormCode,
|
||||||
|
CultureName = LanguageCodes.En,
|
||||||
|
SourceDbType = DbType.Guid,
|
||||||
|
FieldName = "Id",
|
||||||
|
CaptionName = "App.Listform.ListformField.Id",
|
||||||
|
Width = 100,
|
||||||
|
ListOrderNo = 1,
|
||||||
|
Visible = false,
|
||||||
|
IsActive = true,
|
||||||
|
IsDeleted = false,
|
||||||
|
ColumnCustomizationJson = DefaultColumnCustomizationJson,
|
||||||
|
PermissionJson = DefaultFieldPermissionJson(listForm.Name),
|
||||||
|
PivotSettingsJson = DefaultPivotSettingsJson
|
||||||
|
},
|
||||||
|
new() {
|
||||||
|
ListFormCode = listForm.ListFormCode,
|
||||||
|
CultureName = LanguageCodes.En,
|
||||||
|
SourceDbType = DbType.String,
|
||||||
|
FieldName = "ResourceType",
|
||||||
|
CaptionName = "App.Listform.ListformField.ResourceType",
|
||||||
|
Width = 400,
|
||||||
|
ListOrderNo = 2,
|
||||||
|
Visible = true,
|
||||||
|
IsActive = true,
|
||||||
|
IsDeleted = false,
|
||||||
|
SortIndex = 1,
|
||||||
|
SortDirection = GridColumnOptions.SortOrderAsc,
|
||||||
|
AllowSearch = true,
|
||||||
|
LookupJson = JsonSerializer.Serialize(new LookupDto
|
||||||
|
{
|
||||||
|
DataSourceType = UiLookupDataSourceTypeEnum.StaticData,
|
||||||
|
DisplayExpr = "name",
|
||||||
|
ValueExpr = "key",
|
||||||
|
LookupQuery = JsonSerializer.Serialize(new LookupDataDto[] {
|
||||||
|
new () { Key="User", Name="User" },
|
||||||
|
new () { Key="Role", Name="Role" },
|
||||||
|
new () { Key="Global", Name="Global" },
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
ValidationRuleJson = DefaultValidationRuleRequiredJson,
|
||||||
|
ColumnCustomizationJson = DefaultColumnCustomizationJson,
|
||||||
|
PermissionJson = DefaultFieldPermissionJson(listForm.Name),
|
||||||
|
PivotSettingsJson = DefaultPivotSettingsJson
|
||||||
|
},
|
||||||
|
new() {
|
||||||
|
ListFormCode = listForm.ListFormCode,
|
||||||
|
CultureName = LanguageCodes.En,
|
||||||
|
SourceDbType = DbType.String,
|
||||||
|
FieldName = "ResourceId",
|
||||||
|
CaptionName = "App.Listform.ListformField.ResourceId",
|
||||||
|
Width = 400,
|
||||||
|
ListOrderNo = 3,
|
||||||
|
Visible = true,
|
||||||
|
IsActive = true,
|
||||||
|
IsDeleted = false,
|
||||||
|
AllowSearch = true,
|
||||||
|
LookupJson = JsonSerializer.Serialize(new LookupDto {
|
||||||
|
DataSourceType = UiLookupDataSourceTypeEnum.Query,
|
||||||
|
DisplayExpr = "Name",
|
||||||
|
ValueExpr = "Key",
|
||||||
|
LookupQuery = $"SELECT \"UserName\" AS \"Key\", \"UserName\" AS \"Name\" FROM \"AbpUsers\" UNION SELECT \"Name\" AS \"Key\", \"Name\" AS \"Name\" FROM \"AbpRoles\"",
|
||||||
|
}),
|
||||||
|
ColumnCustomizationJson = DefaultColumnCustomizationJson,
|
||||||
|
PermissionJson = DefaultFieldPermissionJson(listForm.Name),
|
||||||
|
PivotSettingsJson = DefaultPivotSettingsJson
|
||||||
|
},
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
ListFormCode = listForm.ListFormCode,
|
||||||
|
CultureName = LanguageCodes.En,
|
||||||
|
SourceDbType = DbType.String,
|
||||||
|
FieldName = "IP",
|
||||||
|
CaptionName = "App.Listform.ListformField.IP",
|
||||||
|
Width = 100,
|
||||||
|
ListOrderNo = 4,
|
||||||
|
Visible = true,
|
||||||
|
IsActive = true,
|
||||||
|
IsDeleted = false,
|
||||||
|
AllowSearch = true,
|
||||||
|
ValidationRuleJson = DefaultValidationRuleRequiredJson,
|
||||||
|
ColumnCustomizationJson = DefaultColumnCustomizationJson,
|
||||||
|
PermissionJson = DefaultFieldPermissionJson(listForm.Name),
|
||||||
|
PivotSettingsJson = DefaultPivotSettingsJson
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,12 +15,12 @@ public static class ListFormSeeder_DefaultJsons
|
||||||
return $"UPDATE \"{TableNameResolver.GetFullTableName(tableName)}\" SET \"DeleterId\"=@DeleterId, \"DeletionTime\"=CURRENT_TIMESTAMP, \"IsDeleted\"='true' WHERE \"Id\"=@Id";
|
return $"UPDATE \"{TableNameResolver.GetFullTableName(tableName)}\" SET \"DeleterId\"=@DeleterId, \"DeletionTime\"=CURRENT_TIMESTAMP, \"IsDeleted\"='true' WHERE \"Id\"=@Id";
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string DefaultInsertFieldsDefaultValueJson(DbType dbType = DbType.Guid) => JsonSerializer.Serialize(new FieldsDefaultValue[]
|
public static string DefaultInsertFieldsDefaultValueJson(DbType dbType = DbType.Guid, string newId = "@NEWID") => JsonSerializer.Serialize(new FieldsDefaultValue[]
|
||||||
{
|
{
|
||||||
new() { FieldName = "CreationTime", FieldDbType = DbType.DateTime, Value = "@NOW", CustomValueType = FieldCustomValueTypeEnum.CustomKey },
|
new() { FieldName = "CreationTime", FieldDbType = DbType.DateTime, Value = "@NOW", CustomValueType = FieldCustomValueTypeEnum.CustomKey },
|
||||||
new() { FieldName = "CreatorId", FieldDbType = DbType.Guid, Value = "@USERID", CustomValueType = FieldCustomValueTypeEnum.CustomKey },
|
new() { FieldName = "CreatorId", FieldDbType = DbType.Guid, Value = "@USERID", CustomValueType = FieldCustomValueTypeEnum.CustomKey },
|
||||||
new() { FieldName = "IsDeleted", FieldDbType = DbType.Boolean, Value = "false", CustomValueType = FieldCustomValueTypeEnum.Value },
|
new() { FieldName = "IsDeleted", FieldDbType = DbType.Boolean, Value = "false", CustomValueType = FieldCustomValueTypeEnum.Value },
|
||||||
new() { FieldName = "Id", FieldDbType = dbType, Value = "@NEWID", CustomValueType = FieldCustomValueTypeEnum.CustomKey }
|
new() { FieldName = "Id", FieldDbType = dbType, Value = newId, CustomValueType = FieldCustomValueTypeEnum.CustomKey }
|
||||||
});
|
});
|
||||||
|
|
||||||
public static string DefaultDeleteFieldsDefaultValueJson(DbType dbType = DbType.Guid) => JsonSerializer.Serialize(new FieldsDefaultValue[]
|
public static string DefaultDeleteFieldsDefaultValueJson(DbType dbType = DbType.Guid) => JsonSerializer.Serialize(new FieldsDefaultValue[]
|
||||||
|
|
|
||||||
|
|
@ -426,7 +426,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
|
||||||
CultureName = LanguageCodes.En,
|
CultureName = LanguageCodes.En,
|
||||||
SourceDbType = DbType.String,
|
SourceDbType = DbType.String,
|
||||||
FieldName = "Email",
|
FieldName = "Email",
|
||||||
CaptionName = "App.Listform.ListformField.Email",
|
CaptionName = "Abp.Account.EmailAddress",
|
||||||
Width = 170,
|
Width = 170,
|
||||||
ListOrderNo = 14,
|
ListOrderNo = 14,
|
||||||
Visible = true,
|
Visible = true,
|
||||||
|
|
@ -479,7 +479,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
|
||||||
CultureName = LanguageCodes.En,
|
CultureName = LanguageCodes.En,
|
||||||
SourceDbType = DbType.String,
|
SourceDbType = DbType.String,
|
||||||
FieldName = "PhoneNumber",
|
FieldName = "PhoneNumber",
|
||||||
CaptionName = "App.Listform.ListformField.PhoneNumber",
|
CaptionName = "Abp.Identity.User.UserInformation.PhoneNumber",
|
||||||
Width = 100,
|
Width = 100,
|
||||||
ListOrderNo = 17,
|
ListOrderNo = 17,
|
||||||
Visible = true,
|
Visible = true,
|
||||||
|
|
@ -684,7 +684,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
|
||||||
CultureName = LanguageCodes.En,
|
CultureName = LanguageCodes.En,
|
||||||
SourceDbType = DbType.String,
|
SourceDbType = DbType.String,
|
||||||
FieldName = "Code",
|
FieldName = "Code",
|
||||||
CaptionName = "App.Listform.ListformField.Code",
|
CaptionName = "App.Platform.Code",
|
||||||
Width = 100,
|
Width = 100,
|
||||||
ListOrderNo = 2,
|
ListOrderNo = 2,
|
||||||
Visible = true,
|
Visible = true,
|
||||||
|
|
@ -911,7 +911,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
|
||||||
CultureName = LanguageCodes.En,
|
CultureName = LanguageCodes.En,
|
||||||
SourceDbType = DbType.String,
|
SourceDbType = DbType.String,
|
||||||
FieldName = "Email",
|
FieldName = "Email",
|
||||||
CaptionName = "App.Listform.ListformField.Email",
|
CaptionName = "Abp.Account.EmailAddress",
|
||||||
Width = 170,
|
Width = 170,
|
||||||
ListOrderNo = 13,
|
ListOrderNo = 13,
|
||||||
Visible = true,
|
Visible = true,
|
||||||
|
|
@ -964,7 +964,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
|
||||||
CultureName = LanguageCodes.En,
|
CultureName = LanguageCodes.En,
|
||||||
SourceDbType = DbType.String,
|
SourceDbType = DbType.String,
|
||||||
FieldName = "PhoneNumber",
|
FieldName = "PhoneNumber",
|
||||||
CaptionName = "App.Listform.ListformField.PhoneNumber",
|
CaptionName = "Abp.Identity.User.UserInformation.PhoneNumber",
|
||||||
Width = 100,
|
Width = 100,
|
||||||
ListOrderNo = 16,
|
ListOrderNo = 16,
|
||||||
Visible = true,
|
Visible = true,
|
||||||
|
|
@ -1016,7 +1016,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Global Search
|
#region Global Search
|
||||||
listFormName = AppCodes.Settings.GlobalSearch;
|
listFormName = AppCodes.Definitions.GlobalSearch;
|
||||||
if (!await _listFormRepository.AnyAsync(a => a.ListFormCode == listFormName))
|
if (!await _listFormRepository.AnyAsync(a => a.ListFormCode == listFormName))
|
||||||
{
|
{
|
||||||
var listForm = await _listFormRepository.InsertAsync(
|
var listForm = await _listFormRepository.InsertAsync(
|
||||||
|
|
@ -1060,7 +1060,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
|
||||||
new() { Order=1, ColCount=1, ColSpan=1, ItemType="group", Items = [
|
new() { Order=1, ColCount=1, ColSpan=1, ItemType="group", Items = [
|
||||||
new EditingFormItemDto { Order=1, DataField="System", ColSpan=1, IsRequired=true, EditorType2=EditorTypes.dxTextBox },
|
new EditingFormItemDto { Order=1, DataField="System", ColSpan=1, IsRequired=true, EditorType2=EditorTypes.dxTextBox },
|
||||||
new EditingFormItemDto { Order=2, DataField="Group", ColSpan=1, IsRequired=true, EditorType2=EditorTypes.dxTextBox },
|
new EditingFormItemDto { Order=2, DataField="Group", ColSpan=1, IsRequired=true, EditorType2=EditorTypes.dxTextBox },
|
||||||
new EditingFormItemDto { Order=3, DataField="Term", ColSpan=1, IsRequired=true, EditorType2=EditorTypes.dxTextBox },
|
new EditingFormItemDto { Order=3, DataField="Term", ColSpan=1, IsRequired=true, EditorType2=EditorTypes.dxSelectBox, EditorOptions=EditorOptionValues.ShowClearButton },
|
||||||
new EditingFormItemDto { Order=4, DataField="Url", ColSpan=1, IsRequired=true, EditorType2=EditorTypes.dxTextBox },
|
new EditingFormItemDto { Order=4, DataField="Url", ColSpan=1, IsRequired=true, EditorType2=EditorTypes.dxTextBox },
|
||||||
new EditingFormItemDto { Order=5, DataField="Weight", ColSpan=1, IsRequired=true, EditorType2=EditorTypes.dxNumberBox, EditorOptions=EditorOptionValues.NumberStandartFormat() },
|
new EditingFormItemDto { Order=5, DataField="Weight", ColSpan=1, IsRequired=true, EditorType2=EditorTypes.dxNumberBox, EditorOptions=EditorOptionValues.NumberStandartFormat() },
|
||||||
]}
|
]}
|
||||||
|
|
@ -1134,6 +1134,12 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
|
||||||
IsActive = true,
|
IsActive = true,
|
||||||
IsDeleted = false,
|
IsDeleted = false,
|
||||||
AllowSearch = true,
|
AllowSearch = true,
|
||||||
|
LookupJson = JsonSerializer.Serialize(new LookupDto {
|
||||||
|
DataSourceType = UiLookupDataSourceTypeEnum.Query,
|
||||||
|
DisplayExpr = "Name",
|
||||||
|
ValueExpr = "Key",
|
||||||
|
LookupQuery = LookupQueryValues.LanguageKeyValues
|
||||||
|
}),
|
||||||
ColumnCustomizationJson = DefaultColumnCustomizationJson,
|
ColumnCustomizationJson = DefaultColumnCustomizationJson,
|
||||||
PermissionJson = DefaultFieldPermissionJson(listForm.Name),
|
PermissionJson = DefaultFieldPermissionJson(listForm.Name),
|
||||||
PivotSettingsJson = DefaultPivotSettingsJson
|
PivotSettingsJson = DefaultPivotSettingsJson
|
||||||
|
|
@ -1176,7 +1182,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region AiBot
|
#region AiBot
|
||||||
listFormName = AppCodes.AiBot;
|
listFormName = AppCodes.Definitions.AiBot;
|
||||||
if (!await _listFormRepository.AnyAsync(a => a.ListFormCode == listFormName))
|
if (!await _listFormRepository.AnyAsync(a => a.ListFormCode == listFormName))
|
||||||
{
|
{
|
||||||
var listForm = await _listFormRepository.InsertAsync(
|
var listForm = await _listFormRepository.InsertAsync(
|
||||||
|
|
@ -1200,7 +1206,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
|
||||||
SelectCommandType = SelectCommandTypeEnum.Table,
|
SelectCommandType = SelectCommandTypeEnum.Table,
|
||||||
SelectCommand = TableNameResolver.GetFullTableName(nameof(TableNameEnum.AiBot)),
|
SelectCommand = TableNameResolver.GetFullTableName(nameof(TableNameEnum.AiBot)),
|
||||||
KeyFieldName = "Id",
|
KeyFieldName = "Id",
|
||||||
KeyFieldDbSourceType = DbType.Int32,
|
KeyFieldDbSourceType = DbType.Guid,
|
||||||
SortMode = GridOptions.SortModeSingle,
|
SortMode = GridOptions.SortModeSingle,
|
||||||
FilterRowJson = DefaultFilterRowJson,
|
FilterRowJson = DefaultFilterRowJson,
|
||||||
HeaderFilterJson = DefaultHeaderFilterJson,
|
HeaderFilterJson = DefaultHeaderFilterJson,
|
||||||
|
|
@ -1212,7 +1218,10 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
|
||||||
PagerOptionJson = DefaultPagerOptionJson,
|
PagerOptionJson = DefaultPagerOptionJson,
|
||||||
DeleteCommand = $"DELETE FROM \"{TableNameResolver.GetFullTableName(nameof(TableNameEnum.AiBot))}\" WHERE \"Id\"=@Id",
|
DeleteCommand = $"DELETE FROM \"{TableNameResolver.GetFullTableName(nameof(TableNameEnum.AiBot))}\" WHERE \"Id\"=@Id",
|
||||||
DeleteFieldsDefaultValueJson = JsonSerializer.Serialize(new FieldsDefaultValue[] {
|
DeleteFieldsDefaultValueJson = JsonSerializer.Serialize(new FieldsDefaultValue[] {
|
||||||
new() { FieldName = "Id", FieldDbType = DbType.Int32, Value = "@ID", CustomValueType = FieldCustomValueTypeEnum.CustomKey }
|
new() { FieldName = "Id", FieldDbType = DbType.Guid, Value = "@ID", CustomValueType = FieldCustomValueTypeEnum.CustomKey }
|
||||||
|
}),
|
||||||
|
InsertFieldsDefaultValueJson = JsonSerializer.Serialize(new FieldsDefaultValue[] {
|
||||||
|
new() { FieldName = "Id", FieldDbType = DbType.Guid, Value = "@NEWID", CustomValueType = FieldCustomValueTypeEnum.CustomKey }
|
||||||
}),
|
}),
|
||||||
EditingOptionJson = DefaultEditingOptionJson(listFormName, 500, 450, true, true, true, true, false),
|
EditingOptionJson = DefaultEditingOptionJson(listFormName, 500, 450, true, true, true, true, false),
|
||||||
EditingFormJson = JsonSerializer.Serialize(new List<EditingFormDto>()
|
EditingFormJson = JsonSerializer.Serialize(new List<EditingFormDto>()
|
||||||
|
|
@ -1472,6 +1481,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
|
||||||
PermissionJson = DefaultPermissionJson(listFormName),
|
PermissionJson = DefaultPermissionJson(listFormName),
|
||||||
DeleteCommand = DefaultDeleteCommand(nameof(TableNameEnum.Currency)),
|
DeleteCommand = DefaultDeleteCommand(nameof(TableNameEnum.Currency)),
|
||||||
DeleteFieldsDefaultValueJson = DefaultDeleteFieldsDefaultValueJson(),
|
DeleteFieldsDefaultValueJson = DefaultDeleteFieldsDefaultValueJson(),
|
||||||
|
InsertFieldsDefaultValueJson = DefaultInsertFieldsDefaultValueJson(DbType.String, "Name"),
|
||||||
PagerOptionJson = DefaultPagerOptionJson,
|
PagerOptionJson = DefaultPagerOptionJson,
|
||||||
EditingOptionJson = DefaultEditingOptionJson(listFormName, 500, 350, true, true, true, true, false),
|
EditingOptionJson = DefaultEditingOptionJson(listFormName, 500, 350, true, true, true, true, false),
|
||||||
EditingFormJson = JsonSerializer.Serialize(new List<EditingFormDto>
|
EditingFormJson = JsonSerializer.Serialize(new List<EditingFormDto>
|
||||||
|
|
@ -1479,14 +1489,13 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
|
||||||
new() {
|
new() {
|
||||||
Order = 1, ColCount = 1, ColSpan = 1, ItemType = "group", Items =
|
Order = 1, ColCount = 1, ColSpan = 1, ItemType = "group", Items =
|
||||||
[
|
[
|
||||||
|
new EditingFormItemDto { Order = 1, DataField = "Name", ColSpan = 1, IsRequired = true, EditorType2 = EditorTypes.dxTextBox },
|
||||||
new EditingFormItemDto { Order = 2, DataField = "Symbol", ColSpan = 1, IsRequired = true, EditorType2 = EditorTypes.dxTextBox },
|
new EditingFormItemDto { Order = 2, DataField = "Symbol", ColSpan = 1, IsRequired = true, EditorType2 = EditorTypes.dxTextBox },
|
||||||
new EditingFormItemDto { Order = 3, DataField = "Name", ColSpan = 1, IsRequired = true, EditorType2 = EditorTypes.dxTextBox },
|
new EditingFormItemDto { Order = 3, DataField = "Rate", ColSpan = 1, IsRequired = true, EditorType2 = EditorTypes.dxNumberBox },
|
||||||
new EditingFormItemDto { Order = 4, DataField = "Rate", ColSpan = 1, IsRequired = true, EditorType2 = EditorTypes.dxNumberBox },
|
new EditingFormItemDto { Order = 4, DataField = "IsActive", ColSpan = 1, EditorType2 = EditorTypes.dxCheckBox }
|
||||||
new EditingFormItemDto { Order = 5, DataField = "IsActive", ColSpan = 1, EditorType2 = EditorTypes.dxCheckBox }
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
InsertFieldsDefaultValueJson = DefaultInsertFieldsDefaultValueJson(),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
#region Currency Fields
|
#region Currency Fields
|
||||||
|
|
@ -1617,6 +1626,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
|
||||||
PermissionJson = DefaultPermissionJson(listFormName),
|
PermissionJson = DefaultPermissionJson(listFormName),
|
||||||
DeleteCommand = DefaultDeleteCommand(nameof(TableNameEnum.CountryGroup)),
|
DeleteCommand = DefaultDeleteCommand(nameof(TableNameEnum.CountryGroup)),
|
||||||
DeleteFieldsDefaultValueJson = DefaultDeleteFieldsDefaultValueJson(),
|
DeleteFieldsDefaultValueJson = DefaultDeleteFieldsDefaultValueJson(),
|
||||||
|
InsertFieldsDefaultValueJson = DefaultInsertFieldsDefaultValueJson(DbType.String, "Name"),
|
||||||
PagerOptionJson = DefaultPagerOptionJson,
|
PagerOptionJson = DefaultPagerOptionJson,
|
||||||
EditingOptionJson = DefaultEditingOptionJson(listFormName, 400, 200, true, true, true, true, false),
|
EditingOptionJson = DefaultEditingOptionJson(listFormName, 400, 200, true, true, true, true, false),
|
||||||
EditingFormJson = JsonSerializer.Serialize(new List<EditingFormDto>
|
EditingFormJson = JsonSerializer.Serialize(new List<EditingFormDto>
|
||||||
|
|
@ -1628,7 +1638,6 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
InsertFieldsDefaultValueJson = DefaultInsertFieldsDefaultValueJson(),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
#region CountryGroup Fields
|
#region CountryGroup Fields
|
||||||
|
|
@ -1707,6 +1716,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
|
||||||
PermissionJson = DefaultPermissionJson(listFormName),
|
PermissionJson = DefaultPermissionJson(listFormName),
|
||||||
DeleteCommand = DefaultDeleteCommand(nameof(TableNameEnum.Country)),
|
DeleteCommand = DefaultDeleteCommand(nameof(TableNameEnum.Country)),
|
||||||
DeleteFieldsDefaultValueJson = DefaultDeleteFieldsDefaultValueJson(),
|
DeleteFieldsDefaultValueJson = DefaultDeleteFieldsDefaultValueJson(),
|
||||||
|
InsertFieldsDefaultValueJson = DefaultInsertFieldsDefaultValueJson(DbType.String, "Name"),
|
||||||
PagerOptionJson = DefaultPagerOptionJson,
|
PagerOptionJson = DefaultPagerOptionJson,
|
||||||
EditingOptionJson = DefaultEditingOptionJson(listFormName, 600, 550, true, true, true, true, false),
|
EditingOptionJson = DefaultEditingOptionJson(listFormName, 600, 550, true, true, true, true, false),
|
||||||
EditingFormJson = JsonSerializer.Serialize(new List<EditingFormDto>
|
EditingFormJson = JsonSerializer.Serialize(new List<EditingFormDto>
|
||||||
|
|
@ -1724,7 +1734,6 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
InsertFieldsDefaultValueJson = DefaultInsertFieldsDefaultValueJson(),
|
|
||||||
FormFieldsDefaultValueJson = JsonSerializer.Serialize(new FieldsDefaultValue[] {
|
FormFieldsDefaultValueJson = JsonSerializer.Serialize(new FieldsDefaultValue[] {
|
||||||
new() { FieldName = "Currency", FieldDbType = DbType.String, Value = "TRY", CustomValueType = FieldCustomValueTypeEnum.Value }
|
new() { FieldName = "Currency", FieldDbType = DbType.String, Value = "TRY", CustomValueType = FieldCustomValueTypeEnum.Value }
|
||||||
})
|
})
|
||||||
|
|
@ -2204,6 +2213,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
|
||||||
PermissionJson = DefaultPermissionJson(listFormName),
|
PermissionJson = DefaultPermissionJson(listFormName),
|
||||||
DeleteCommand = DefaultDeleteCommand(nameof(TableNameEnum.UomCategory)),
|
DeleteCommand = DefaultDeleteCommand(nameof(TableNameEnum.UomCategory)),
|
||||||
DeleteFieldsDefaultValueJson = DefaultDeleteFieldsDefaultValueJson(),
|
DeleteFieldsDefaultValueJson = DefaultDeleteFieldsDefaultValueJson(),
|
||||||
|
InsertFieldsDefaultValueJson = DefaultInsertFieldsDefaultValueJson(DbType.String, "Name"),
|
||||||
PagerOptionJson = DefaultPagerOptionJson,
|
PagerOptionJson = DefaultPagerOptionJson,
|
||||||
EditingOptionJson = DefaultEditingOptionJson(listFormName, 400, 200, true, true, true, false, false, true),
|
EditingOptionJson = DefaultEditingOptionJson(listFormName, 400, 200, true, true, true, false, false, true),
|
||||||
EditingFormJson = JsonSerializer.Serialize(new List<EditingFormDto>
|
EditingFormJson = JsonSerializer.Serialize(new List<EditingFormDto>
|
||||||
|
|
@ -2215,7 +2225,6 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
InsertFieldsDefaultValueJson = DefaultInsertFieldsDefaultValueJson(),
|
|
||||||
SubFormsJson = JsonSerializer.Serialize(new List<dynamic>() {
|
SubFormsJson = JsonSerializer.Serialize(new List<dynamic>() {
|
||||||
new {
|
new {
|
||||||
TabType = ListFormTabTypeEnum.List,
|
TabType = ListFormTabTypeEnum.List,
|
||||||
|
|
@ -2310,6 +2319,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
|
||||||
PermissionJson = DefaultPermissionJson(listFormName),
|
PermissionJson = DefaultPermissionJson(listFormName),
|
||||||
DeleteCommand = DefaultDeleteCommand(nameof(TableNameEnum.Uom)),
|
DeleteCommand = DefaultDeleteCommand(nameof(TableNameEnum.Uom)),
|
||||||
DeleteFieldsDefaultValueJson = DefaultDeleteFieldsDefaultValueJson(),
|
DeleteFieldsDefaultValueJson = DefaultDeleteFieldsDefaultValueJson(),
|
||||||
|
InsertFieldsDefaultValueJson = DefaultInsertFieldsDefaultValueJson(DbType.String, "Name"),
|
||||||
PagerOptionJson = DefaultPagerOptionJson,
|
PagerOptionJson = DefaultPagerOptionJson,
|
||||||
EditingOptionJson = DefaultEditingOptionJson(listFormName, 600, 300, true, true, true, false, false),
|
EditingOptionJson = DefaultEditingOptionJson(listFormName, 600, 300, true, true, true, false, false),
|
||||||
EditingFormJson = JsonSerializer.Serialize(new List<EditingFormDto>() {
|
EditingFormJson = JsonSerializer.Serialize(new List<EditingFormDto>() {
|
||||||
|
|
@ -2322,7 +2332,6 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
|
||||||
new EditingFormItemDto { Order = 5, DataField = "IsActive", ColSpan = 1, EditorType2=EditorTypes.dxCheckBox },
|
new EditingFormItemDto { Order = 5, DataField = "IsActive", ColSpan = 1, EditorType2=EditorTypes.dxCheckBox },
|
||||||
]}
|
]}
|
||||||
}),
|
}),
|
||||||
InsertFieldsDefaultValueJson = DefaultInsertFieldsDefaultValueJson(),
|
|
||||||
FormFieldsDefaultValueJson = JsonSerializer.Serialize(new FieldsDefaultValue[] {
|
FormFieldsDefaultValueJson = JsonSerializer.Serialize(new FieldsDefaultValue[] {
|
||||||
new() { FieldName = "IsActive", FieldDbType = DbType.Boolean, Value = "true", CustomValueType = FieldCustomValueTypeEnum.Value }
|
new() { FieldName = "IsActive", FieldDbType = DbType.Boolean, Value = "true", CustomValueType = FieldCustomValueTypeEnum.Value }
|
||||||
}),
|
}),
|
||||||
|
|
@ -2514,7 +2523,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
|
||||||
DeleteCommand = DefaultDeleteCommand(nameof(TableNameEnum.SkillType)),
|
DeleteCommand = DefaultDeleteCommand(nameof(TableNameEnum.SkillType)),
|
||||||
DeleteFieldsDefaultValueJson = DefaultDeleteFieldsDefaultValueJson(DbType.String),
|
DeleteFieldsDefaultValueJson = DefaultDeleteFieldsDefaultValueJson(DbType.String),
|
||||||
PagerOptionJson = DefaultPagerOptionJson,
|
PagerOptionJson = DefaultPagerOptionJson,
|
||||||
InsertFieldsDefaultValueJson = DefaultInsertFieldsDefaultValueJson(DbType.String),
|
InsertFieldsDefaultValueJson = DefaultInsertFieldsDefaultValueJson(DbType.String, "Name"),
|
||||||
EditingOptionJson = DefaultEditingOptionJson(listFormName, 400, 200, true, true, true, true, false, true),
|
EditingOptionJson = DefaultEditingOptionJson(listFormName, 400, 200, true, true, true, true, false, true),
|
||||||
EditingFormJson = JsonSerializer.Serialize(new List<EditingFormDto>
|
EditingFormJson = JsonSerializer.Serialize(new List<EditingFormDto>
|
||||||
{
|
{
|
||||||
|
|
@ -2632,7 +2641,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
|
||||||
DeleteCommand = DefaultDeleteCommand(nameof(TableNameEnum.SkillLevel)),
|
DeleteCommand = DefaultDeleteCommand(nameof(TableNameEnum.SkillLevel)),
|
||||||
DeleteFieldsDefaultValueJson = DefaultDeleteFieldsDefaultValueJson(DbType.String),
|
DeleteFieldsDefaultValueJson = DefaultDeleteFieldsDefaultValueJson(DbType.String),
|
||||||
PagerOptionJson = DefaultPagerOptionJson,
|
PagerOptionJson = DefaultPagerOptionJson,
|
||||||
InsertFieldsDefaultValueJson = DefaultInsertFieldsDefaultValueJson(DbType.String),
|
InsertFieldsDefaultValueJson = DefaultInsertFieldsDefaultValueJson(DbType.String, "Name"),
|
||||||
EditingOptionJson = DefaultEditingOptionJson(listFormName, 600, 300, true, true, true, true, false),
|
EditingOptionJson = DefaultEditingOptionJson(listFormName, 600, 300, true, true, true, true, false),
|
||||||
EditingFormJson = JsonSerializer.Serialize(new List<EditingFormDto>() {
|
EditingFormJson = JsonSerializer.Serialize(new List<EditingFormDto>() {
|
||||||
new() { Order=1, ColCount=1, ColSpan=1, ItemType="group", Items=
|
new() { Order=1, ColCount=1, ColSpan=1, ItemType="group", Items=
|
||||||
|
|
@ -2778,7 +2787,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
|
||||||
DeleteCommand = DefaultDeleteCommand(nameof(TableNameEnum.Skill)),
|
DeleteCommand = DefaultDeleteCommand(nameof(TableNameEnum.Skill)),
|
||||||
DeleteFieldsDefaultValueJson = DefaultDeleteFieldsDefaultValueJson(DbType.String),
|
DeleteFieldsDefaultValueJson = DefaultDeleteFieldsDefaultValueJson(DbType.String),
|
||||||
PagerOptionJson = DefaultPagerOptionJson,
|
PagerOptionJson = DefaultPagerOptionJson,
|
||||||
InsertFieldsDefaultValueJson = DefaultInsertFieldsDefaultValueJson(DbType.String),
|
InsertFieldsDefaultValueJson = DefaultInsertFieldsDefaultValueJson(DbType.String, "Name"),
|
||||||
EditingOptionJson = DefaultEditingOptionJson(AppCodes.Definitions.SkillLevel, 600, 300, true, true, true, true, false),
|
EditingOptionJson = DefaultEditingOptionJson(AppCodes.Definitions.SkillLevel, 600, 300, true, true, true, true, false),
|
||||||
EditingFormJson = JsonSerializer.Serialize(new List<EditingFormDto>() {
|
EditingFormJson = JsonSerializer.Serialize(new List<EditingFormDto>() {
|
||||||
new() { Order=1, ColCount=1, ColSpan=1, ItemType="group", Items=[
|
new() { Order=1, ColCount=1, ColSpan=1, ItemType="group", Items=[
|
||||||
|
|
@ -2848,7 +2857,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region SettingDefinition
|
#region SettingDefinition
|
||||||
listFormName = AppCodes.Settings.SettingDefinitions;
|
listFormName = AppCodes.SettingDefinitions;
|
||||||
if (!await _listFormRepository.AnyAsync(a => a.ListFormCode == listFormName))
|
if (!await _listFormRepository.AnyAsync(a => a.ListFormCode == listFormName))
|
||||||
{
|
{
|
||||||
var listForm = await _listFormRepository.InsertAsync(
|
var listForm = await _listFormRepository.InsertAsync(
|
||||||
|
|
@ -2937,7 +2946,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
|
||||||
CultureName = LanguageCodes.En,
|
CultureName = LanguageCodes.En,
|
||||||
SourceDbType = DbType.String,
|
SourceDbType = DbType.String,
|
||||||
FieldName = "Code",
|
FieldName = "Code",
|
||||||
CaptionName = "App.Listform.ListformField.Code",
|
CaptionName = "App.Platform.Code",
|
||||||
Width = 400,
|
Width = 400,
|
||||||
ListOrderNo = 4,
|
ListOrderNo = 4,
|
||||||
Visible = true,
|
Visible = true,
|
||||||
|
|
@ -3575,7 +3584,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Data Source
|
#region Data Source
|
||||||
listFormName = AppCodes.Listforms.DataSource;
|
listFormName = AppCodes.DataSource;
|
||||||
if (!await _listFormRepository.AnyAsync(a => a.ListFormCode == listFormName))
|
if (!await _listFormRepository.AnyAsync(a => a.ListFormCode == listFormName))
|
||||||
{
|
{
|
||||||
var listForm = await _listFormRepository.InsertAsync(
|
var listForm = await _listFormRepository.InsertAsync(
|
||||||
|
|
@ -3649,7 +3658,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
|
||||||
CultureName = LanguageCodes.En,
|
CultureName = LanguageCodes.En,
|
||||||
SourceDbType = DbType.String,
|
SourceDbType = DbType.String,
|
||||||
FieldName = "Code",
|
FieldName = "Code",
|
||||||
CaptionName = "App.Listform.ListformField.Code",
|
CaptionName = "App.Platform.Code",
|
||||||
Width = 300,
|
Width = 300,
|
||||||
ListOrderNo = 2,
|
ListOrderNo = 2,
|
||||||
Visible = true,
|
Visible = true,
|
||||||
|
|
@ -4881,7 +4890,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Route
|
#region Route
|
||||||
listFormName = AppCodes.Routes;
|
listFormName = AppCodes.Menus.Routes;
|
||||||
if (!await _listFormRepository.AnyAsync(a => a.ListFormCode == listFormName))
|
if (!await _listFormRepository.AnyAsync(a => a.ListFormCode == listFormName))
|
||||||
{
|
{
|
||||||
var listForm = await _listFormRepository.InsertAsync(
|
var listForm = await _listFormRepository.InsertAsync(
|
||||||
|
|
@ -5109,7 +5118,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
|
||||||
}),
|
}),
|
||||||
DeleteCommand = DefaultDeleteCommand(nameof(TableNameEnum.MenuGroup)),
|
DeleteCommand = DefaultDeleteCommand(nameof(TableNameEnum.MenuGroup)),
|
||||||
DeleteFieldsDefaultValueJson = DefaultDeleteFieldsDefaultValueJson(),
|
DeleteFieldsDefaultValueJson = DefaultDeleteFieldsDefaultValueJson(),
|
||||||
InsertFieldsDefaultValueJson = DefaultInsertFieldsDefaultValueJson(),
|
InsertFieldsDefaultValueJson = DefaultInsertFieldsDefaultValueJson(DbType.String, "Name"),
|
||||||
});
|
});
|
||||||
|
|
||||||
#region MenuGroup Fields
|
#region MenuGroup Fields
|
||||||
|
|
@ -5211,7 +5220,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
|
||||||
new EditingFormItemDto { Order = 12, DataField = "ShortName", ColSpan = 1, EditorType2=EditorTypes.dxTextBox },
|
new EditingFormItemDto { Order = 12, DataField = "ShortName", ColSpan = 1, EditorType2=EditorTypes.dxTextBox },
|
||||||
]}
|
]}
|
||||||
}),
|
}),
|
||||||
InsertFieldsDefaultValueJson = DefaultInsertFieldsDefaultValueJson(),
|
InsertFieldsDefaultValueJson = DefaultInsertFieldsDefaultValueJson(DbType.String, "Code"),
|
||||||
FormFieldsDefaultValueJson = JsonSerializer.Serialize(new FieldsDefaultValue[] {
|
FormFieldsDefaultValueJson = JsonSerializer.Serialize(new FieldsDefaultValue[] {
|
||||||
new() { FieldName = "IsDisabled", FieldDbType = DbType.Boolean, Value = "false", CustomValueType = FieldCustomValueTypeEnum.Value }
|
new() { FieldName = "IsDisabled", FieldDbType = DbType.Boolean, Value = "false", CustomValueType = FieldCustomValueTypeEnum.Value }
|
||||||
})
|
})
|
||||||
|
|
@ -5242,7 +5251,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
|
||||||
CultureName = LanguageCodes.En,
|
CultureName = LanguageCodes.En,
|
||||||
SourceDbType = DbType.String,
|
SourceDbType = DbType.String,
|
||||||
FieldName = "Code",
|
FieldName = "Code",
|
||||||
CaptionName = "App.Listform.ListformField.Code",
|
CaptionName = "App.Platform.Code",
|
||||||
Width = 300,
|
Width = 300,
|
||||||
ListOrderNo = 2,
|
ListOrderNo = 2,
|
||||||
Visible = true,
|
Visible = true,
|
||||||
|
|
@ -5986,7 +5995,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
|
||||||
}),
|
}),
|
||||||
DeleteCommand = DefaultDeleteCommand(nameof(TableNameEnum.PaymentMethod)),
|
DeleteCommand = DefaultDeleteCommand(nameof(TableNameEnum.PaymentMethod)),
|
||||||
DeleteFieldsDefaultValueJson = DefaultDeleteFieldsDefaultValueJson(),
|
DeleteFieldsDefaultValueJson = DefaultDeleteFieldsDefaultValueJson(),
|
||||||
InsertFieldsDefaultValueJson = DefaultInsertFieldsDefaultValueJson(),
|
InsertFieldsDefaultValueJson = DefaultInsertFieldsDefaultValueJson(DbType.String, "Name"),
|
||||||
FormFieldsDefaultValueJson = JsonSerializer.Serialize(new FieldsDefaultValue[] {
|
FormFieldsDefaultValueJson = JsonSerializer.Serialize(new FieldsDefaultValue[] {
|
||||||
new() { FieldName = "Commission", FieldDbType = DbType.Decimal, Value = "0", CustomValueType = FieldCustomValueTypeEnum.Value }
|
new() { FieldName = "Commission", FieldDbType = DbType.Decimal, Value = "0", CustomValueType = FieldCustomValueTypeEnum.Value }
|
||||||
}),
|
}),
|
||||||
|
|
@ -6499,7 +6508,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
|
||||||
CultureName = LanguageCodes.En,
|
CultureName = LanguageCodes.En,
|
||||||
SourceDbType = DbType.String,
|
SourceDbType = DbType.String,
|
||||||
FieldName = "PhoneNumber",
|
FieldName = "PhoneNumber",
|
||||||
CaptionName = "App.Listform.ListformField.PhoneNumber",
|
CaptionName = "Abp.Identity.User.UserInformation.PhoneNumber",
|
||||||
Width = 100,
|
Width = 100,
|
||||||
ListOrderNo = 11,
|
ListOrderNo = 11,
|
||||||
Visible = true,
|
Visible = true,
|
||||||
|
|
@ -7366,7 +7375,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
|
||||||
CultureName = LanguageCodes.En,
|
CultureName = LanguageCodes.En,
|
||||||
SourceDbType = DbType.String,
|
SourceDbType = DbType.String,
|
||||||
FieldName = "Email",
|
FieldName = "Email",
|
||||||
CaptionName = "App.Listform.ListformField.Email",
|
CaptionName = "Abp.Account.EmailAddress",
|
||||||
Width = 250,
|
Width = 250,
|
||||||
ListOrderNo = 4,
|
ListOrderNo = 4,
|
||||||
Visible = true,
|
Visible = true,
|
||||||
|
|
@ -7384,7 +7393,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
|
||||||
CultureName = LanguageCodes.En,
|
CultureName = LanguageCodes.En,
|
||||||
SourceDbType = DbType.String,
|
SourceDbType = DbType.String,
|
||||||
FieldName = "PhoneNumber",
|
FieldName = "PhoneNumber",
|
||||||
CaptionName = "App.Listform.ListformField.PhoneNumber",
|
CaptionName = "Abp.Identity.User.UserInformation.PhoneNumber",
|
||||||
Width = 100,
|
Width = 100,
|
||||||
ListOrderNo = 5,
|
ListOrderNo = 5,
|
||||||
Visible = true,
|
Visible = true,
|
||||||
|
|
|
||||||
|
|
@ -21,13 +21,6 @@
|
||||||
"routeType": "public",
|
"routeType": "public",
|
||||||
"authority": []
|
"authority": []
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"key": "about",
|
|
||||||
"path": "/about",
|
|
||||||
"componentPath": "@/views/public/About",
|
|
||||||
"routeType": "public",
|
|
||||||
"authority": []
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"key": "products",
|
"key": "products",
|
||||||
"path": "/products",
|
"path": "/products",
|
||||||
|
|
@ -187,7 +180,7 @@
|
||||||
"path": "/admin/ai",
|
"path": "/admin/ai",
|
||||||
"componentPath": "@/views/ai/Assistant",
|
"componentPath": "@/views/ai/Assistant",
|
||||||
"routeType": "protected",
|
"routeType": "protected",
|
||||||
"authority": ["App.AiBot.Asistant"]
|
"authority": ["App.Definitions.AiBot.Asistant"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"key": "admin.profile.general",
|
"key": "admin.profile.general",
|
||||||
|
|
@ -452,22 +445,22 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ParentCode": "App.Saas.Definitions",
|
"ParentCode": "App.Saas.Definitions",
|
||||||
"Code": "App.AiBot",
|
"Code": "App.Definitions.AiBot",
|
||||||
"DisplayName": "App.AiBot",
|
"DisplayName": "App.Definitions.AiBot",
|
||||||
"Order": 1,
|
"Order": 1,
|
||||||
"Url": "/admin/list/App.AiBot",
|
"Url": "/admin/list/App.Definitions.AiBot",
|
||||||
"Icon": "FcMindMap",
|
"Icon": "FcMindMap",
|
||||||
"RequiredPermissionName": "App.AiBot",
|
"RequiredPermissionName": "App.Definitions.AiBot",
|
||||||
"IsDisabled": false
|
"IsDisabled": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ParentCode": "App.Saas.Definitions",
|
"ParentCode": "App.Saas.Definitions",
|
||||||
"Code": "App.Settings.GlobalSearch",
|
"Code": "App.Definitions.GlobalSearch",
|
||||||
"DisplayName": "App.Settings.GlobalSearch",
|
"DisplayName": "App.Definitions.GlobalSearch",
|
||||||
"Order": 2,
|
"Order": 2,
|
||||||
"Url": "/admin/list/App.Settings.GlobalSearch",
|
"Url": "/admin/list/App.Definitions.GlobalSearch",
|
||||||
"Icon": "FcSearch",
|
"Icon": "FcSearch",
|
||||||
"RequiredPermissionName": "App.Settings.GlobalSearch",
|
"RequiredPermissionName": "App.Definitions.GlobalSearch",
|
||||||
"IsDisabled": false
|
"IsDisabled": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -552,12 +545,12 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ParentCode": "App.Saas",
|
"ParentCode": "App.Saas",
|
||||||
"Code": "App.Settings.SettingDefinitions",
|
"Code": "App.SettingDefinitions",
|
||||||
"DisplayName": "App.Settings.SettingDefinitions",
|
"DisplayName": "App.SettingDefinitions",
|
||||||
"Order": 5,
|
"Order": 5,
|
||||||
"Url": "/admin/list/App.Settings.SettingDefinitions",
|
"Url": "/admin/list/App.SettingDefinitions",
|
||||||
"Icon": "FcSupport",
|
"Icon": "FcSupport",
|
||||||
"RequiredPermissionName": "App.Settings.SettingDefinitions",
|
"RequiredPermissionName": "App.SettingDefinitions",
|
||||||
"IsDisabled": false
|
"IsDisabled": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -592,12 +585,12 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ParentCode": "App.Saas",
|
"ParentCode": "App.Saas",
|
||||||
"Code": "App.Listforms.DataSource",
|
"Code": "App.DataSource",
|
||||||
"DisplayName": "App.Listforms.DataSource",
|
"DisplayName": "App.DataSource",
|
||||||
"Order": 7,
|
"Order": 7,
|
||||||
"Url": "/admin/list/App.Listforms.DataSource",
|
"Url": "/admin/list/App.DataSource",
|
||||||
"Icon": "FcAcceptDatabase",
|
"Icon": "FcAcceptDatabase",
|
||||||
"RequiredPermissionName": "App.Listforms.DataSource",
|
"RequiredPermissionName": "App.DataSource",
|
||||||
"IsDisabled": false
|
"IsDisabled": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -784,12 +777,12 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ParentCode": "App.Menus",
|
"ParentCode": "App.Menus",
|
||||||
"Code": "App.Routes",
|
"Code": "App.Menus.Routes",
|
||||||
"DisplayName": "App.Routes",
|
"DisplayName": "App.Menus.Routes",
|
||||||
"Order": 1,
|
"Order": 1,
|
||||||
"Url": "/admin/list/App.Routes",
|
"Url": "/admin/list/App.Menus.Routes",
|
||||||
"Icon": "FaSynagogue",
|
"Icon": "FaSynagogue",
|
||||||
"RequiredPermissionName": "App.Routes",
|
"RequiredPermissionName": "App.Menus.Routes",
|
||||||
"IsDisabled": false
|
"IsDisabled": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -822,16 +815,6 @@
|
||||||
"RequiredPermissionName": "App.Menus.Manager",
|
"RequiredPermissionName": "App.Menus.Manager",
|
||||||
"IsDisabled": false
|
"IsDisabled": false
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"ParentCode": "App.Saas",
|
|
||||||
"Code": "App.Files",
|
|
||||||
"DisplayName": "App.Files",
|
|
||||||
"Order": 14,
|
|
||||||
"Url": "/admin/files",
|
|
||||||
"Icon": "FcFolder",
|
|
||||||
"RequiredPermissionName": "App.Files",
|
|
||||||
"IsDisabled": false
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"ParentCode": "App.Saas",
|
"ParentCode": "App.Saas",
|
||||||
"Code": "App.DeveloperKit",
|
"Code": "App.DeveloperKit",
|
||||||
|
|
@ -955,20 +938,40 @@
|
||||||
"IsDisabled": false
|
"IsDisabled": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ParentCode": "App.Administration.Definitions",
|
"ParentCode": "App.Administration",
|
||||||
"Code": "App.Definitions.WorkHour",
|
"Code": "App.Administration.Restrictions",
|
||||||
"DisplayName": "App.Definitions.WorkHour",
|
"DisplayName": "App.Restrictions",
|
||||||
"Order": 2,
|
"Order": 3,
|
||||||
"Url": "/admin/list/App.Definitions.WorkHour",
|
"Url": null,
|
||||||
|
"Icon": "FaLock",
|
||||||
|
"RequiredPermissionName": null,
|
||||||
|
"IsDisabled": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ParentCode": "App.Administration.Restrictions",
|
||||||
|
"Code": "App.Restrictions.WorkHour",
|
||||||
|
"DisplayName": "App.Restrictions.WorkHour",
|
||||||
|
"Order": 1,
|
||||||
|
"Url": "/admin/list/App.Restrictions.WorkHour",
|
||||||
"Icon": "FcClock",
|
"Icon": "FcClock",
|
||||||
"RequiredPermissionName": "App.Definitions.WorkHour",
|
"RequiredPermissionName": "App.Restrictions.WorkHour",
|
||||||
|
"IsDisabled": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ParentCode": "App.Administration.Restrictions",
|
||||||
|
"Code": "App.Restrictions.IpRestrictions",
|
||||||
|
"DisplayName": "App.Restrictions.IpRestrictions",
|
||||||
|
"Order": 2,
|
||||||
|
"Url": "/admin/list/App.Restrictions.IpRestrictions",
|
||||||
|
"Icon": "FcNfcSign",
|
||||||
|
"RequiredPermissionName": "App.Restrictions.IpRestrictions",
|
||||||
"IsDisabled": false
|
"IsDisabled": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ParentCode": "App.Administration",
|
"ParentCode": "App.Administration",
|
||||||
"Code": "Abp.Identity",
|
"Code": "Abp.Identity",
|
||||||
"DisplayName": "Abp.Identity",
|
"DisplayName": "Abp.Identity",
|
||||||
"Order": 3,
|
"Order": 4,
|
||||||
"Url": null,
|
"Url": null,
|
||||||
"Icon": "FcConferenceCall",
|
"Icon": "FcConferenceCall",
|
||||||
"RequiredPermissionName": null,
|
"RequiredPermissionName": null,
|
||||||
|
|
@ -1036,29 +1039,19 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ParentCode": "Abp.Identity",
|
"ParentCode": "Abp.Identity",
|
||||||
"Code": "App.IpRestrictions",
|
"Code": "App.IdentityManagement.AuditLogs",
|
||||||
"DisplayName": "App.IpRestrictions",
|
"DisplayName": "App.IdentityManagement.AuditLogs",
|
||||||
"Order": 7,
|
"Order": 7,
|
||||||
"Url": "/admin/list/App.IpRestrictions",
|
"Url": "/admin/list/App.IdentityManagement.AuditLogs",
|
||||||
"Icon": "FcNfcSign",
|
|
||||||
"RequiredPermissionName": "App.IpRestrictions",
|
|
||||||
"IsDisabled": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ParentCode": "Abp.Identity",
|
|
||||||
"Code": "App.AuditLogs",
|
|
||||||
"DisplayName": "App.AuditLogs",
|
|
||||||
"Order": 8,
|
|
||||||
"Url": "/admin/list/App.AuditLogs",
|
|
||||||
"Icon": "FcMultipleInputs",
|
"Icon": "FcMultipleInputs",
|
||||||
"RequiredPermissionName": "App.AuditLogs",
|
"RequiredPermissionName": "App.IdentityManagement.AuditLogs",
|
||||||
"IsDisabled": false
|
"IsDisabled": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ParentCode": "App.Administration",
|
"ParentCode": "App.Administration",
|
||||||
"Code": "App.Reports.Management",
|
"Code": "App.Reports.Management",
|
||||||
"DisplayName": "App.Reports.Management",
|
"DisplayName": "App.Reports.Management",
|
||||||
"Order": 4,
|
"Order": 5,
|
||||||
"Url": null,
|
"Url": null,
|
||||||
"Icon": "FcDocument",
|
"Icon": "FcDocument",
|
||||||
"RequiredPermissionName": null,
|
"RequiredPermissionName": null,
|
||||||
|
|
@ -1084,11 +1077,21 @@
|
||||||
"RequiredPermissionName": "App.Reports.ReportTemplates",
|
"RequiredPermissionName": "App.Reports.ReportTemplates",
|
||||||
"IsDisabled": false
|
"IsDisabled": false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"ParentCode": "App.Administration",
|
||||||
|
"Code": "App.Files",
|
||||||
|
"DisplayName": "App.Files",
|
||||||
|
"Order": 6,
|
||||||
|
"Url": "/admin/files",
|
||||||
|
"Icon": "FcFolder",
|
||||||
|
"RequiredPermissionName": "App.Files",
|
||||||
|
"IsDisabled": false
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"ParentCode": "App.Administration",
|
"ParentCode": "App.Administration",
|
||||||
"Code": "App.Forum",
|
"Code": "App.Forum",
|
||||||
"DisplayName": "App.Forum",
|
"DisplayName": "App.Forum",
|
||||||
"Order": 5,
|
"Order": 7,
|
||||||
"Url": "/admin/forum",
|
"Url": "/admin/forum",
|
||||||
"Icon": "FcLink",
|
"Icon": "FcLink",
|
||||||
"RequiredPermissionName": "App.ForumManagement.Publish",
|
"RequiredPermissionName": "App.ForumManagement.Publish",
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -11,6 +11,7 @@ public enum OperationEnum
|
||||||
Delete,
|
Delete,
|
||||||
DeleteBefore,
|
DeleteBefore,
|
||||||
DeleteAfter,
|
DeleteAfter,
|
||||||
Select
|
Select,
|
||||||
|
Duplicate,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -290,42 +290,80 @@ public static class PlatformConsts
|
||||||
|
|
||||||
public static class AppCodes
|
public static class AppCodes
|
||||||
{
|
{
|
||||||
public const string Home = Prefix.App + ".Home";
|
|
||||||
public const string Saas = Prefix.App + ".Saas";
|
public const string Saas = Prefix.App + ".Saas";
|
||||||
|
|
||||||
public const string Branches = Prefix.App + ".Branches";
|
public const string Branches = Prefix.App + ".Branches";
|
||||||
public static class Settings
|
public static class Definitions
|
||||||
{
|
{
|
||||||
public const string Default = Prefix.App + ".Settings";
|
public const string Default = Prefix.App + ".Definitions";
|
||||||
|
|
||||||
|
public const string AiBot = Default + ".AiBot";
|
||||||
public const string GlobalSearch = Default + ".GlobalSearch";
|
public const string GlobalSearch = Default + ".GlobalSearch";
|
||||||
public const string SettingDefinitions = Default + ".SettingDefinitions";
|
public const string ContactTitle = Default + ".ContactTitle";
|
||||||
|
public const string Currency = Default + ".Currency";
|
||||||
|
public const string CountryGroup = Default + ".CountryGroup";
|
||||||
|
public const string Country = Default + ".Country";
|
||||||
|
public const string City = Default + ".City";
|
||||||
|
public const string District = Default + ".District";
|
||||||
|
public const string SkillType = Default + ".SkillType";
|
||||||
|
public const string UomCategory = Default + ".UomCategory";
|
||||||
|
|
||||||
|
public const string Sector = Default + ".Sector";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class Restrictions
|
||||||
|
{
|
||||||
|
public const string Default = Prefix.App + ".Restrictions";
|
||||||
|
|
||||||
|
public const string WorkHour = Default + ".WorkHour";
|
||||||
|
public const string IpRestrictions = Default + ".IpRestrictions";
|
||||||
|
}
|
||||||
|
|
||||||
|
public const string SettingDefinitions = Prefix.App + ".SettingDefinitions";
|
||||||
|
|
||||||
public static class Languages
|
public static class Languages
|
||||||
{
|
{
|
||||||
public const string Default = Prefix.App + ".Languages";
|
public const string Default = Prefix.App + ".Languages";
|
||||||
|
|
||||||
public const string Language = Default + ".Language";
|
public const string Language = Default + ".Language";
|
||||||
public const string LanguageText = Default + ".LanguageText";
|
public const string LanguageText = Default + ".LanguageText";
|
||||||
}
|
}
|
||||||
public const string Menus = Prefix.App + ".Menus";
|
|
||||||
|
public const string DataSource = Prefix.App + ".DataSource";
|
||||||
public static class Listforms
|
public static class Listforms
|
||||||
{
|
{
|
||||||
public const string Default = Prefix.App + ".Listforms";
|
public const string Default = Prefix.App + ".Listforms";
|
||||||
|
|
||||||
public const string Wizard = Default + ".Wizard";
|
public const string Wizard = Default + ".Wizard";
|
||||||
public const string DataSource = Default + ".DataSource";
|
|
||||||
public const string Listform = Default + ".Listform";
|
public const string Listform = Default + ".Listform";
|
||||||
public const string ListformField = Default + ".ListformField";
|
public const string ListformField = Default + ".ListformField";
|
||||||
public const string Chart = Default + ".Chart";
|
public const string Chart = Default + ".Chart";
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class Notifications
|
public static class Notifications
|
||||||
{
|
{
|
||||||
public const string Default = Prefix.App + ".Notifications";
|
public const string Default = Prefix.App + ".Notifications";
|
||||||
|
|
||||||
public const string NotificationRules = Default + ".NotificationRules";
|
public const string NotificationRules = Default + ".NotificationRules";
|
||||||
public const string Notification = Default + ".Notification";
|
public const string Notification = Default + ".Notification";
|
||||||
}
|
}
|
||||||
|
|
||||||
public const string BackgroundWorkers = Prefix.App + ".BackgroundWorkers";
|
public const string BackgroundWorkers = Prefix.App + ".BackgroundWorkers";
|
||||||
|
|
||||||
|
public static class Menus
|
||||||
|
{
|
||||||
|
public const string Default = Prefix.App + ".Menus";
|
||||||
|
|
||||||
|
public const string Routes = Default + ".Routes";
|
||||||
|
public const string MenuGroup = Default + ".MenuGroup";
|
||||||
|
public const string Menu = Default + ".Menu";
|
||||||
|
public const string Manager = Default + ".Manager";
|
||||||
|
}
|
||||||
|
|
||||||
public static class DeveloperKits
|
public static class DeveloperKits
|
||||||
{
|
{
|
||||||
public const string Default = Prefix.App + ".DeveloperKit";
|
public const string Default = Prefix.App + ".DeveloperKit";
|
||||||
|
|
||||||
public const string CustomEndpoints = Default + ".CustomEndpoints";
|
public const string CustomEndpoints = Default + ".CustomEndpoints";
|
||||||
|
|
||||||
public const string Get = CustomEndpoints + ".Get";
|
public const string Get = CustomEndpoints + ".Get";
|
||||||
|
|
@ -346,63 +384,48 @@ public static class PlatformConsts
|
||||||
public const string ViewCode = DynamicService + ".ViewCode";
|
public const string ViewCode = DynamicService + ".ViewCode";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public const string Blog = Prefix.App + ".Blog";
|
|
||||||
public const string Forum = Prefix.App + ".Forum";
|
//Web Site
|
||||||
|
public const string Home = Prefix.App + ".Home";
|
||||||
|
public const string About = Prefix.App + ".About";
|
||||||
|
public const string Services = Prefix.App + ".Services";
|
||||||
|
public static class Orders
|
||||||
|
{
|
||||||
|
public const string Default = Prefix.App + ".Orders";
|
||||||
|
|
||||||
|
public const string Products = Default + ".Products";
|
||||||
|
public const string PaymentMethods = Default + ".PaymentMethods";
|
||||||
|
public const string InstallmentOptions = Default + ".InstallmentOptions";
|
||||||
|
public const string SalesOrders = Default + ".SalesOrders";
|
||||||
|
}
|
||||||
|
public static class BlogManagement
|
||||||
|
{
|
||||||
|
public const string Default = Prefix.App + ".BlogManagement";
|
||||||
|
|
||||||
|
public const string BlogPosts = Default + ".Posts";
|
||||||
|
public const string BlogCategory = Default + ".Category";
|
||||||
|
}
|
||||||
|
public const string Demos = Prefix.App + ".Demos";
|
||||||
|
public const string Contact = Prefix.App + ".Contact";
|
||||||
|
|
||||||
|
//Administration
|
||||||
public const string Administration = Prefix.App + ".Administration";
|
public const string Administration = Prefix.App + ".Administration";
|
||||||
|
|
||||||
public const string Setting = Prefix.App + ".Setting";
|
public const string Setting = Prefix.App + ".Setting";
|
||||||
public static class IdentityManagement
|
public static class IdentityManagement
|
||||||
{
|
{
|
||||||
public const string ClaimTypes = Prefix.App + ".ClaimType";
|
public const string Default = Prefix.App + ".IdentityManagement";
|
||||||
public const string IpRestrictions = Prefix.App + ".IpRestrictions";
|
|
||||||
|
public const string ClaimTypes = Default + ".ClaimType";
|
||||||
|
public const string AuditLogs = Default + ".AuditLogs";
|
||||||
}
|
}
|
||||||
public const string AuditLogs = Prefix.App + ".AuditLogs";
|
|
||||||
public static class Definitions
|
public static class Reports
|
||||||
{
|
{
|
||||||
public const string ContactTag = Default + ".ContactTag";
|
public const string Default = Prefix.App + ".Reports";
|
||||||
public const string ContactTitle = Default + ".ContactTitle";
|
|
||||||
public const string Currency = Default + ".Currency";
|
public const string Categories = Default + ".Categories";
|
||||||
public const string CountryGroup = Default + ".CountryGroup";
|
public const string ReportTemplates = Default + ".ReportTemplates";
|
||||||
public const string Country = Default + ".Country";
|
|
||||||
public const string City = Default + ".City";
|
|
||||||
public const string District = Default + ".District";
|
|
||||||
public const string Default = Prefix.App + ".Definitions";
|
|
||||||
public const string Sector = Default + ".Sector";
|
|
||||||
public const string SkillType = Default + ".SkillType";
|
|
||||||
public const string UomCategory = Default + ".UomCategory";
|
|
||||||
public const string Bank = Default + ".Bank";
|
|
||||||
public const string Behavior = Default + ".Behavior";
|
|
||||||
public const string Disease = Default + ".Disease";
|
|
||||||
public const string Document = Default + ".Document";
|
|
||||||
public const string EducationStatus = Default + ".EducationStatus";
|
|
||||||
public const string MeetingMethod = Default + ".MeetingMethod";
|
|
||||||
public const string MeetingResult = Default + ".MeetingResult";
|
|
||||||
public const string Program = Default + ".Program";
|
|
||||||
public const string Interesting = Default + ".Interesting";
|
|
||||||
public const string SalesRejectionReason = Default + ".SalesRejectionReason";
|
|
||||||
public const string ClassCancellationReason = Default + ".ClassCancellationReason";
|
|
||||||
public const string Source = Default + ".Source";
|
|
||||||
public const string Vaccine = Default + ".Vaccine";
|
|
||||||
public const string NoteType = Default + ".NoteType";
|
|
||||||
public const string WorkHour = Default + ".WorkHour";
|
|
||||||
public const string Vehicle = Default + ".Vehicle";
|
|
||||||
public const string Schedule = Default + ".Schedule";
|
|
||||||
public const string ScheduleLesson = Default + ".ScheduleLesson";
|
|
||||||
public const string Psychologist = Default + ".Psychologist";
|
|
||||||
public const string Meal = Default + ".Meal";
|
|
||||||
public const string Lawyer = Default + ".Lawyer";
|
|
||||||
public const string LessonPeriod = Default + ".LessonPeriod";
|
|
||||||
public const string RegistrationType = Default + ".RegistrationType";
|
|
||||||
public const string RegistrationMethod = Default + ".RegistrationMethod";
|
|
||||||
public const string ClassType = Default + ".ClassType";
|
|
||||||
public const string Class = Default + ".Class";
|
|
||||||
public const string Level = Default + ".Level";
|
|
||||||
}
|
|
||||||
public static class Hr
|
|
||||||
{
|
|
||||||
public const string Default = Prefix.App + ".Hr";
|
|
||||||
public const string EventType = Default + ".EventType";
|
|
||||||
public const string EventCategory = Default + ".EventCategory";
|
|
||||||
public const string Event = Default + ".Event";
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -318,70 +318,77 @@ public static class SeedConsts
|
||||||
|
|
||||||
public static class AppCodes
|
public static class AppCodes
|
||||||
{
|
{
|
||||||
public const string Home = Prefix.App + ".Home";
|
|
||||||
|
|
||||||
//Saas
|
|
||||||
public const string Saas = Prefix.App + ".Saas";
|
public const string Saas = Prefix.App + ".Saas";
|
||||||
|
|
||||||
public const string Branches = Prefix.App + ".Branches";
|
public const string Branches = Prefix.App + ".Branches";
|
||||||
public static class Settings
|
|
||||||
|
public static class Definitions
|
||||||
{
|
{
|
||||||
public const string Default = Prefix.App + ".Settings";
|
public const string Default = Prefix.App + ".Definitions";
|
||||||
|
|
||||||
|
public const string AiBot = Default + ".AiBot";
|
||||||
public const string GlobalSearch = Default + ".GlobalSearch";
|
public const string GlobalSearch = Default + ".GlobalSearch";
|
||||||
public const string SettingDefinitions = Default + ".SettingDefinitions";
|
public const string ContactTitle = Default + ".ContactTitle";
|
||||||
|
public const string Currency = Default + ".Currency";
|
||||||
|
public const string CountryGroup = Default + ".CountryGroup";
|
||||||
|
public const string Country = Default + ".Country";
|
||||||
|
public const string City = Default + ".City";
|
||||||
|
public const string District = Default + ".District";
|
||||||
|
public const string SkillType = Default + ".SkillType";
|
||||||
|
public const string SkillLevel = Default + ".SkillLevel";
|
||||||
|
public const string Skill = Default + ".Skill";
|
||||||
|
public const string UomCategory = Default + ".UomCategory";
|
||||||
|
public const string Uom = Default + ".Uom";
|
||||||
|
|
||||||
|
public const string Sector = Default + ".Sector";
|
||||||
}
|
}
|
||||||
public const string AiBot = Prefix.App + ".AiBot";
|
|
||||||
|
public static class Restrictions
|
||||||
|
{
|
||||||
|
public const string Default = Prefix.App + ".Restrictions";
|
||||||
|
|
||||||
|
public const string WorkHour = Default + ".WorkHour";
|
||||||
|
public const string IpRestrictions = Default + ".IpRestrictions";
|
||||||
|
}
|
||||||
|
|
||||||
|
public const string SettingDefinitions = Prefix.App + ".SettingDefinitions";
|
||||||
|
|
||||||
public static class Languages
|
public static class Languages
|
||||||
{
|
{
|
||||||
public const string Default = Prefix.App + ".Languages";
|
public const string Default = Prefix.App + ".Languages";
|
||||||
public const string Language = Default + ".Language";
|
public const string Language = Default + ".Language";
|
||||||
public const string LanguageText = Default + ".LanguageText";
|
public const string LanguageText = Default + ".LanguageText";
|
||||||
}
|
}
|
||||||
public const string Routes = Prefix.App + ".Routes";
|
|
||||||
public static class Menus
|
|
||||||
{
|
|
||||||
public const string Default = Prefix.App + ".Menus";
|
|
||||||
|
|
||||||
public const string MenuGroup = Default + ".MenuGroup";
|
public const string DataSource = Prefix.App + ".DataSource";
|
||||||
public const string Menu = Default + ".Menu";
|
|
||||||
public const string Manager = Default + ".Manager";
|
|
||||||
}
|
|
||||||
public static class Listforms
|
public static class Listforms
|
||||||
{
|
{
|
||||||
public const string Default = Prefix.App + ".Listforms";
|
public const string Default = Prefix.App + ".Listforms";
|
||||||
|
|
||||||
public const string DataSource = Default + ".DataSource";
|
|
||||||
public const string Wizard = Default + ".Wizard";
|
public const string Wizard = Default + ".Wizard";
|
||||||
public const string Listform = Default + ".Listform";
|
public const string Listform = Default + ".Listform";
|
||||||
public const string ListformField = Default + ".ListformField";
|
public const string ListformField = Default + ".ListformField";
|
||||||
public const string Chart = Default + ".Chart";
|
public const string Chart = Default + ".Chart";
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class Notifications
|
public static class Notifications
|
||||||
{
|
{
|
||||||
public const string Default = Prefix.App + ".Notifications";
|
public const string Default = Prefix.App + ".Notifications";
|
||||||
|
|
||||||
public const string NotificationRules = Default + ".NotificationRules";
|
public const string NotificationRules = Default + ".NotificationRules";
|
||||||
public const string Notification = Default + ".Notification";
|
public const string Notification = Default + ".Notification";
|
||||||
}
|
}
|
||||||
|
|
||||||
public const string BackgroundWorkers = Prefix.App + ".BackgroundWorkers";
|
public const string BackgroundWorkers = Prefix.App + ".BackgroundWorkers";
|
||||||
public const string Forum = Prefix.App + ".Forum";
|
|
||||||
|
|
||||||
public static class DeveloperKits
|
|
||||||
{
|
|
||||||
public const string Default = Prefix.App + ".DeveloperKit";
|
|
||||||
public const string CustomEndpoints = Default + ".CustomEndpoints";
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class Reports
|
|
||||||
{
|
|
||||||
public const string Default = Prefix.App + ".Reports";
|
|
||||||
public const string Categories = Default + ".Categories";
|
|
||||||
public const string ReportTemplates = Default + ".ReportTemplates";
|
|
||||||
}
|
|
||||||
//Web Site
|
//Web Site
|
||||||
|
public const string Home = Prefix.App + ".Home";
|
||||||
public const string About = Prefix.App + ".About";
|
public const string About = Prefix.App + ".About";
|
||||||
public const string Services = Prefix.App + ".Services";
|
public const string Services = Prefix.App + ".Services";
|
||||||
public static class Orders
|
public static class Orders
|
||||||
{
|
{
|
||||||
public const string Default = Prefix.App + ".Orders";
|
public const string Default = Prefix.App + ".Orders";
|
||||||
|
|
||||||
public const string Products = Default + ".Products";
|
public const string Products = Default + ".Products";
|
||||||
public const string PaymentMethods = Default + ".PaymentMethods";
|
public const string PaymentMethods = Default + ".PaymentMethods";
|
||||||
public const string InstallmentOptions = Default + ".InstallmentOptions";
|
public const string InstallmentOptions = Default + ".InstallmentOptions";
|
||||||
|
|
@ -397,32 +404,43 @@ public static class SeedConsts
|
||||||
public const string Demos = Prefix.App + ".Demos";
|
public const string Demos = Prefix.App + ".Demos";
|
||||||
public const string Contact = Prefix.App + ".Contact";
|
public const string Contact = Prefix.App + ".Contact";
|
||||||
|
|
||||||
|
public static class Menus
|
||||||
|
{
|
||||||
|
public const string Default = Prefix.App + ".Menus";
|
||||||
|
|
||||||
|
public const string Routes = Default + ".Routes";
|
||||||
|
public const string MenuGroup = Default + ".MenuGroup";
|
||||||
|
public const string Menu = Default + ".Menu";
|
||||||
|
public const string Manager = Default + ".Manager";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class DeveloperKits
|
||||||
|
{
|
||||||
|
public const string Default = Prefix.App + ".DeveloperKit";
|
||||||
|
|
||||||
|
public const string CustomEndpoints = Default + ".CustomEndpoints";
|
||||||
|
}
|
||||||
|
|
||||||
|
public const string Forum = Prefix.App + ".Forum";
|
||||||
|
|
||||||
//Administration
|
//Administration
|
||||||
public const string Administration = Prefix.App + ".Administration";
|
public const string Administration = Prefix.App + ".Administration";
|
||||||
|
|
||||||
public const string Setting = Prefix.App + ".Setting";
|
public const string Setting = Prefix.App + ".Setting";
|
||||||
public static class IdentityManagement
|
public static class IdentityManagement
|
||||||
{
|
{
|
||||||
public const string ClaimTypes = Prefix.App + ".ClaimType";
|
public const string Default = Prefix.App + ".IdentityManagement";
|
||||||
public const string IpRestrictions = Prefix.App + ".IpRestrictions";
|
|
||||||
}
|
|
||||||
public const string AuditLogs = Prefix.App + ".AuditLogs";
|
|
||||||
public static class Definitions
|
|
||||||
{
|
|
||||||
public const string Default = Prefix.App + ".Definitions";
|
|
||||||
|
|
||||||
public const string ContactTitle = Default + ".ContactTitle";
|
public const string ClaimTypes = Default + ".ClaimType";
|
||||||
public const string Currency = Default + ".Currency";
|
public const string AuditLogs = Default + ".AuditLogs";
|
||||||
public const string CountryGroup = Default + ".CountryGroup";
|
}
|
||||||
public const string Country = Default + ".Country";
|
|
||||||
public const string City = Default + ".City";
|
public static class Reports
|
||||||
public const string District = Default + ".District";
|
{
|
||||||
public const string Sector = Default + ".Sector";
|
public const string Default = Prefix.App + ".Reports";
|
||||||
public const string SkillType = Default + ".SkillType";
|
|
||||||
public const string SkillLevel = Default + ".SkillLevel";
|
public const string Categories = Default + ".Categories";
|
||||||
public const string Skill = Default + ".Skill";
|
public const string ReportTemplates = Default + ".ReportTemplates";
|
||||||
public const string UomCategory = Default + ".UomCategory";
|
|
||||||
public const string Uom = Default + ".Uom";
|
|
||||||
public const string WorkHour = Default + ".WorkHour";
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ public class ListFormField : FullAuditedEntity<Guid>
|
||||||
public string CultureName { get; set; } // Bu tanım hangi dil için (“tr”, “en”)
|
public string CultureName { get; set; } // Bu tanım hangi dil için (“tr”, “en”)
|
||||||
public string FieldName { get; set; } // Kaynaktaki sutun adi
|
public string FieldName { get; set; } // Kaynaktaki sutun adi
|
||||||
public string CaptionName { get; set; } // Sutun basligi
|
public string CaptionName { get; set; } // Sutun basligi
|
||||||
|
public string PlaceHolder { get; set; } // Sutun placeholder'i
|
||||||
public bool? Visible { get; set; } // Liste üzerinde gösterilecek mi? Yoksa eklenebilir sütunların arasında mı duracak. select sorgusuna dahildir
|
public bool? Visible { get; set; } // Liste üzerinde gösterilecek mi? Yoksa eklenebilir sütunların arasında mı duracak. select sorgusuna dahildir
|
||||||
public bool? IsActive { get; set; } = true; // Sadece IsActive olan alanlar sorguya dahil edilir
|
public bool? IsActive { get; set; } = true; // Sadece IsActive olan alanlar sorguya dahil edilir
|
||||||
public int? Width { get; set; } // Sütunun listedeki genişliği
|
public int? Width { get; set; } // Sütunun listedeki genişliği
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@ using Microsoft.Extensions.Localization;
|
||||||
using Microsoft.Extensions.Primitives;
|
using Microsoft.Extensions.Primitives;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Data;
|
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
|
|
@ -95,6 +94,7 @@ public class ListFormManager : PlatformDomainService, IListFormManager
|
||||||
var field = listFormFields.FirstOrDefault(c => c.FieldName == item.Key);
|
var field = listFormFields.FirstOrDefault(c => c.FieldName == item.Key);
|
||||||
if (field == null
|
if (field == null
|
||||||
|| (op == OperationEnum.Insert && !field.CanCreate)
|
|| (op == OperationEnum.Insert && !field.CanCreate)
|
||||||
|
|| (op == OperationEnum.Duplicate && !field.CanCreate)
|
||||||
|| (op == OperationEnum.Update && !field.CanUpdate)
|
|| (op == OperationEnum.Update && !field.CanUpdate)
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
|
|
@ -106,7 +106,7 @@ public class ListFormManager : PlatformDomainService, IListFormManager
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListFormField icerisinde olmayan (select sorgusu ile alinmayan) deger almasi gereken zorunlu alanlar
|
// ListFormField icerisinde olmayan (select sorgusu ile alinmayan) deger almasi gereken zorunlu alanlar
|
||||||
var defaultFields = await defaultValueManager.GenerateDefaultValuesAsync(listForm, op, keys, queryParameters);
|
var defaultFields = await defaultValueManager.GenerateDefaultValuesAsync(listForm, listFormFields, op, keys, queryParameters, inputParams);
|
||||||
|
|
||||||
if (PlatformConsts.IsMultiTenant && listForm.IsTenant)
|
if (PlatformConsts.IsMultiTenant && listForm.IsTenant)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -20,9 +20,11 @@ public interface IDefaultValueManager
|
||||||
{
|
{
|
||||||
Task<Dictionary<string, object>> GenerateDefaultValuesAsync(
|
Task<Dictionary<string, object>> GenerateDefaultValuesAsync(
|
||||||
ListForm listForm,
|
ListForm listForm,
|
||||||
|
List<ListFormField> listFormFields,
|
||||||
OperationEnum op,
|
OperationEnum op,
|
||||||
object[] keys = null,
|
object[] keys = null,
|
||||||
Dictionary<string, StringValues> queryParameters = null);
|
Dictionary<string, StringValues> queryParameters = null,
|
||||||
|
dynamic inputParams = null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public class DefaultValueManager : PlatformDomainService, IDefaultValueManager
|
public class DefaultValueManager : PlatformDomainService, IDefaultValueManager
|
||||||
|
|
@ -39,15 +41,18 @@ public class DefaultValueManager : PlatformDomainService, IDefaultValueManager
|
||||||
|
|
||||||
public async Task<Dictionary<string, object>> GenerateDefaultValuesAsync(
|
public async Task<Dictionary<string, object>> GenerateDefaultValuesAsync(
|
||||||
ListForm listForm,
|
ListForm listForm,
|
||||||
|
List<ListFormField> listFormFields,
|
||||||
OperationEnum op,
|
OperationEnum op,
|
||||||
object[] keys = null,
|
object[] keys = null,
|
||||||
Dictionary<string, StringValues> queryParameters = null)
|
Dictionary<string, StringValues> queryParameters = null,
|
||||||
|
dynamic inputParams = null)
|
||||||
{
|
{
|
||||||
var fields = new Dictionary<string, object>();
|
var fields = new Dictionary<string, object>();
|
||||||
|
|
||||||
var defaultFieldsJson = op switch
|
var defaultFieldsJson = op switch
|
||||||
{
|
{
|
||||||
OperationEnum.Insert => listForm.InsertFieldsDefaultValueJson,
|
OperationEnum.Insert => listForm.InsertFieldsDefaultValueJson,
|
||||||
|
OperationEnum.Duplicate => listForm.InsertFieldsDefaultValueJson,
|
||||||
OperationEnum.Update => listForm.UpdateFieldsDefaultValueJson,
|
OperationEnum.Update => listForm.UpdateFieldsDefaultValueJson,
|
||||||
OperationEnum.Delete => listForm.DeleteFieldsDefaultValueJson,
|
OperationEnum.Delete => listForm.DeleteFieldsDefaultValueJson,
|
||||||
OperationEnum.Select => listForm.FormFieldsDefaultValueJson,
|
OperationEnum.Select => listForm.FormFieldsDefaultValueJson,
|
||||||
|
|
@ -60,69 +65,88 @@ public class DefaultValueManager : PlatformDomainService, IDefaultValueManager
|
||||||
return fields;
|
return fields;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var field in JsonSerializer.Deserialize<List<FieldsDefaultValue>>(defaultFieldsJson))
|
foreach (var defaultField in JsonSerializer.Deserialize<List<FieldsDefaultValue>>(defaultFieldsJson))
|
||||||
{
|
{
|
||||||
object value = null;
|
object value = null;
|
||||||
switch (field.CustomValueType)
|
switch (defaultField.CustomValueType)
|
||||||
{
|
{
|
||||||
case FieldCustomValueTypeEnum.CustomKey:
|
case FieldCustomValueTypeEnum.CustomKey:
|
||||||
if (field.Value == PlatformConsts.DefaultValues.UserId)
|
if (defaultField.Value == PlatformConsts.DefaultValues.UserId)
|
||||||
value = CurrentUser.Id;
|
value = CurrentUser.Id;
|
||||||
else if (field.Value == PlatformConsts.DefaultValues.UserName)
|
else if (defaultField.Value == PlatformConsts.DefaultValues.UserName)
|
||||||
value = CurrentUser.Name;
|
value = CurrentUser.Name;
|
||||||
else if (field.Value == PlatformConsts.DefaultValues.Roles)
|
else if (defaultField.Value == PlatformConsts.DefaultValues.Roles)
|
||||||
value = CurrentUser.Roles; //.JoinAsString("','");
|
value = CurrentUser.Roles; //.JoinAsString("','");
|
||||||
else if (field.Value == PlatformConsts.DefaultValues.Date)
|
else if (defaultField.Value == PlatformConsts.DefaultValues.Date)
|
||||||
value = Clock.Now.Date;
|
value = Clock.Now.Date;
|
||||||
else if (field.Value == PlatformConsts.DefaultValues.Now)
|
else if (defaultField.Value == PlatformConsts.DefaultValues.Now)
|
||||||
value = Clock.Now;
|
value = Clock.Now;
|
||||||
else if (field.Value == PlatformConsts.DefaultValues.Day)
|
else if (defaultField.Value == PlatformConsts.DefaultValues.Day)
|
||||||
value = Clock.Now.Day;
|
value = Clock.Now.Day;
|
||||||
else if (field.Value == PlatformConsts.DefaultValues.Month)
|
else if (defaultField.Value == PlatformConsts.DefaultValues.Month)
|
||||||
value = Clock.Now.Month;
|
value = Clock.Now.Month;
|
||||||
else if (field.Value == PlatformConsts.DefaultValues.Year)
|
else if (defaultField.Value == PlatformConsts.DefaultValues.Year)
|
||||||
value = Clock.Now.Year;
|
value = Clock.Now.Year;
|
||||||
else if (field.Value == PlatformConsts.DefaultValues.Id)
|
else if (defaultField.Value == PlatformConsts.DefaultValues.Id)
|
||||||
value = keys?.FirstOrDefault();
|
value = keys?.FirstOrDefault();
|
||||||
else if (field.Value == PlatformConsts.DefaultValues.NewId)
|
else if (defaultField.Value == PlatformConsts.DefaultValues.NewId)
|
||||||
value = Guid.NewGuid();
|
value = Guid.NewGuid();
|
||||||
else if (field.Value == PlatformConsts.DefaultValues.Selected_Ids)
|
else if (defaultField.Value == PlatformConsts.DefaultValues.Selected_Ids)
|
||||||
value = keys;
|
value = keys;
|
||||||
else if (field.Value == PlatformConsts.DefaultValues.TenantId)
|
else if (defaultField.Value == PlatformConsts.DefaultValues.TenantId)
|
||||||
value = CurrentTenant.Id;
|
value = CurrentTenant.Id;
|
||||||
else
|
else
|
||||||
value = field.Value;
|
{
|
||||||
|
if ((object)inputParams != null)
|
||||||
|
{
|
||||||
|
foreach (var item in JsonSerializer.Deserialize<Dictionary<string, object>>(inputParams))
|
||||||
|
{
|
||||||
|
var field = listFormFields.FirstOrDefault(c => c.FieldName == item.Key);
|
||||||
|
if (field == null
|
||||||
|
|| (op == OperationEnum.Insert && !field.CanCreate)
|
||||||
|
|| (op == OperationEnum.Duplicate && !field.CanCreate)
|
||||||
|
|| (op == OperationEnum.Update && !field.CanUpdate)
|
||||||
|
)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
value = QueryHelper.GetFormattedValue(field.SourceDbType, item.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//value = field.Value;
|
||||||
//TODO: artirilabilir
|
//TODO: artirilabilir
|
||||||
break;
|
break;
|
||||||
case FieldCustomValueTypeEnum.QueryParams:
|
case FieldCustomValueTypeEnum.QueryParams:
|
||||||
if (queryParameters != null)
|
if (queryParameters != null)
|
||||||
{
|
{
|
||||||
value = queryParameters.GetValueOrDefault(field.Value);
|
value = queryParameters.GetValueOrDefault(defaultField.Value);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case FieldCustomValueTypeEnum.DbQuery:
|
case FieldCustomValueTypeEnum.DbQuery:
|
||||||
if (!string.IsNullOrWhiteSpace(field.SqlQuery))
|
if (!string.IsNullOrWhiteSpace(defaultField.SqlQuery))
|
||||||
{
|
{
|
||||||
var dynamicDataManager = LazyServiceProvider.LazyGetRequiredService<IDynamicDataManager>();
|
var dynamicDataManager = LazyServiceProvider.LazyGetRequiredService<IDynamicDataManager>();
|
||||||
var (dynamicDataRepository, connectionString, _) = await dynamicDataManager.GetAsync(listForm.IsTenant, listForm.DataSourceCode);
|
var (dynamicDataRepository, connectionString, _) = await dynamicDataManager.GetAsync(listForm.IsTenant, listForm.DataSourceCode);
|
||||||
value = await dynamicDataRepository.ExecuteScalarAsync<object>(field.SqlQuery, connectionString);
|
value = await dynamicDataRepository.ExecuteScalarAsync<object>(defaultField.SqlQuery, connectionString);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case FieldCustomValueTypeEnum.Value:
|
case FieldCustomValueTypeEnum.Value:
|
||||||
default:
|
default:
|
||||||
value = field.Value;
|
value = defaultField.Value;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (value != null && !string.IsNullOrWhiteSpace(value.ToString()))
|
if (value != null && !string.IsNullOrWhiteSpace(value.ToString()))
|
||||||
{
|
{
|
||||||
var formattedValue = QueryHelper.GetFormattedValue(field.FieldDbType, value);
|
var formattedValue = QueryHelper.GetFormattedValue(defaultField.FieldDbType, value);
|
||||||
if (fields.ContainsKey(field.FieldName))
|
if (fields.ContainsKey(defaultField.FieldName))
|
||||||
{
|
{
|
||||||
fields[field.FieldName] = formattedValue;
|
fields[defaultField.FieldName] = formattedValue;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
fields.Add(field.FieldName, formattedValue);
|
fields.Add(defaultField.FieldName, formattedValue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@ public interface IQueryManager
|
||||||
|
|
||||||
string GenerateQuery(
|
string GenerateQuery(
|
||||||
ListForm listForm,
|
ListForm listForm,
|
||||||
|
List<ListFormField> listFormFields,
|
||||||
Dictionary<string, object> parameters,
|
Dictionary<string, object> parameters,
|
||||||
OperationEnum op,
|
OperationEnum op,
|
||||||
DataSourceTypeEnum dataSourceType,
|
DataSourceTypeEnum dataSourceType,
|
||||||
|
|
@ -77,24 +78,20 @@ public class QueryManager : PlatformDomainService, IQueryManager
|
||||||
var listFormFields = await listFormFieldManager.GetUserListFormFields(listFormCode);
|
var listFormFields = await listFormFieldManager.GetUserListFormFields(listFormCode);
|
||||||
|
|
||||||
var parameters = await listFormManager.GetParametersAsync(listForm, listFormFields, inputParams, op, keys, queryParameters);
|
var parameters = await listFormManager.GetParametersAsync(listForm, listFormFields, inputParams, op, keys, queryParameters);
|
||||||
// if (parameters == null || parameters.Count == 0)
|
|
||||||
// {
|
|
||||||
// throw new UserFriendlyException(localizer[AppErrorCodes.ParameterNotValid]);
|
|
||||||
// }
|
|
||||||
|
|
||||||
var (dynamicDataRepository, connectionString, dataSourceType) = await dynamicDataManager.GetAsync(listForm.IsTenant, listForm.DataSourceCode);
|
var (dynamicDataRepository, connectionString, dataSourceType) = await dynamicDataManager.GetAsync(listForm.IsTenant, listForm.DataSourceCode);
|
||||||
|
|
||||||
var sql = GenerateQuery(listForm, parameters, op, dataSourceType, keys);
|
var sql = GenerateQuery(listForm, listFormFields, parameters, op, dataSourceType, keys);
|
||||||
|
|
||||||
// Sorguyu calistir
|
// Sorguyu calistir
|
||||||
if (!string.IsNullOrEmpty(sql))
|
if (!string.IsNullOrEmpty(sql))
|
||||||
{
|
{
|
||||||
// TODO: Log
|
// TODO: Log
|
||||||
if (op == OperationEnum.Insert)
|
if (op == OperationEnum.Insert || op == OperationEnum.Duplicate)
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrEmpty(listForm.InsertBeforeCommand))
|
if (!string.IsNullOrEmpty(listForm.InsertBeforeCommand))
|
||||||
{
|
{
|
||||||
var beforeSql = GenerateQuery(listForm, parameters, OperationEnum.InsertBefore, dataSourceType, keys);
|
var beforeSql = GenerateQuery(listForm, listFormFields, parameters, OperationEnum.InsertBefore, dataSourceType, keys);
|
||||||
await dynamicDataRepository.ExecuteAsync(beforeSql, connectionString, parameters);
|
await dynamicDataRepository.ExecuteAsync(beforeSql, connectionString, parameters);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -102,7 +99,7 @@ public class QueryManager : PlatformDomainService, IQueryManager
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(listForm.InsertAfterCommand))
|
if (!string.IsNullOrEmpty(listForm.InsertAfterCommand))
|
||||||
{
|
{
|
||||||
var afterSql = GenerateQuery(listForm, parameters, OperationEnum.InsertAfter, dataSourceType, keys);
|
var afterSql = GenerateQuery(listForm, listFormFields, parameters, OperationEnum.InsertAfter, dataSourceType, keys);
|
||||||
await dynamicDataRepository.ExecuteAsync(afterSql, connectionString, parameters);
|
await dynamicDataRepository.ExecuteAsync(afterSql, connectionString, parameters);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -113,12 +110,12 @@ public class QueryManager : PlatformDomainService, IQueryManager
|
||||||
// Before komutlari varsa calistir
|
// Before komutlari varsa calistir
|
||||||
if (op == OperationEnum.Update && !string.IsNullOrEmpty(listForm.UpdateBeforeCommand))
|
if (op == OperationEnum.Update && !string.IsNullOrEmpty(listForm.UpdateBeforeCommand))
|
||||||
{
|
{
|
||||||
var beforeSql = GenerateQuery(listForm, parameters, OperationEnum.UpdateBefore, dataSourceType, keys);
|
var beforeSql = GenerateQuery(listForm, listFormFields, parameters, OperationEnum.UpdateBefore, dataSourceType, keys);
|
||||||
await dynamicDataRepository.ExecuteAsync(beforeSql, connectionString, parameters);
|
await dynamicDataRepository.ExecuteAsync(beforeSql, connectionString, parameters);
|
||||||
}
|
}
|
||||||
else if (op == OperationEnum.Delete && !string.IsNullOrEmpty(listForm.DeleteBeforeCommand))
|
else if (op == OperationEnum.Delete && !string.IsNullOrEmpty(listForm.DeleteBeforeCommand))
|
||||||
{
|
{
|
||||||
var beforeSql = GenerateQuery(listForm, parameters, OperationEnum.DeleteBefore, dataSourceType, keys);
|
var beforeSql = GenerateQuery(listForm, listFormFields, parameters, OperationEnum.DeleteBefore, dataSourceType, keys);
|
||||||
await dynamicDataRepository.ExecuteAsync(beforeSql, connectionString, parameters);
|
await dynamicDataRepository.ExecuteAsync(beforeSql, connectionString, parameters);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -128,12 +125,12 @@ public class QueryManager : PlatformDomainService, IQueryManager
|
||||||
// After komutlari varsa calistir
|
// After komutlari varsa calistir
|
||||||
if (op == OperationEnum.Update && !string.IsNullOrEmpty(listForm.UpdateAfterCommand))
|
if (op == OperationEnum.Update && !string.IsNullOrEmpty(listForm.UpdateAfterCommand))
|
||||||
{
|
{
|
||||||
var afterSql = GenerateQuery(listForm, parameters, OperationEnum.UpdateAfter, dataSourceType, keys);
|
var afterSql = GenerateQuery(listForm, listFormFields, parameters, OperationEnum.UpdateAfter, dataSourceType, keys);
|
||||||
await dynamicDataRepository.ExecuteAsync(afterSql, connectionString, parameters);
|
await dynamicDataRepository.ExecuteAsync(afterSql, connectionString, parameters);
|
||||||
}
|
}
|
||||||
else if (op == OperationEnum.Delete && !string.IsNullOrEmpty(listForm.DeleteAfterCommand))
|
else if (op == OperationEnum.Delete && !string.IsNullOrEmpty(listForm.DeleteAfterCommand))
|
||||||
{
|
{
|
||||||
var afterSql = GenerateQuery(listForm, parameters, OperationEnum.DeleteAfter, dataSourceType, keys);
|
var afterSql = GenerateQuery(listForm, listFormFields, parameters, OperationEnum.DeleteAfter, dataSourceType, keys);
|
||||||
await dynamicDataRepository.ExecuteAsync(afterSql, connectionString, parameters);
|
await dynamicDataRepository.ExecuteAsync(afterSql, connectionString, parameters);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -146,6 +143,7 @@ public class QueryManager : PlatformDomainService, IQueryManager
|
||||||
|
|
||||||
public string GenerateQuery(
|
public string GenerateQuery(
|
||||||
ListForm listForm,
|
ListForm listForm,
|
||||||
|
List<ListFormField> listFormField,
|
||||||
Dictionary<string, object> parameters,
|
Dictionary<string, object> parameters,
|
||||||
OperationEnum op,
|
OperationEnum op,
|
||||||
DataSourceTypeEnum dataSourceType,
|
DataSourceTypeEnum dataSourceType,
|
||||||
|
|
@ -153,6 +151,7 @@ public class QueryManager : PlatformDomainService, IQueryManager
|
||||||
{
|
{
|
||||||
var command = op switch
|
var command = op switch
|
||||||
{
|
{
|
||||||
|
OperationEnum.Duplicate => listForm.InsertCommand,
|
||||||
OperationEnum.Insert => listForm.InsertCommand,
|
OperationEnum.Insert => listForm.InsertCommand,
|
||||||
OperationEnum.InsertBefore => listForm.InsertBeforeCommand,
|
OperationEnum.InsertBefore => listForm.InsertBeforeCommand,
|
||||||
OperationEnum.InsertAfter => listForm.InsertAfterCommand,
|
OperationEnum.InsertAfter => listForm.InsertAfterCommand,
|
||||||
|
|
@ -177,7 +176,7 @@ public class QueryManager : PlatformDomainService, IQueryManager
|
||||||
var fieldString = string.Join(',', parameters.Keys.Select(a => $"\"{a}\"").ToList());
|
var fieldString = string.Join(',', parameters.Keys.Select(a => $"\"{a}\"").ToList());
|
||||||
var fieldParams = string.Join(',', parameters.Keys.Select(a => $"@{a}"));
|
var fieldParams = string.Join(',', parameters.Keys.Select(a => $"@{a}"));
|
||||||
|
|
||||||
if (op == OperationEnum.Insert)
|
if (op == OperationEnum.Insert || op == OperationEnum.Duplicate)
|
||||||
{
|
{
|
||||||
sql = dataSourceType switch
|
sql = dataSourceType switch
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -365,6 +365,7 @@ public class PlatformDbContext :
|
||||||
b.Property(a => a.CultureName).HasMaxLength(10).IsRequired();
|
b.Property(a => a.CultureName).HasMaxLength(10).IsRequired();
|
||||||
b.Property(a => a.FieldName).IsRequired().HasMaxLength(128);
|
b.Property(a => a.FieldName).IsRequired().HasMaxLength(128);
|
||||||
b.Property(a => a.CaptionName).HasMaxLength(256);
|
b.Property(a => a.CaptionName).HasMaxLength(256);
|
||||||
|
b.Property(a => a.PlaceHolder).HasMaxLength(256);
|
||||||
|
|
||||||
// Varsayılan değerler
|
// Varsayılan değerler
|
||||||
b.Property(a => a.AllowSearch).HasDefaultValue(false);
|
b.Property(a => a.AllowSearch).HasDefaultValue(false);
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ using Volo.Abp.EntityFrameworkCore;
|
||||||
namespace Sozsoft.Platform.Migrations
|
namespace Sozsoft.Platform.Migrations
|
||||||
{
|
{
|
||||||
[DbContext(typeof(PlatformDbContext))]
|
[DbContext(typeof(PlatformDbContext))]
|
||||||
[Migration("20260317181749_Initial")]
|
[Migration("20260330120142_Initial")]
|
||||||
partial class Initial
|
partial class Initial
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
|
@ -2656,6 +2656,10 @@ namespace Sozsoft.Platform.Migrations
|
||||||
b.Property<string>("PivotSettingsJson")
|
b.Property<string>("PivotSettingsJson")
|
||||||
.HasColumnType("text");
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("PlaceHolder")
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("nvarchar(256)");
|
||||||
|
|
||||||
b.Property<string>("RoleId")
|
b.Property<string>("RoleId")
|
||||||
.HasMaxLength(256)
|
.HasMaxLength(256)
|
||||||
.HasColumnType("nvarchar(256)");
|
.HasColumnType("nvarchar(256)");
|
||||||
|
|
@ -2031,6 +2031,7 @@ namespace Sozsoft.Platform.Migrations
|
||||||
CultureName = table.Column<string>(type: "nvarchar(10)", maxLength: 10, nullable: false),
|
CultureName = table.Column<string>(type: "nvarchar(10)", maxLength: 10, nullable: false),
|
||||||
FieldName = table.Column<string>(type: "nvarchar(128)", maxLength: 128, nullable: false),
|
FieldName = table.Column<string>(type: "nvarchar(128)", maxLength: 128, nullable: false),
|
||||||
CaptionName = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: true),
|
CaptionName = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: true),
|
||||||
|
PlaceHolder = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: true),
|
||||||
Visible = table.Column<bool>(type: "bit", nullable: true, defaultValue: true),
|
Visible = table.Column<bool>(type: "bit", nullable: true, defaultValue: true),
|
||||||
IsActive = table.Column<bool>(type: "bit", nullable: true, defaultValue: true),
|
IsActive = table.Column<bool>(type: "bit", nullable: true, defaultValue: true),
|
||||||
Width = table.Column<int>(type: "int", nullable: true, defaultValue: 100),
|
Width = table.Column<int>(type: "int", nullable: true, defaultValue: 100),
|
||||||
|
|
@ -2653,6 +2653,10 @@ namespace Sozsoft.Platform.Migrations
|
||||||
b.Property<string>("PivotSettingsJson")
|
b.Property<string>("PivotSettingsJson")
|
||||||
.HasColumnType("text");
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("PlaceHolder")
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("nvarchar(256)");
|
||||||
|
|
||||||
b.Property<string>("RoleId")
|
b.Property<string>("RoleId")
|
||||||
.HasMaxLength(256)
|
.HasMaxLength(256)
|
||||||
.HasColumnType("nvarchar(256)");
|
.HasColumnType("nvarchar(256)");
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ using DevExpress.DataAccess.Sql;
|
||||||
using DevExpress.XtraReports.Web.ReportDesigner;
|
using DevExpress.XtraReports.Web.ReportDesigner;
|
||||||
using DevExpress.XtraReports.Web.ReportDesigner.Services;
|
using DevExpress.XtraReports.Web.ReportDesigner.Services;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Sozsoft.Platform.Enums;
|
||||||
|
|
||||||
namespace Sozsoft.Platform.Controllers;
|
namespace Sozsoft.Platform.Controllers;
|
||||||
|
|
||||||
|
|
@ -23,9 +24,9 @@ public class CustomReportDesignerController : ReportDesignerController
|
||||||
var ds = new SqlDataSource("SqlServer");
|
var ds = new SqlDataSource("SqlServer");
|
||||||
|
|
||||||
SelectQuery query = SelectQueryFluentBuilder
|
SelectQuery query = SelectQueryFluentBuilder
|
||||||
.AddTable("Sas_T_Sector")
|
.AddTable(TableNameResolver.GetFullTableName(nameof(TableNameEnum.Sector)))
|
||||||
.SelectAllColumnsFromTable()
|
.SelectAllColumnsFromTable()
|
||||||
.Build("Sas_T_Sector");
|
.Build(TableNameResolver.GetFullTableName(nameof(TableNameEnum.Sector)));
|
||||||
ds.Queries.Add(query);
|
ds.Queries.Add(query);
|
||||||
ds.RebuildResultSchema();
|
ds.RebuildResultSchema();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,281 @@
|
||||||
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.Data.SqlClient;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Hosting;
|
||||||
|
using Serilog;
|
||||||
|
using static Sozsoft.Settings.SettingsConsts;
|
||||||
|
|
||||||
|
namespace Sozsoft.Platform;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Veritabanı henüz hazır değilken çalışan minimal kurulum uygulaması.
|
||||||
|
/// Tam ABP stack yüklemez; sadece /api/setup/* endpointlerini sunar.
|
||||||
|
/// </summary>
|
||||||
|
internal static class SetupAppRunner
|
||||||
|
{
|
||||||
|
// Veritabanı Hazırlık Kontrolü
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// DB var mı ve AbpRoles tablosu oluşmuş mu diye kontrol eder.
|
||||||
|
/// Boş DB veya bağlantı hatası durumunda false döner.
|
||||||
|
/// </summary>
|
||||||
|
public static bool DatabaseIsReady(IConfiguration configuration)
|
||||||
|
{
|
||||||
|
var connectionString = configuration.GetConnectionString(DefaultDatabaseProvider);
|
||||||
|
if (string.IsNullOrWhiteSpace(connectionString))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (DefaultDatabaseProvider == DatabaseProvider.SqlServer)
|
||||||
|
return SqlServerIsReady(connectionString);
|
||||||
|
|
||||||
|
#pragma warning disable CS0162
|
||||||
|
return true; // Diğer sağlayıcılar için geçici — ileride PostgreSQL desteği eklenecek
|
||||||
|
#pragma warning restore CS0162
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log.Warning("Veritabanı hazırlık kontrolü başarısız: {Error}", ex.Message);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool SqlServerIsReady(string connectionString)
|
||||||
|
{
|
||||||
|
var csb = new SqlConnectionStringBuilder(connectionString);
|
||||||
|
var dbName = csb.InitialCatalog;
|
||||||
|
if (string.IsNullOrEmpty(dbName))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// 1) master'a bağlan — DB varlığını kontrol et
|
||||||
|
var masterCsb = new SqlConnectionStringBuilder(connectionString)
|
||||||
|
{
|
||||||
|
InitialCatalog = "master",
|
||||||
|
ConnectTimeout = 8
|
||||||
|
};
|
||||||
|
|
||||||
|
using var masterConn = new SqlConnection(masterCsb.ConnectionString);
|
||||||
|
masterConn.Open();
|
||||||
|
|
||||||
|
using var dbCheck = new SqlCommand(
|
||||||
|
"SELECT COUNT(1) FROM sys.databases WHERE name = @n", masterConn);
|
||||||
|
dbCheck.Parameters.AddWithValue("@n", dbName);
|
||||||
|
if ((int)dbCheck.ExecuteScalar() == 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// 2) Hedef DB'ye bağlan — AbpRoles tablosunun varlığını kontrol et
|
||||||
|
csb.ConnectTimeout = 8;
|
||||||
|
using var dbConn = new SqlConnection(csb.ConnectionString);
|
||||||
|
dbConn.Open();
|
||||||
|
|
||||||
|
using var tableCheck = new SqlCommand(
|
||||||
|
"SELECT COUNT(1) FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = 'AbpRoles'",
|
||||||
|
dbConn);
|
||||||
|
return (int)tableCheck.ExecuteScalar() > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Minimal Kurulum Uygulaması
|
||||||
|
|
||||||
|
public static async Task<int> RunAsync(string[] args, IConfiguration configuration)
|
||||||
|
{
|
||||||
|
Log.Warning("Veritabanı hazır değil — kurulum modu başlatılıyor.");
|
||||||
|
|
||||||
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
|
||||||
|
var extraOrigins = (configuration["App:CorsOrigins"] ?? "")
|
||||||
|
.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
||||||
|
var baseDomain = configuration["App:BaseDomain"]?.Trim();
|
||||||
|
|
||||||
|
builder.Services.AddCors(o => o.AddPolicy("Setup", policy =>
|
||||||
|
policy.AllowAnyHeader()
|
||||||
|
.AllowAnyMethod()
|
||||||
|
.AllowCredentials()
|
||||||
|
.SetIsOriginAllowed(origin =>
|
||||||
|
{
|
||||||
|
if (!Uri.TryCreate(origin, UriKind.Absolute, out var uri)) return false;
|
||||||
|
var host = uri.Host.ToLowerInvariant();
|
||||||
|
|
||||||
|
if (host is "localhost" or "127.0.0.1" or "[::1]")
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(baseDomain))
|
||||||
|
{
|
||||||
|
var bd = baseDomain.ToLowerInvariant();
|
||||||
|
if (host == bd || host.EndsWith("." + bd))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var o in extraOrigins)
|
||||||
|
if (Uri.TryCreate(o, UriKind.Absolute, out var eo) &&
|
||||||
|
eo.Host.Equals(host, StringComparison.OrdinalIgnoreCase))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
})));
|
||||||
|
|
||||||
|
builder.Host.UseSerilog();
|
||||||
|
|
||||||
|
var app = builder.Build();
|
||||||
|
app.UseCors("Setup");
|
||||||
|
|
||||||
|
app.MapGet("/api/setup/status", (IConfiguration cfg) =>
|
||||||
|
Results.Ok(new { dbExists = DatabaseIsReady(cfg) }));
|
||||||
|
|
||||||
|
app.MapGet("/api/setup/migrate", async (IConfiguration cfg, IHostEnvironment env,
|
||||||
|
IHostApplicationLifetime lifetime, HttpContext ctx, CancellationToken ct) =>
|
||||||
|
{
|
||||||
|
ctx.Response.ContentType = "text/event-stream; charset=utf-8";
|
||||||
|
ctx.Response.Headers["Cache-Control"] = "no-cache, no-store";
|
||||||
|
ctx.Response.Headers["X-Accel-Buffering"] = "no";
|
||||||
|
await ctx.Response.Body.FlushAsync(ct);
|
||||||
|
|
||||||
|
async Task Send(string level, string message)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var payload = JsonSerializer.Serialize(new { level, message });
|
||||||
|
await ctx.Response.WriteAsync($"data: {payload}\n\n", ct);
|
||||||
|
await ctx.Response.Body.FlushAsync(ct);
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (DatabaseIsReady(cfg))
|
||||||
|
{
|
||||||
|
await Send("warn", "Veritabanı zaten hazır. Migration atlanıyor.");
|
||||||
|
await Send("done", "Tamamlandı.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var migratorPath = cfg["Setup:MigratorPath"]
|
||||||
|
?? Path.GetFullPath(Path.Combine(env.ContentRootPath, "..", "Sozsoft.Platform.DbMigrator"));
|
||||||
|
|
||||||
|
await Send("info", "Veritabanı migration ve seed başlatılıyor...");
|
||||||
|
await Send("info", $"Migrator yolu: {migratorPath}");
|
||||||
|
|
||||||
|
var extraArgs = cfg["Setup:MigratorArgs"] ?? "--Seed=true";
|
||||||
|
|
||||||
|
string fileName;
|
||||||
|
string arguments;
|
||||||
|
string workingDirectory;
|
||||||
|
|
||||||
|
if (migratorPath.EndsWith(".dll", StringComparison.OrdinalIgnoreCase) && File.Exists(migratorPath))
|
||||||
|
{
|
||||||
|
// Doğrudan DLL yolu verilmiş — "--" separator YOK, doğrudan argüman
|
||||||
|
fileName = "dotnet";
|
||||||
|
arguments = $"\"{migratorPath}\" {extraArgs}";
|
||||||
|
workingDirectory = Path.GetDirectoryName(migratorPath)!;
|
||||||
|
}
|
||||||
|
else if (Directory.Exists(migratorPath))
|
||||||
|
{
|
||||||
|
// Klasör verilmiş — içinde publish edilmiş DLL var mı?
|
||||||
|
var dllFiles = Directory.GetFiles(migratorPath, "*.DbMigrator.dll", SearchOption.TopDirectoryOnly);
|
||||||
|
if (dllFiles.Length == 0)
|
||||||
|
dllFiles = Directory.GetFiles(migratorPath, "*Migrator*.dll", SearchOption.TopDirectoryOnly);
|
||||||
|
|
||||||
|
if (dllFiles.Length > 0)
|
||||||
|
{
|
||||||
|
// Publish çıktısı — SDK gerekmez, "--" separator YOK
|
||||||
|
fileName = "dotnet";
|
||||||
|
arguments = $"\"{dllFiles[0]}\" {extraArgs}";
|
||||||
|
workingDirectory = migratorPath;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Kaynak proje klasörü — geliştirme ortamı, "--" gerekli
|
||||||
|
fileName = "dotnet";
|
||||||
|
arguments = $"run --project \"{migratorPath}\" -- {extraArgs}";
|
||||||
|
workingDirectory = migratorPath;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await Send("error", $"Migrator yolu bulunamadı veya geçersiz: {migratorPath}");
|
||||||
|
await Send("done", "Hata ile sonlandı.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await Send("info", $"Çalıştırılıyor: {fileName} {arguments}");
|
||||||
|
|
||||||
|
Process? process = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
process = new Process
|
||||||
|
{
|
||||||
|
StartInfo = new ProcessStartInfo
|
||||||
|
{
|
||||||
|
FileName = fileName,
|
||||||
|
Arguments = arguments,
|
||||||
|
RedirectStandardOutput = true,
|
||||||
|
RedirectStandardError = true,
|
||||||
|
UseShellExecute = false,
|
||||||
|
CreateNoWindow = true,
|
||||||
|
WorkingDirectory = workingDirectory,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
process.Start();
|
||||||
|
|
||||||
|
async Task ReadStream(StreamReader reader, string level)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
while (!reader.EndOfStream)
|
||||||
|
{
|
||||||
|
var line = await reader.ReadLineAsync(ct);
|
||||||
|
if (line != null) await Send(level, line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException) { }
|
||||||
|
}
|
||||||
|
|
||||||
|
await Task.WhenAll(
|
||||||
|
ReadStream(process.StandardOutput, "info"),
|
||||||
|
ReadStream(process.StandardError, "warn"));
|
||||||
|
|
||||||
|
await process.WaitForExitAsync(ct);
|
||||||
|
|
||||||
|
if (process.ExitCode == 0)
|
||||||
|
{
|
||||||
|
await Send("success", "Migration ve seed başarıyla tamamlandı.");
|
||||||
|
await Send("restart", "Uygulama sunucusu yeniden başlatılıyor...");
|
||||||
|
await Send("done", "Tamamlandı.");
|
||||||
|
|
||||||
|
_ = Task.Delay(1500).ContinueWith(_ => lifetime.StopApplication());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await Send("error", $"Migration başarısız. Çıkış kodu: {process.ExitCode}");
|
||||||
|
await Send("done", "Hata ile sonlandı.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
await Send("warn", "Migration isteği iptal edildi.");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await Send("error", $"Migration hatası: {ex.Message}");
|
||||||
|
await Send("done", "Hata ile sonlandı.");
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
process?.Dispose();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
app.MapFallback(() => Results.StatusCode(503));
|
||||||
|
|
||||||
|
await app.RunAsync();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
using DevExpress.XtraReports.UI;
|
using DevExpress.XtraReports.UI;
|
||||||
|
using Sozsoft.Platform.Enums;
|
||||||
|
|
||||||
namespace Sozsoft.Reports.PredefinedReports
|
namespace Sozsoft.Reports.PredefinedReports
|
||||||
{
|
{
|
||||||
|
|
@ -104,12 +105,12 @@ namespace Sozsoft.Reports.PredefinedReports
|
||||||
//
|
//
|
||||||
this.sqlDataSource1.ConnectionName = "SqlServer";
|
this.sqlDataSource1.ConnectionName = "SqlServer";
|
||||||
this.sqlDataSource1.Name = "sqlDataSource1";
|
this.sqlDataSource1.Name = "sqlDataSource1";
|
||||||
table1.Name = "Sas_T_ReportTemplate";
|
table1.Name = TableNameResolver.GetFullTableName(nameof(TableNameEnum.ReportTemplate));
|
||||||
columnExpression1.ColumnName = "HtmlContent";
|
columnExpression1.ColumnName = "HtmlContent";
|
||||||
columnExpression1.Table = table1;
|
columnExpression1.Table = table1;
|
||||||
column1.Expression = columnExpression1;
|
column1.Expression = columnExpression1;
|
||||||
selectQuery1.Columns.Add(column1);
|
selectQuery1.Columns.Add(column1);
|
||||||
selectQuery1.Name = "Sas_T_ReportTemplate";
|
selectQuery1.Name = TableNameResolver.GetFullTableName(nameof(TableNameEnum.ReportTemplate));
|
||||||
selectQuery1.Tables.Add(table1);
|
selectQuery1.Tables.Add(table1);
|
||||||
this.sqlDataSource1.Queries.AddRange(new DevExpress.DataAccess.Sql.SqlQuery[] { selectQuery1 });
|
this.sqlDataSource1.Queries.AddRange(new DevExpress.DataAccess.Sql.SqlQuery[] { selectQuery1 });
|
||||||
this.sqlDataSource1.ResultSchemaSerializable = resources.GetString("sqlDataSource1.ResultSchemaSerializable");
|
this.sqlDataSource1.ResultSchemaSerializable = resources.GetString("sqlDataSource1.ResultSchemaSerializable");
|
||||||
|
|
@ -176,7 +177,7 @@ namespace Sozsoft.Reports.PredefinedReports
|
||||||
this.Detail});
|
this.Detail});
|
||||||
this.ComponentStorage.AddRange(new System.ComponentModel.IComponent[] {
|
this.ComponentStorage.AddRange(new System.ComponentModel.IComponent[] {
|
||||||
this.sqlDataSource1});
|
this.sqlDataSource1});
|
||||||
this.DataMember = "Sas_T_ReportTemplate";
|
this.DataMember = TableNameResolver.GetFullTableName(nameof(TableNameEnum.ReportTemplate));
|
||||||
this.DataSource = this.sqlDataSource1;
|
this.DataSource = this.sqlDataSource1;
|
||||||
this.Font = new DevExpress.Drawing.DXFont("Arial", 9.75F);
|
this.Font = new DevExpress.Drawing.DXFont("Arial", 9.75F);
|
||||||
this.StyleSheet.AddRange(new DevExpress.XtraReports.UI.XRControlStyle[] {
|
this.StyleSheet.AddRange(new DevExpress.XtraReports.UI.XRControlStyle[] {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
namespace Sozsoft.Reports.PredefinedReports
|
using Sozsoft.Platform.Enums;
|
||||||
|
|
||||||
|
namespace Sozsoft.Reports.PredefinedReports
|
||||||
{
|
{
|
||||||
partial class TestReport
|
partial class TestReport
|
||||||
{
|
{
|
||||||
|
|
@ -11,8 +13,10 @@
|
||||||
/// Clean up any resources being used.
|
/// Clean up any resources being used.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
|
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
|
||||||
protected override void Dispose(bool disposing) {
|
protected override void Dispose(bool disposing)
|
||||||
if(disposing && (components != null)) {
|
{
|
||||||
|
if (disposing && (components != null))
|
||||||
|
{
|
||||||
components.Dispose();
|
components.Dispose();
|
||||||
}
|
}
|
||||||
base.Dispose(disposing);
|
base.Dispose(disposing);
|
||||||
|
|
@ -24,7 +28,8 @@
|
||||||
/// Required method for Designer support - do not modify
|
/// Required method for Designer support - do not modify
|
||||||
/// the contents of this method with the code editor.
|
/// the contents of this method with the code editor.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void InitializeComponent() {
|
private void InitializeComponent()
|
||||||
|
{
|
||||||
this.components = new System.ComponentModel.Container();
|
this.components = new System.ComponentModel.Container();
|
||||||
DevExpress.DataAccess.Sql.SelectQuery selectQuery1 = new DevExpress.DataAccess.Sql.SelectQuery();
|
DevExpress.DataAccess.Sql.SelectQuery selectQuery1 = new DevExpress.DataAccess.Sql.SelectQuery();
|
||||||
DevExpress.DataAccess.Sql.Column column1 = new DevExpress.DataAccess.Sql.Column();
|
DevExpress.DataAccess.Sql.Column column1 = new DevExpress.DataAccess.Sql.Column();
|
||||||
|
|
@ -145,7 +150,7 @@
|
||||||
this.sqlDataSource1.ConnectionName = "SqlServer";
|
this.sqlDataSource1.ConnectionName = "SqlServer";
|
||||||
this.sqlDataSource1.Name = "sqlDataSource1";
|
this.sqlDataSource1.Name = "sqlDataSource1";
|
||||||
columnExpression1.ColumnName = "Id";
|
columnExpression1.ColumnName = "Id";
|
||||||
table1.Name = "Sas_T_Sector";
|
table1.Name = TableNameResolver.GetFullTableName(nameof(TableNameEnum.Sector));
|
||||||
columnExpression1.Table = table1;
|
columnExpression1.Table = table1;
|
||||||
column1.Expression = columnExpression1;
|
column1.Expression = columnExpression1;
|
||||||
columnExpression2.ColumnName = "Name";
|
columnExpression2.ColumnName = "Name";
|
||||||
|
|
@ -153,7 +158,7 @@
|
||||||
column2.Expression = columnExpression2;
|
column2.Expression = columnExpression2;
|
||||||
selectQuery1.Columns.Add(column1);
|
selectQuery1.Columns.Add(column1);
|
||||||
selectQuery1.Columns.Add(column2);
|
selectQuery1.Columns.Add(column2);
|
||||||
selectQuery1.Name = "Sas_T_Sector";
|
selectQuery1.Name = TableNameResolver.GetFullTableName(nameof(TableNameEnum.Sector));
|
||||||
selectQuery1.Tables.Add(table1);
|
selectQuery1.Tables.Add(table1);
|
||||||
this.sqlDataSource1.Queries.AddRange(new DevExpress.DataAccess.Sql.SqlQuery[] {
|
this.sqlDataSource1.Queries.AddRange(new DevExpress.DataAccess.Sql.SqlQuery[] {
|
||||||
selectQuery1});
|
selectQuery1});
|
||||||
|
|
@ -221,7 +226,7 @@
|
||||||
this.Detail});
|
this.Detail});
|
||||||
this.ComponentStorage.AddRange(new System.ComponentModel.IComponent[] {
|
this.ComponentStorage.AddRange(new System.ComponentModel.IComponent[] {
|
||||||
this.sqlDataSource1});
|
this.sqlDataSource1});
|
||||||
this.DataMember = "Sas_T_Sector";
|
this.DataMember = TableNameResolver.GetFullTableName(nameof(TableNameEnum.Sector));
|
||||||
this.DataSource = this.sqlDataSource1;
|
this.DataSource = this.sqlDataSource1;
|
||||||
this.Font = new DevExpress.Drawing.DXFont("Arial", 9.75F);
|
this.Font = new DevExpress.Drawing.DXFont("Arial", 9.75F);
|
||||||
this.StyleSheet.AddRange(new DevExpress.XtraReports.UI.XRControlStyle[] {
|
this.StyleSheet.AddRange(new DevExpress.XtraReports.UI.XRControlStyle[] {
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@ using System.Threading.Tasks;
|
||||||
using Sozsoft.Platform.Enums;
|
using Sozsoft.Platform.Enums;
|
||||||
using Sozsoft.Platform.DynamicServices;
|
using Sozsoft.Platform.DynamicServices;
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
using Microsoft.AspNetCore.SignalR;
|
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Hosting;
|
using Microsoft.Extensions.Hosting;
|
||||||
|
|
@ -26,6 +25,24 @@ public class Program
|
||||||
.AddJsonFile($"appsettings.{Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ?? ""}.json", true)
|
.AddJsonFile($"appsettings.{Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ?? ""}.json", true)
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
|
// ── Veritabanı varlık kontrolü ────────────────────────────────────────
|
||||||
|
// Serilog SQL sink kurulmadan ve ABP modülleri yüklenmeden önce yapılmalı.
|
||||||
|
// DB yoksa minimal setup uygulaması çalıştırılır.
|
||||||
|
Log.Logger = new LoggerConfiguration()
|
||||||
|
.MinimumLevel.Warning()
|
||||||
|
.WriteTo.Console()
|
||||||
|
.CreateLogger();
|
||||||
|
|
||||||
|
if (!SetupAppRunner.DatabaseIsReady(configuration))
|
||||||
|
{
|
||||||
|
var setupResult = await SetupAppRunner.RunAsync(args, configuration);
|
||||||
|
if (setupResult != 0)
|
||||||
|
return setupResult;
|
||||||
|
// Migration başarılı — DB artık hazır, tam ABP başlatmasına geç
|
||||||
|
Log.Warning("Migration tamamlandı — tam uygulama başlatılıyor.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Veritabanı mevcut — tam ABP başlatma ─────────────────────────────
|
||||||
|
|
||||||
var columnWriters = new Dictionary<string, ColumnWriterBase>
|
var columnWriters = new Dictionary<string, ColumnWriterBase>
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,10 @@
|
||||||
"StringEncryption": {
|
"StringEncryption": {
|
||||||
"DefaultPassPhrase": "UQpiYfT79zRZ3yYH"
|
"DefaultPassPhrase": "UQpiYfT79zRZ3yYH"
|
||||||
},
|
},
|
||||||
|
"Setup": {
|
||||||
|
"MigratorPath": "/srv/Sozsoft.Platform.DbMigrator",
|
||||||
|
"MigratorArgs": "--Seed=true"
|
||||||
|
},
|
||||||
"Serilog": {
|
"Serilog": {
|
||||||
"MinimumLevel": {
|
"MinimumLevel": {
|
||||||
"Default": "Information"
|
"Default": "Information"
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Volo.Abp.AspNetCore.Mvc;
|
||||||
|
|
||||||
|
namespace Sozsoft.Platform.Controllers;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tam ABP uygulaması çalışırken setup durumunu döner.
|
||||||
|
/// SetupAppRunner'ın aynı endpoint'i DB hazır olmadığında dbExists=false döner.
|
||||||
|
/// </summary>
|
||||||
|
[ApiController]
|
||||||
|
[Route("api/setup")]
|
||||||
|
public class SetupController : AbpControllerBase
|
||||||
|
{
|
||||||
|
[HttpGet("status")]
|
||||||
|
public IActionResult Status() => Ok(new { dbExists = true });
|
||||||
|
}
|
||||||
71
claude.md
Normal file
71
claude.md
Normal file
|
|
@ -0,0 +1,71 @@
|
||||||
|
# Sozsoft Platform - Claude Instructions
|
||||||
|
|
||||||
|
This file provides Claude-specific operating rules for this repository.
|
||||||
|
Primary source of truth for platform behavior is:
|
||||||
|
|
||||||
|
- `.github/instructions/ai.instructions.md`
|
||||||
|
|
||||||
|
If there is any conflict, follow `.github/instructions/ai.instructions.md`.
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
|
||||||
|
- Maximize delivery through runtime configuration.
|
||||||
|
- Minimize custom code.
|
||||||
|
- Preserve platform consistency, security, and tenant isolation.
|
||||||
|
|
||||||
|
Primary principle: Configuration first, code last.
|
||||||
|
|
||||||
|
## Mandatory Decision Order
|
||||||
|
|
||||||
|
For every request, evaluate and propose in this order:
|
||||||
|
|
||||||
|
1. Dynamic configuration with existing ListForm ecosystem
|
||||||
|
2. SQL Query Manager + Custom Endpoint
|
||||||
|
3. Dynamic Service
|
||||||
|
4. Code change (last resort, justification required)
|
||||||
|
|
||||||
|
## Non-Negotiable Rules
|
||||||
|
|
||||||
|
1. Do not propose new custom React component/page development for standard feature requests.
|
||||||
|
2. Build new screens using platform configuration mechanisms.
|
||||||
|
3. Every proposal must include tenant and permission design.
|
||||||
|
4. Never bypass platform authorization patterns.
|
||||||
|
5. Never hardcode secrets, tenant IDs, or connection strings.
|
||||||
|
|
||||||
|
Exception:
|
||||||
|
|
||||||
|
- Custom React/backend code is allowed only when the user explicitly requests implementation and configuration is insufficient.
|
||||||
|
- In such cases, explain why configuration-first options are not enough.
|
||||||
|
|
||||||
|
## Architecture Guardrails
|
||||||
|
|
||||||
|
- Backend: Respect ABP module boundaries and explicit, auditable permissions.
|
||||||
|
- Frontend: Use existing dynamic view infrastructure and metadata-driven behavior.
|
||||||
|
- Data: Use parameterized SQL patterns and enforce tenant-safe access.
|
||||||
|
|
||||||
|
## Dynamic Platform Expectations
|
||||||
|
|
||||||
|
- Menus and routes are database-driven.
|
||||||
|
- Dynamic List/Form/Component infrastructure is the default solution path.
|
||||||
|
- Keep route, menu, permission, and datasource mappings coherent.
|
||||||
|
|
||||||
|
## Security and Compliance
|
||||||
|
|
||||||
|
- Enforce RBAC and permission-driven visibility in all layers.
|
||||||
|
- Never output real credentials, tokens, keys, or secrets.
|
||||||
|
- Use placeholders in examples.
|
||||||
|
- Maintain tenant isolation in every query and action.
|
||||||
|
|
||||||
|
## Response Contract
|
||||||
|
|
||||||
|
When producing an implementation proposal, include:
|
||||||
|
|
||||||
|
1. Goal
|
||||||
|
2. Decision flow result (which step used)
|
||||||
|
3. Artifacts to configure
|
||||||
|
4. SQL/query/endpoint design (if needed)
|
||||||
|
5. Menu + route + component mapping
|
||||||
|
6. Permission and role mapping
|
||||||
|
7. Tenant isolation notes
|
||||||
|
8. Validation and test checklist
|
||||||
|
9. Rollback strategy
|
||||||
194
configs/ai/Chat.json
Normal file
194
configs/ai/Chat.json
Normal file
|
|
@ -0,0 +1,194 @@
|
||||||
|
{
|
||||||
|
"name": "Chat",
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"multipleMethods": true,
|
||||||
|
"httpMethod": [
|
||||||
|
"POST"
|
||||||
|
],
|
||||||
|
"path": "chat",
|
||||||
|
"responseMode": "lastNode",
|
||||||
|
"responseData": "allEntries",
|
||||||
|
"options": {}
|
||||||
|
},
|
||||||
|
"type": "n8n-nodes-base.webhook",
|
||||||
|
"typeVersion": 2,
|
||||||
|
"position": [
|
||||||
|
-704,
|
||||||
|
224
|
||||||
|
],
|
||||||
|
"id": "5624c28a-5e8f-4fd9-88ac-bc9a630a9cc0",
|
||||||
|
"name": "Webhook",
|
||||||
|
"webhookId": "562dfd4f-4e0b-4292-9986-2fbd3b8ecdc9",
|
||||||
|
"alwaysOutputData": true,
|
||||||
|
"executeOnce": false,
|
||||||
|
"retryOnFail": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"promptType": "define",
|
||||||
|
"text": "={{ $('Webhook').item.json.body.question }}",
|
||||||
|
"options": {
|
||||||
|
"systemMessage": "Kullanıcı: {{ $json.body.question }}\n\nDoğrudan yanıt ver. Açıklayıcı, öğretici ve samimi ol."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": "@n8n/n8n-nodes-langchain.agent",
|
||||||
|
"typeVersion": 1.8,
|
||||||
|
"position": [
|
||||||
|
-48,
|
||||||
|
224
|
||||||
|
],
|
||||||
|
"id": "f5a4eb80-6e3f-47a3-9e42-069fb6992a8b",
|
||||||
|
"name": "AI Chat",
|
||||||
|
"alwaysOutputData": false,
|
||||||
|
"onError": "continueErrorOutput"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"options": {}
|
||||||
|
},
|
||||||
|
"type": "@n8n/n8n-nodes-langchain.memoryManager",
|
||||||
|
"typeVersion": 1.1,
|
||||||
|
"position": [
|
||||||
|
-448,
|
||||||
|
224
|
||||||
|
],
|
||||||
|
"id": "f008f540-216b-42f3-815c-b17658b4c7ad",
|
||||||
|
"name": "Chat Memory Manager",
|
||||||
|
"alwaysOutputData": false,
|
||||||
|
"disabled": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"sessionIdType": "customKey",
|
||||||
|
"sessionKey": "={{ $('Webhook').item.json.body.sessionId }}",
|
||||||
|
"contextWindowLength": 100
|
||||||
|
},
|
||||||
|
"type": "@n8n/n8n-nodes-langchain.memoryBufferWindow",
|
||||||
|
"typeVersion": 1.3,
|
||||||
|
"position": [
|
||||||
|
-304,
|
||||||
|
480
|
||||||
|
],
|
||||||
|
"id": "80c637b6-06e1-41b1-a946-84065a9e327a",
|
||||||
|
"name": "Simple Memory"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"options": {}
|
||||||
|
},
|
||||||
|
"type": "@n8n/n8n-nodes-langchain.lmChatGoogleGemini",
|
||||||
|
"typeVersion": 1,
|
||||||
|
"position": [
|
||||||
|
-48,
|
||||||
|
480
|
||||||
|
],
|
||||||
|
"id": "08879cec-78df-4bd7-a8aa-f0ec6fc5e0ae",
|
||||||
|
"name": "Google Gemini Chat Model",
|
||||||
|
"credentials": {
|
||||||
|
"googlePalmApi": {
|
||||||
|
"id": "2y8O0r5mQKG5cdcc",
|
||||||
|
"name": "Google Gemini(PaLM) Api account"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"jsCode": "const cleanText = (text) =>\n text\n .replace(/\\\\/g, \"\\\\\\\\\") // ters slash\n .replace(/\"/g, '\\\\\"'); // çift tırnak\n\nreturn [\n {\n json: {\n type: \"chat\",\n question: $('Webhook').first().json.body.question,\n sql: null,\n answer: cleanText($input.first().json.output),\n chart: null,\n error: $input.first().json.error || null\n }\n }\n];\n\n"
|
||||||
|
},
|
||||||
|
"type": "n8n-nodes-base.code",
|
||||||
|
"typeVersion": 2,
|
||||||
|
"position": [
|
||||||
|
560,
|
||||||
|
224
|
||||||
|
],
|
||||||
|
"id": "7f66cfbf-d19e-421b-92c3-d9a1909c1434",
|
||||||
|
"name": "Chat Code",
|
||||||
|
"alwaysOutputData": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"pinData": {},
|
||||||
|
"connections": {
|
||||||
|
"Webhook": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Chat Memory Manager",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"AI Chat": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Chat Code",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Chat Code",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Chat Memory Manager": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "AI Chat",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Simple Memory": {
|
||||||
|
"ai_memory": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "AI Chat",
|
||||||
|
"type": "ai_memory",
|
||||||
|
"index": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"node": "Chat Memory Manager",
|
||||||
|
"type": "ai_memory",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Google Gemini Chat Model": {
|
||||||
|
"ai_languageModel": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "AI Chat",
|
||||||
|
"type": "ai_languageModel",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"active": true,
|
||||||
|
"settings": {
|
||||||
|
"executionOrder": "v1",
|
||||||
|
"availableInMCP": false
|
||||||
|
},
|
||||||
|
"versionId": "84010ee3-81a9-48c8-9b48-a0661355d85c",
|
||||||
|
"meta": {
|
||||||
|
"templateCredsSetupCompleted": true,
|
||||||
|
"instanceId": "1d288821beaaeeada5e8dce6f282c802098a0c83ef6ddb35a174b09a9d43850e"
|
||||||
|
},
|
||||||
|
"id": "R1V2XtEQCknLJvSkwUW6s",
|
||||||
|
"tags": []
|
||||||
|
}
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -15,7 +15,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.drawer-body {
|
.drawer-body {
|
||||||
@apply p-6 h-full overflow-y-auto;
|
@apply p-4 h-full overflow-y-auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.drawer-footer {
|
.drawer-footer {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { ComponentInfo } from '../../proxy/developerKit/componentInfo';
|
import { ComponentInfo } from '../../proxy/developerKit/componentInfo';
|
||||||
|
import { Button } from '../ui';
|
||||||
|
|
||||||
interface ComponentSelectorProps {
|
interface ComponentSelectorProps {
|
||||||
components: ComponentInfo[];
|
components: ComponentInfo[];
|
||||||
|
|
@ -20,13 +21,15 @@ const ComponentSelector: React.FC<ComponentSelectorProps> = ({
|
||||||
<label className="block text-sm font-medium text-gray-700">
|
<label className="block text-sm font-medium text-gray-700">
|
||||||
Select Component
|
Select Component
|
||||||
</label>
|
</label>
|
||||||
<button
|
<Button
|
||||||
|
variant='solid'
|
||||||
|
size="xs"
|
||||||
onClick={onRefresh}
|
onClick={onRefresh}
|
||||||
className="px-3 py-1 bg-blue-500 text-white text-xs rounded hover:bg-blue-600 transition-colors"
|
className="px-3 py-1 bg-blue-500 text-white text-xs rounded hover:bg-blue-600 transition-colors"
|
||||||
title="Refresh component list"
|
title="Refresh component list"
|
||||||
>
|
>
|
||||||
Refresh
|
Refresh
|
||||||
</button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<select
|
<select
|
||||||
value={selectedComponentId || ''}
|
value={selectedComponentId || ''}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import { useState, useEffect } from "react";
|
||||||
import TailwindModal from "./TailwindModal";
|
import TailwindModal from "./TailwindModal";
|
||||||
import { ComponentInfo, HookInfo, PropertyInfo } from "../../proxy/developerKit/componentInfo";
|
import { ComponentInfo, HookInfo, PropertyInfo } from "../../proxy/developerKit/componentInfo";
|
||||||
import { getComponentDefinition } from "./data/componentDefinitions";
|
import { getComponentDefinition } from "./data/componentDefinitions";
|
||||||
|
import { Button } from "../ui";
|
||||||
|
|
||||||
interface PropertyPanelProps {
|
interface PropertyPanelProps {
|
||||||
selectedComponent: ComponentInfo | null;
|
selectedComponent: ComponentInfo | null;
|
||||||
|
|
@ -525,7 +526,9 @@ const PropertyPanel: React.FC<PropertyPanelProps> = ({
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/* Sil Butonu */}
|
{/* Sil Butonu */}
|
||||||
<button
|
<Button
|
||||||
|
variant="solid"
|
||||||
|
size="xs"
|
||||||
className="mr-2 px-3 py-1 rounded bg-red-500 text-white hover:bg-red-600 transition-colors text-sm"
|
className="mr-2 px-3 py-1 rounded bg-red-500 text-white hover:bg-red-600 transition-colors text-sm"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (selectedComponent) {
|
if (selectedComponent) {
|
||||||
|
|
@ -541,28 +544,32 @@ const PropertyPanel: React.FC<PropertyPanelProps> = ({
|
||||||
title="Komponenti Sil"
|
title="Komponenti Sil"
|
||||||
>
|
>
|
||||||
Sil
|
Sil
|
||||||
</button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Footer Action Buttons - her iki tabda da sabit */}
|
{/* Footer Action Buttons - her iki tabda da sabit */}
|
||||||
<div className="p-4 border-t">
|
<div className="p-4 border-t">
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<button
|
<Button
|
||||||
|
size="xs"
|
||||||
|
variant="solid"
|
||||||
onClick={
|
onClick={
|
||||||
activeTab === "props"
|
activeTab === "props"
|
||||||
? handleApplyPropChanges
|
? handleApplyPropChanges
|
||||||
: handleApplyHookChanges
|
: handleApplyHookChanges
|
||||||
}
|
}
|
||||||
disabled={activeTab === "props" ? !hasChanges : !hasHookChanges}
|
disabled={activeTab === "props" ? !hasChanges : !hasHookChanges}
|
||||||
className={`flex-1 px-4 py-2 rounded-md font-medium transition-colors ${
|
className={`flex-1 rounded-md font-medium transition-colors ${
|
||||||
(activeTab === "props" ? hasChanges : hasHookChanges)
|
(activeTab === "props" ? hasChanges : hasHookChanges)
|
||||||
? "bg-green-500 text-white hover:bg-green-600"
|
? "bg-green-500 text-white hover:bg-green-600"
|
||||||
: "bg-gray-300 text-gray-500 cursor-not-allowed"
|
: "bg-gray-300 text-gray-500 cursor-not-allowed"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
Uygula
|
Uygula
|
||||||
</button>
|
</Button>
|
||||||
<button
|
<Button
|
||||||
|
size="xs"
|
||||||
|
variant="default"
|
||||||
onClick={
|
onClick={
|
||||||
activeTab === "props"
|
activeTab === "props"
|
||||||
? handleResetChanges
|
? handleResetChanges
|
||||||
|
|
@ -572,20 +579,20 @@ const PropertyPanel: React.FC<PropertyPanelProps> = ({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
disabled={activeTab === "props" ? !hasChanges : !hasHookChanges}
|
disabled={activeTab === "props" ? !hasChanges : !hasHookChanges}
|
||||||
className={`flex-1 px-4 py-2 rounded-md font-medium transition-colors ${
|
className={`flex-1 rounded-md font-medium transition-colors ${
|
||||||
(activeTab === "props" ? hasChanges : hasHookChanges)
|
(activeTab === "props" ? hasChanges : hasHookChanges)
|
||||||
? "bg-red-500 text-white hover:bg-red-600"
|
? "bg-red-500 text-white hover:bg-red-600"
|
||||||
: "bg-gray-300 text-gray-500 cursor-not-allowed"
|
: "bg-gray-300 text-gray-500 cursor-not-allowed"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
İptal
|
İptal
|
||||||
</button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Content */}
|
{/* Content */}
|
||||||
{activeTab === "props" && (
|
{activeTab === "props" && (
|
||||||
<div className="flex-1 overflow-y-auto p-4 max-h-[calc(100vh-200px)]">
|
<div className="flex-1 text-black overflow-y-auto p-4 max-h-[calc(100vh-200px)]">
|
||||||
<h3 className="text-md font-medium text-gray-800 mb-4">Properties</h3>
|
<h3 className="text-md font-medium text-gray-800 mb-4">Properties</h3>
|
||||||
{/* Properties */}
|
{/* Properties */}
|
||||||
{properties.length > 0 && (
|
{properties.length > 0 && (
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { searchTailwindClasses, TAILWIND_CLASSES } from './data/tailwindClasses';
|
import { searchTailwindClasses, TAILWIND_CLASSES } from './data/tailwindClasses';
|
||||||
|
import { Button } from '../ui';
|
||||||
|
|
||||||
interface TailwindModalProps {
|
interface TailwindModalProps {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
|
|
@ -61,7 +62,7 @@ const TailwindModal: React.FC<TailwindModalProps> = ({
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Search and Filter */}
|
{/* Search and Filter */}
|
||||||
<div className="p-4 border-b bg-gray-50">
|
<div className="p-4 border-b bg-gray-50 text-black">
|
||||||
<div className="flex gap-4 mb-4">
|
<div className="flex gap-4 mb-4">
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
|
|
@ -106,25 +107,15 @@ const TailwindModal: React.FC<TailwindModalProps> = ({
|
||||||
key={`${className}-${index}`}
|
key={`${className}-${index}`}
|
||||||
className="group relative"
|
className="group relative"
|
||||||
>
|
>
|
||||||
<button
|
<Button
|
||||||
|
variant='default'
|
||||||
onClick={() => handleClassSelect(className)}
|
onClick={() => handleClassSelect(className)}
|
||||||
className="w-full text-left px-3 py-2 text-sm bg-gray-100 hover:bg-blue-100 rounded border hover:border-blue-300 transition-colors"
|
className="w-full text-left px-3 py-2 text-sm bg-gray-100 hover:bg-blue-100 rounded border hover:border-blue-300 transition-colors"
|
||||||
>
|
>
|
||||||
<span className="font-mono text-xs text-gray-600">
|
<span className="font-mono text-xs text-gray-600">
|
||||||
{className}
|
{className}
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</Button>
|
||||||
|
|
||||||
{/* Add to current value button */}
|
|
||||||
{currentValue && (
|
|
||||||
<button
|
|
||||||
onClick={() => handleAddToCurrentValue(className)}
|
|
||||||
className="absolute top-1 right-1 w-6 h-6 bg-blue-500 text-white rounded-full text-xs opacity-0 group-hover:opacity-100 transition-opacity hover:bg-blue-600"
|
|
||||||
title="Add to current value"
|
|
||||||
>
|
|
||||||
+
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -137,18 +128,20 @@ const TailwindModal: React.FC<TailwindModalProps> = ({
|
||||||
{filteredClasses.length} classes found
|
{filteredClasses.length} classes found
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<button
|
<Button
|
||||||
|
variant='default'
|
||||||
onClick={() => onSelectClass('')}
|
onClick={() => onSelectClass('')}
|
||||||
className="px-4 py-2 bg-gray-200 text-gray-700 rounded hover:bg-gray-300 transition-colors"
|
className="px-4 py-2 bg-gray-200 text-gray-700 rounded hover:bg-gray-300 transition-colors"
|
||||||
>
|
>
|
||||||
Clear
|
Clear
|
||||||
</button>
|
</Button>
|
||||||
<button
|
<Button
|
||||||
|
variant='solid'
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600 transition-colors"
|
className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600 transition-colors"
|
||||||
>
|
>
|
||||||
Close
|
Close
|
||||||
</button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -283,7 +283,7 @@ export const ImportDashboard: React.FC<ImportDashboardProps> = ({ gridDto }) =>
|
||||||
<thead className="bg-slate-100 sticky top-0">
|
<thead className="bg-slate-100 sticky top-0">
|
||||||
<tr>
|
<tr>
|
||||||
<th className="px-4 py-2 text-left text-xs font-medium text-slate-500 uppercase">
|
<th className="px-4 py-2 text-left text-xs font-medium text-slate-500 uppercase">
|
||||||
{translate('::App.Listforms.ImportManager.Column')}
|
{translate('::App.Listform.ListformField.Column')}
|
||||||
</th>
|
</th>
|
||||||
<th className="px-4 py-2 text-left text-xs font-medium text-slate-500 uppercase">
|
<th className="px-4 py-2 text-left text-xs font-medium text-slate-500 uppercase">
|
||||||
{translate('::ListForms.ListFormEdit.Type')}
|
{translate('::ListForms.ListFormEdit.Type')}
|
||||||
|
|
|
||||||
|
|
@ -60,6 +60,11 @@ const Layout = () => {
|
||||||
}, [routes, currentPath])
|
}, [routes, currentPath])
|
||||||
|
|
||||||
const AppLayout = useMemo(() => {
|
const AppLayout = useMemo(() => {
|
||||||
|
// 0) Setup path ise minimal blank layout
|
||||||
|
if (currentPath === '/setup') {
|
||||||
|
return layouts[LAYOUT_TYPE_BLANK]
|
||||||
|
}
|
||||||
|
|
||||||
// 1) Admin path ise, route bulunmasa bile admin layout'u göster
|
// 1) Admin path ise, route bulunmasa bile admin layout'u göster
|
||||||
if (isAdminPath) {
|
if (isAdminPath) {
|
||||||
return layouts[layoutType]
|
return layouts[layoutType]
|
||||||
|
|
@ -78,7 +83,7 @@ const Layout = () => {
|
||||||
return AuthLayout
|
return AuthLayout
|
||||||
}
|
}
|
||||||
return PublicLayout
|
return PublicLayout
|
||||||
}, [isAdminPath, route, layoutType, authenticated])
|
}, [isAdminPath, route, layoutType, authenticated, currentPath])
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
|
|
|
||||||
|
|
@ -155,7 +155,7 @@ export const Cart: React.FC<CartProps> = ({
|
||||||
<div className="border-t border-gray-200 p-6">
|
<div className="border-t border-gray-200 p-6">
|
||||||
<div className="flex justify-between items-center mb-4">
|
<div className="flex justify-between items-center mb-4">
|
||||||
<span className="text-lg font-semibold text-gray-900">
|
<span className="text-lg font-semibold text-gray-900">
|
||||||
{translate('::Public.payment.summary.total')}
|
{translate('::App.Listform.ListformField.Total')}
|
||||||
</span>
|
</span>
|
||||||
<span className="text-xl font-bold text-blue-600">
|
<span className="text-xl font-bold text-blue-600">
|
||||||
{formatPrice(cartState.total)}
|
{formatPrice(cartState.total)}
|
||||||
|
|
|
||||||
|
|
@ -436,7 +436,7 @@ export const PaymentForm: React.FC<PaymentFormProps> = ({
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="flex justify-between text-base font-bold pt-2 text-gray-900">
|
<div className="flex justify-between text-base font-bold pt-2 text-gray-900">
|
||||||
<span>{translate('::Public.payment.summary.total')}</span>
|
<span>{translate('::App.Listform.ListformField.Total')}</span>
|
||||||
<span className="text-blue-600">{formatPrice(finalTotal)}</span>
|
<span className="text-blue-600">{formatPrice(finalTotal)}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -449,7 +449,7 @@ export const PaymentForm: React.FC<PaymentFormProps> = ({
|
||||||
className="flex items-center px-6 py-3 border border-gray-300 text-gray-700 rounded-lg"
|
className="flex items-center px-6 py-3 border border-gray-300 text-gray-700 rounded-lg"
|
||||||
>
|
>
|
||||||
<FaArrowLeft className="w-4 h-4 mr-2" />
|
<FaArrowLeft className="w-4 h-4 mr-2" />
|
||||||
{translate('::Public.payment.buttons.back')}
|
{translate('::Back')}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
|
|
|
||||||
|
|
@ -154,7 +154,7 @@ export const ProductCard: React.FC<ProductCardProps> = ({
|
||||||
</div>
|
</div>
|
||||||
{globalPeriod > 1 && (
|
{globalPeriod > 1 && (
|
||||||
<div className="text-lg font-semibold text-blue-600 mt-1">
|
<div className="text-lg font-semibold text-blue-600 mt-1">
|
||||||
{translate('::Public.payment.summary.total')} {formatPrice(getTotalPrice())}
|
{translate('::App.Listform.ListformField.Total')} {formatPrice(getTotalPrice())}
|
||||||
<span className="text-sm font-normal text-gray-500 ml-1">{getPeriodText()}</span>
|
<span className="text-sm font-normal text-gray-500 ml-1">{getPeriodText()}</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -479,7 +479,7 @@ export const TenantForm: React.FC<TenantFormProps> = ({ onSubmit }) => {
|
||||||
className="flex items-center px-6 py-3 border border-gray-300 text-gray-700 rounded-lg"
|
className="flex items-center px-6 py-3 border border-gray-300 text-gray-700 rounded-lg"
|
||||||
>
|
>
|
||||||
<FaArrowLeft className="w-4 h-4 mr-2" />
|
<FaArrowLeft className="w-4 h-4 mr-2" />
|
||||||
{translate('::Public.payment.buttons.back')}
|
{translate('::Back')}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
import Tooltip from '@/components/ui/Tooltip'
|
import Tooltip from '@/components/ui/Tooltip'
|
||||||
|
import { AI_ASSISTANT } from '@/constants/permission.constant'
|
||||||
import { ROUTES_ENUM } from '@/routes/route.constant'
|
import { ROUTES_ENUM } from '@/routes/route.constant'
|
||||||
import { useLocalization } from '@/utils/hooks/useLocalization'
|
import { useLocalization } from '@/utils/hooks/useLocalization'
|
||||||
import { usePermission } from '@/utils/hooks/usePermission'
|
import { usePermission } from '@/utils/hooks/usePermission'
|
||||||
import { FcAssistant, FcHeadset } from 'react-icons/fc'
|
import { FcHeadset } from 'react-icons/fc'
|
||||||
import { useNavigate } from 'react-router-dom'
|
import { useNavigate } from 'react-router-dom'
|
||||||
|
|
||||||
const AiAssistant = () => {
|
const AiAssistant = () => {
|
||||||
|
|
@ -10,14 +11,14 @@ const AiAssistant = () => {
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const { checkPermissions } = usePermission()
|
const { checkPermissions } = usePermission()
|
||||||
|
|
||||||
const canViewAi = checkPermissions(['App.AiBot.Asistant'])
|
const canViewAi = checkPermissions([AI_ASSISTANT])
|
||||||
|
|
||||||
if (!canViewAi) {
|
if (!canViewAi) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tooltip title={translate('::App.AiBot.Asistant')}>
|
<Tooltip title={translate('::' + AI_ASSISTANT)}>
|
||||||
<div
|
<div
|
||||||
onClick={() => navigate(ROUTES_ENUM.protected.admin.ai)}
|
onClick={() => navigate(ROUTES_ENUM.protected.admin.ai)}
|
||||||
className="flex items-center justify-center text-2xl m-1 rounded-full hover:bg-gray-100 dark:hover:bg-gray-700 cursor-pointer transition-colors duration-200"
|
className="flex items-center justify-center text-2xl m-1 rounded-full hover:bg-gray-100 dark:hover:bg-gray-700 cursor-pointer transition-colors duration-200"
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import classNames from 'classnames'
|
import classNames from 'classnames'
|
||||||
import Drawer from '@/components/ui/Drawer'
|
import Drawer from '@/components/ui/Drawer'
|
||||||
import SidePanelContent, { SidePanelContentProps } from './SidePanelContent'
|
import SidePanelContent, { SidePanelContentProps } from './SidePanelContent'
|
||||||
|
import CopyButton from '../ThemeConfigurator/CopyButton'
|
||||||
import withHeaderItem from '@/utils/hoc/withHeaderItem'
|
import withHeaderItem from '@/utils/hoc/withHeaderItem'
|
||||||
import { useStoreState, useStoreActions } from '@/store'
|
import { useStoreState, useStoreActions } from '@/store'
|
||||||
import type { CommonProps } from '@/proxy/common'
|
import type { CommonProps } from '@/proxy/common'
|
||||||
|
|
@ -41,7 +42,12 @@ const _SidePanel = (props: SidePanelProps) => {
|
||||||
</div>
|
</div>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Drawer
|
<Drawer
|
||||||
title={translate('::SidePanel.Title')}
|
title={
|
||||||
|
<div className="flex items-center justify-between gap-x-2">
|
||||||
|
<h4>{translate('::SidePanel.Title')}</h4>
|
||||||
|
<CopyButton />
|
||||||
|
</div>
|
||||||
|
}
|
||||||
isOpen={panelExpand}
|
isOpen={panelExpand}
|
||||||
placement={direction === 'rtl' ? 'left' : 'right'}
|
placement={direction === 'rtl' ? 'left' : 'right'}
|
||||||
width={375}
|
width={375}
|
||||||
|
|
|
||||||
|
|
@ -8,18 +8,43 @@ import { useSetting } from '@/utils/hooks/useSetting'
|
||||||
import useTabFocus from '@/utils/hooks/useTabFocus'
|
import useTabFocus from '@/utils/hooks/useTabFocus'
|
||||||
import { useEffect } from 'react'
|
import { useEffect } from 'react'
|
||||||
import { Helmet } from 'react-helmet'
|
import { Helmet } from 'react-helmet'
|
||||||
|
import { useNavigate, useLocation } from 'react-router-dom'
|
||||||
|
import { getSetupStatus } from '@/services/setup.service'
|
||||||
|
import { ROUTES_ENUM } from '@/routes/route.constant'
|
||||||
|
|
||||||
let didInit = false
|
let didInit = false
|
||||||
|
|
||||||
const Theme = (props: CommonProps) => {
|
const Theme = (props: CommonProps) => {
|
||||||
// ABP App Config'i uygulama acilirken al
|
// ABP App Config'i uygulama acilirken al
|
||||||
const { getConfig } = useStoreActions((a) => a.abpConfig)
|
const { getConfig } = useStoreActions((a) => a.abpConfig)
|
||||||
|
const { setSetupMode } = useStoreActions((a) => a.base.common)
|
||||||
|
const navigate = useNavigate()
|
||||||
|
const location = useLocation()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!didInit) {
|
if (!didInit) {
|
||||||
didInit = true
|
didInit = true
|
||||||
|
|
||||||
|
// Direkt /setup'a gelindiyse — hemen setupMode=true yap (loading gate açılsın)
|
||||||
|
if (location.pathname === ROUTES_ENUM.setup) {
|
||||||
|
setSetupMode(true)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Veritabanı var mı kontrol et; yoksa setup sayfasına yönlendir
|
||||||
|
getSetupStatus()
|
||||||
|
.then((res) => {
|
||||||
|
if (!res.data.dbExists) {
|
||||||
|
setSetupMode(true)
|
||||||
|
navigate(ROUTES_ENUM.setup, { replace: true })
|
||||||
|
} else {
|
||||||
getConfig(false)
|
getConfig(false)
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
getConfig(false)
|
||||||
|
})
|
||||||
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
function getThemeStyle() {
|
function getThemeStyle() {
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,12 @@ import Button from '@/components/ui/Button'
|
||||||
import toast from '@/components/ui/toast'
|
import toast from '@/components/ui/toast'
|
||||||
import { themeConfig } from '@/proxy/theme/theme.config'
|
import { themeConfig } from '@/proxy/theme/theme.config'
|
||||||
import { useStoreState } from '@/store'
|
import { useStoreState } from '@/store'
|
||||||
|
import { FaSave } from 'react-icons/fa'
|
||||||
|
import { useLocalization } from '@/utils/hooks/useLocalization'
|
||||||
|
|
||||||
const CopyButton = () => {
|
const CopyButton = () => {
|
||||||
const theme = useStoreState((state) => state.theme)
|
const theme = useStoreState((state) => state.theme)
|
||||||
|
const { translate } = useLocalization()
|
||||||
|
|
||||||
const handleCopy = () => {
|
const handleCopy = () => {
|
||||||
const config = {
|
const config = {
|
||||||
|
|
@ -31,9 +34,14 @@ const CopyButton = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button block variant="solid" onClick={handleCopy}>
|
<Button
|
||||||
Copy config
|
shape="circle"
|
||||||
</Button>
|
variant="plain"
|
||||||
|
size="xs"
|
||||||
|
onClick={handleCopy}
|
||||||
|
title={translate('::SidePanel.SaveConfig')}
|
||||||
|
icon={<FaSave />}
|
||||||
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ import {
|
||||||
LAYOUT_TYPE_SIMPLE,
|
LAYOUT_TYPE_SIMPLE,
|
||||||
LAYOUT_TYPE_DECKED,
|
LAYOUT_TYPE_DECKED,
|
||||||
LAYOUT_TYPE_BLANK,
|
LAYOUT_TYPE_BLANK,
|
||||||
|
NAV_MODE_TRANSPARENT,
|
||||||
} from '@/constants/theme.constant'
|
} from '@/constants/theme.constant'
|
||||||
import type { LayoutType } from '@/proxy/theme/models'
|
import type { LayoutType } from '@/proxy/theme/models'
|
||||||
import { useLocalization } from '@/utils/hooks/useLocalization'
|
import { useLocalization } from '@/utils/hooks/useLocalization'
|
||||||
|
|
@ -57,7 +58,7 @@ const layouts = [
|
||||||
|
|
||||||
const LayoutSwitcher = () => {
|
const LayoutSwitcher = () => {
|
||||||
const type = useStoreState((state) => state.theme.layout.type)
|
const type = useStoreState((state) => state.theme.layout.type)
|
||||||
const { setLayout } = useStoreActions((actions) => actions.theme)
|
const { setLayout, setNavMode } = useStoreActions((actions) => actions.theme)
|
||||||
|
|
||||||
const onLayoutSelect = (val: LayoutType) => {
|
const onLayoutSelect = (val: LayoutType) => {
|
||||||
setLayout(val)
|
setLayout(val)
|
||||||
|
|
|
||||||
|
|
@ -1,23 +1,31 @@
|
||||||
import Radio from '@/components/ui/Radio'
|
import Radio from '@/components/ui/Radio'
|
||||||
import { useStoreState, useStoreActions } from '@/store'
|
import { useStoreState, useStoreActions } from '@/store'
|
||||||
import { NAV_MODE_THEMED } from '@/constants/theme.constant'
|
import {
|
||||||
|
NAV_MODE_DARK,
|
||||||
type NavModeParam = 'default' | 'themed'
|
NAV_MODE_LIGHT,
|
||||||
|
NAV_MODE_THEMED,
|
||||||
|
NAV_MODE_TRANSPARENT,
|
||||||
|
} from '@/constants/theme.constant'
|
||||||
|
import { NavMode } from '@/proxy/theme/models'
|
||||||
|
import { availableNavColorLayouts } from '@/proxy/theme/theme.config'
|
||||||
|
|
||||||
const NavModeSwitcher = () => {
|
const NavModeSwitcher = () => {
|
||||||
const navMode = useStoreState((state) => state.theme.navMode)
|
const { navMode, layout } = useStoreState((state) => state.theme)
|
||||||
const { setNavMode } = useStoreActions((actions) => actions.theme)
|
const { setNavMode } = useStoreActions((actions) => actions.theme)
|
||||||
|
|
||||||
const onSetNavMode = (val: NavModeParam) => {
|
const onSetNavMode = (val: NavMode) => {
|
||||||
setNavMode(val)
|
setNavMode(val)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Radio.Group
|
<Radio.Group value={navMode} onChange={onSetNavMode}>
|
||||||
value={navMode === NAV_MODE_THEMED ? NAV_MODE_THEMED : 'default'}
|
<Radio value={NAV_MODE_TRANSPARENT}>Transparent</Radio>
|
||||||
onChange={onSetNavMode}
|
{!availableNavColorLayouts.includes(layout.type) && (
|
||||||
>
|
<>
|
||||||
<Radio value="default">Default</Radio>
|
<Radio value={NAV_MODE_LIGHT}>Light</Radio>
|
||||||
|
<Radio value={NAV_MODE_DARK}>Dark</Radio>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
<Radio value={NAV_MODE_THEMED}>Themed</Radio>
|
<Radio value={NAV_MODE_THEMED}>Themed</Radio>
|
||||||
</Radio.Group>
|
</Radio.Group>
|
||||||
)
|
)
|
||||||
|
|
|
||||||
124
ui/src/components/template/ThemeConfigurator/StyleSwitcher.tsx
Normal file
124
ui/src/components/template/ThemeConfigurator/StyleSwitcher.tsx
Normal file
|
|
@ -0,0 +1,124 @@
|
||||||
|
import { HiCheck } from 'react-icons/hi'
|
||||||
|
import { components } from 'react-select'
|
||||||
|
|
||||||
|
import { Select, toast } from '@/components/ui'
|
||||||
|
import { styleMapOptions } from '@/views/admin/listForm/edit/options'
|
||||||
|
import Notification from '@/components/ui/Notification'
|
||||||
|
import { useLocalization } from '@/utils/hooks/useLocalization'
|
||||||
|
import { useStoreActions, useStoreState } from '@/store'
|
||||||
|
import { updateSettingValues } from '@/services/setting-ui.service'
|
||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
const StyleSwitcher = ({ onStyleChange }: { onStyleChange?: () => void }) => {
|
||||||
|
const { translate } = useLocalization()
|
||||||
|
const { setMode, setStyle, setThemeColor, setThemeColorLevel, abpConfig } = useStoreActions(
|
||||||
|
(actions) => ({
|
||||||
|
setMode: actions.theme.setMode,
|
||||||
|
setStyle: actions.theme.setStyle,
|
||||||
|
setThemeColor: actions.theme.setThemeColor,
|
||||||
|
setThemeColorLevel: actions.theme.setThemeColorLevel,
|
||||||
|
abpConfig: actions.abpConfig.getConfig,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
const { style, themeColor, primaryColorLevel } = useStoreState((state) => state.theme)
|
||||||
|
|
||||||
|
const onSetStyle = React.useCallback(
|
||||||
|
async (val: any) => {
|
||||||
|
setStyle(val.value)
|
||||||
|
setMode(val.value.includes('.dark') ? 'dark' : 'light')
|
||||||
|
setThemeColor(val.color?.color)
|
||||||
|
setThemeColorLevel(val.color?.colorLevel)
|
||||||
|
|
||||||
|
//Update setting value
|
||||||
|
const values: Record<string, string> = {
|
||||||
|
App_SiteManagement_Theme_Style: val.value,
|
||||||
|
}
|
||||||
|
|
||||||
|
const resp = await updateSettingValues(values)
|
||||||
|
if (resp.status !== 204) {
|
||||||
|
toast.push(<Notification title={resp?.error?.message} type="danger" />, {
|
||||||
|
placement: 'top-end',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
abpConfig(false)
|
||||||
|
if (onStyleChange) onStyleChange()
|
||||||
|
},
|
||||||
|
[setStyle, setMode, abpConfig, translate, onStyleChange],
|
||||||
|
)
|
||||||
|
|
||||||
|
// Custom Option
|
||||||
|
const CustomSelectOption = ({ innerProps, label, data, isSelected }: any) => {
|
||||||
|
const { border, fill } = data.color
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={`flex items-center justify-between p-2 cursor-pointer ${
|
||||||
|
isSelected ? 'bg-gray-100 dark:bg-gray-500' : 'hover:bg-gray-50 dark:hover:bg-gray-600'
|
||||||
|
}`}
|
||||||
|
{...innerProps}
|
||||||
|
>
|
||||||
|
<div className="flex items-center">
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
display: 'inline-block',
|
||||||
|
width: 20,
|
||||||
|
height: 20,
|
||||||
|
borderRadius: '50%',
|
||||||
|
background: fill,
|
||||||
|
border: `4px solid ${border}`,
|
||||||
|
boxSizing: 'border-box',
|
||||||
|
marginRight: 8,
|
||||||
|
boxShadow: '0 0 0 1px #ccc', // dış 1px border
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<span>{label}</span>
|
||||||
|
</div>
|
||||||
|
{isSelected && <HiCheck className="text-emerald-500 text-xl" />}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Custom Control
|
||||||
|
const CustomControl = ({ children, ...props }: any) => {
|
||||||
|
const selected = props.getValue()[0]
|
||||||
|
const { border, fill } = selected?.color
|
||||||
|
return (
|
||||||
|
<components.Control {...props}>
|
||||||
|
{selected ? (
|
||||||
|
<div
|
||||||
|
className="flex items-center"
|
||||||
|
style={{ marginLeft: 8, border: '1px solid #ccc', borderRadius: '50%' }}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
display: 'inline-block',
|
||||||
|
width: 20,
|
||||||
|
height: 20,
|
||||||
|
borderRadius: '50%',
|
||||||
|
background: fill,
|
||||||
|
border: `4px solid ${border}`,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
{children}
|
||||||
|
</components.Control>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Select
|
||||||
|
value={styleMapOptions.find((o) => o.value === style)}
|
||||||
|
options={styleMapOptions}
|
||||||
|
onChange={(option: any) => {
|
||||||
|
onSetStyle(option)
|
||||||
|
}}
|
||||||
|
components={{
|
||||||
|
Option: CustomSelectOption,
|
||||||
|
Control: CustomControl,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default StyleSwitcher
|
||||||
|
|
@ -3,32 +3,30 @@ import LayoutSwitcher from './LayoutSwitcher'
|
||||||
import ThemeSwitcher from './ThemeSwitcher'
|
import ThemeSwitcher from './ThemeSwitcher'
|
||||||
import DirectionSwitcher from './DirectionSwitcher'
|
import DirectionSwitcher from './DirectionSwitcher'
|
||||||
import NavModeSwitcher from './NavModeSwitcher'
|
import NavModeSwitcher from './NavModeSwitcher'
|
||||||
import CopyButton from './CopyButton'
|
import StyleSwitcher from './StyleSwitcher'
|
||||||
|
import React, { useState, useCallback } from 'react'
|
||||||
import { useLocalization } from '@/utils/hooks/useLocalization'
|
import { useLocalization } from '@/utils/hooks/useLocalization'
|
||||||
|
|
||||||
export type ThemeConfiguratorProps = {
|
export type ThemeConfiguratorProps = {
|
||||||
callBackClose?: () => void
|
callBackClose?: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const ThemeConfigurator = ({ callBackClose }: ThemeConfiguratorProps) => {
|
const ThemeConfigurator = ({ callBackClose }: ThemeConfiguratorProps) => {
|
||||||
const { translate } = useLocalization()
|
const { translate } = useLocalization()
|
||||||
|
const [modeKey, setModeKey] = useState(0)
|
||||||
|
|
||||||
|
// StyleSwitcher'dan tetiklenecek
|
||||||
|
const handleStyleChange = useCallback(() => {
|
||||||
|
setModeKey((prev) => prev + 1)
|
||||||
|
}, [])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col h-full justify-between">
|
<div className="flex flex-col h-full justify-between">
|
||||||
<div className="flex flex-col gap-y-10 mb-6">
|
<div className="flex flex-col gap-y-3 mb-2">
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<div>
|
<div>
|
||||||
<h6>{translate('::SidePanel.Mode')}</h6>
|
<h6 className="mb-3">{translate('::App.SiteManagement.Theme.Style')}</h6>
|
||||||
<span>{translate('::SidePanel.Mode.Description')}</span>
|
<StyleSwitcher onStyleChange={handleStyleChange} />
|
||||||
</div>
|
|
||||||
<ModeSwitcher />
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<div>
|
|
||||||
<h6>{translate('::SidePanel.Direction')}</h6>
|
|
||||||
<span>{translate('::SidePanel.Direction.Description')}</span>
|
|
||||||
</div>
|
|
||||||
<DirectionSwitcher callBackClose={callBackClose} />
|
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h6 className="mb-3">{translate('::SidePanel.NavMode')}</h6>
|
<h6 className="mb-3">{translate('::SidePanel.NavMode')}</h6>
|
||||||
|
|
@ -38,12 +36,25 @@ const ThemeConfigurator = ({ callBackClose }: ThemeConfiguratorProps) => {
|
||||||
<h6 className="mb-3">{translate('::SidePanel.Themed')}</h6>
|
<h6 className="mb-3">{translate('::SidePanel.Themed')}</h6>
|
||||||
<ThemeSwitcher />
|
<ThemeSwitcher />
|
||||||
</div>
|
</div>
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div>
|
||||||
|
<h6>{translate('::SidePanel.Mode')}</h6>
|
||||||
|
<span>{translate('::SidePanel.Mode.Description')}</span>
|
||||||
|
</div>
|
||||||
|
<ModeSwitcher key={modeKey} />
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div>
|
||||||
|
<h6>{translate('::SidePanel.Direction')}</h6>
|
||||||
|
<span>{translate('::SidePanel.Direction.Description')}</span>
|
||||||
|
</div>
|
||||||
|
<DirectionSwitcher callBackClose={callBackClose} />
|
||||||
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h6 className="mb-3">{translate('::SidePanel.Layout')}</h6>
|
<h6 className="mb-3">{translate('::SidePanel.Layout')}</h6>
|
||||||
<LayoutSwitcher />
|
<LayoutSwitcher />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<CopyButton />
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ const colorList: ColorList[] = [
|
||||||
{ label: 'Green', value: 'green' },
|
{ label: 'Green', value: 'green' },
|
||||||
{ label: 'Emerald', value: 'emerald' },
|
{ label: 'Emerald', value: 'emerald' },
|
||||||
{ label: 'Teal', value: 'teal' },
|
{ label: 'Teal', value: 'teal' },
|
||||||
|
{ label: 'Gray', value: 'gray' },
|
||||||
{ label: 'Cyan', value: 'cyan' },
|
{ label: 'Cyan', value: 'cyan' },
|
||||||
{ label: 'Sky', value: 'sky' },
|
{ label: 'Sky', value: 'sky' },
|
||||||
{ label: 'Blue', value: 'blue' },
|
{ label: 'Blue', value: 'blue' },
|
||||||
|
|
@ -59,13 +60,13 @@ const ColorBadge = ({ className, themeColor }: { className?: string; themeColor:
|
||||||
const CustomSelectOption = ({ innerProps, label, data, isSelected }: OptionProps<ColorList>) => {
|
const CustomSelectOption = ({ innerProps, label, data, isSelected }: OptionProps<ColorList>) => {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`flex items-center justify-between p-2 ${
|
className={`flex items-center justify-between p-2 cursor-pointer ${
|
||||||
isSelected ? 'bg-gray-100 dark:bg-gray-500' : 'hover:bg-gray-50 dark:hover:bg-gray-600'
|
isSelected ? 'bg-gray-100 dark:bg-gray-500' : 'hover:bg-gray-50 dark:hover:bg-gray-600'
|
||||||
}`}
|
}`}
|
||||||
{...innerProps}
|
{...innerProps}
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<ColorBadge themeColor={data.value} />
|
<ColorBadge themeColor={data.value} className='p-3' />
|
||||||
<span>{label}</span>
|
<span>{label}</span>
|
||||||
</div>
|
</div>
|
||||||
{isSelected && <FaCheck className="text-emerald-500 text-xl" />}
|
{isSelected && <FaCheck className="text-emerald-500 text-xl" />}
|
||||||
|
|
@ -80,7 +81,7 @@ const CustomControl = ({ children, ...props }: ControlProps<ColorList>) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Control {...props}>
|
<Control {...props}>
|
||||||
{selected && <ColorBadge themeColor={themeColor} className="ltr:ml-4 rtl:mr-4" />}
|
{selected && <ColorBadge themeColor={themeColor} className="ml-2 p-3" />}
|
||||||
{children}
|
{children}
|
||||||
</Control>
|
</Control>
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -1 +1,2 @@
|
||||||
export const GLOBAL_SEARCH = 'App.Settings.GlobalSearch'
|
export const GLOBAL_SEARCH = 'App.Definitions.GlobalSearch'
|
||||||
|
export const AI_ASSISTANT = 'App.Definitions.AiBot.Asistant'
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
export const PREFIX = "App";
|
||||||
|
|
||||||
export const DIR_RTL = 'rtl'
|
export const DIR_RTL = 'rtl'
|
||||||
export const DIR_LTR = 'ltr'
|
export const DIR_LTR = 'ltr'
|
||||||
export const MODE_LIGHT = 'light'
|
export const MODE_LIGHT = 'light'
|
||||||
|
|
|
||||||
|
|
@ -98,3 +98,17 @@ div.dialog-after-open > div.dialog-content.maximized {
|
||||||
position: absolute !important;
|
position: absolute !important;
|
||||||
left: -9999px !important;
|
left: -9999px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dx-toolbar .dx-toolbar-after .dx-menu,
|
||||||
|
.dx-toolbar .dx-toolbar-after .dx-menu .dx-menu-item,
|
||||||
|
.dx-toolbar .dx-toolbar-after .dx-menu .dx-menu-item-content,
|
||||||
|
.dx-toolbar .dx-toolbar-after .dx-menu .dx-menu-item-wrapper {
|
||||||
|
display: flex !important;
|
||||||
|
align-items: center !important;
|
||||||
|
padding: 0 0 !important;
|
||||||
|
font-weight: bold !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dx-datagrid-header-panel {
|
||||||
|
padding: 0 0 !important;
|
||||||
|
}
|
||||||
|
|
@ -2,4 +2,25 @@ import { FullAuditedEntityDto } from '../abp'
|
||||||
|
|
||||||
export interface AiDto extends FullAuditedEntityDto<string> {
|
export interface AiDto extends FullAuditedEntityDto<string> {
|
||||||
name: string
|
name: string
|
||||||
|
apiUrl?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ChatType = 'chat' | 'query' | 'analyze'
|
||||||
|
|
||||||
|
export interface BaseContent {
|
||||||
|
type: ChatType
|
||||||
|
question: string
|
||||||
|
sql: string | null
|
||||||
|
answer: string | any[]
|
||||||
|
chart?: string
|
||||||
|
error?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type MessageContent = string | BaseContent
|
||||||
|
|
||||||
|
export interface Message {
|
||||||
|
role: 'user' | 'assistant'
|
||||||
|
content: MessageContent
|
||||||
|
/** ISO string */
|
||||||
|
createdAt?: string
|
||||||
}
|
}
|
||||||
|
|
@ -26,6 +26,8 @@ export interface AuditLogDto {
|
||||||
applicationName: string
|
applicationName: string
|
||||||
userId?: string
|
userId?: string
|
||||||
userName?: string
|
userName?: string
|
||||||
|
tenantId?: string
|
||||||
|
tenantName?: string
|
||||||
executionTime: string
|
executionTime: string
|
||||||
executionDuration: number
|
executionDuration: number
|
||||||
clientIpAddress?: string
|
clientIpAddress?: string
|
||||||
|
|
|
||||||
|
|
@ -196,6 +196,7 @@ export interface ColumnFilterDto {
|
||||||
export interface ColumnFormatDto extends AuditedEntityDto<string> {
|
export interface ColumnFormatDto extends AuditedEntityDto<string> {
|
||||||
fieldName?: string
|
fieldName?: string
|
||||||
captionName?: string
|
captionName?: string
|
||||||
|
placeHolder?: string
|
||||||
readOnly: boolean
|
readOnly: boolean
|
||||||
visible: boolean
|
visible: boolean
|
||||||
isActive: boolean
|
isActive: boolean
|
||||||
|
|
@ -444,6 +445,7 @@ export interface GridEditingDto {
|
||||||
allowDeleting: boolean
|
allowDeleting: boolean
|
||||||
allowAllDeleting: boolean
|
allowAllDeleting: boolean
|
||||||
allowAdding: boolean
|
allowAdding: boolean
|
||||||
|
allowDuplicate: boolean
|
||||||
useIcons: boolean
|
useIcons: boolean
|
||||||
confirmDelete: boolean
|
confirmDelete: boolean
|
||||||
newRowPosition?: NewRowPosition
|
newRowPosition?: NewRowPosition
|
||||||
|
|
|
||||||
|
|
@ -33,4 +33,5 @@ export type ThemeConfig = {
|
||||||
type: LayoutType
|
type: LayoutType
|
||||||
sideNavCollapse: boolean
|
sideNavCollapse: boolean
|
||||||
}
|
}
|
||||||
|
style: string
|
||||||
}
|
}
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { THEME_ENUM } from '@/constants/theme.constant'
|
import { LAYOUT_TYPE_BLANK, LAYOUT_TYPE_DECKED, LAYOUT_TYPE_SIMPLE, THEME_ENUM } from '@/constants/theme.constant'
|
||||||
import { ThemeConfig } from '@/proxy/theme/models'
|
import { ThemeConfig } from '@/proxy/theme/models'
|
||||||
|
|
||||||
export const themeConfig: ThemeConfig = {
|
export const themeConfig: ThemeConfig = {
|
||||||
|
|
@ -14,4 +14,7 @@ export const themeConfig: ThemeConfig = {
|
||||||
type: THEME_ENUM.LAYOUT_TYPE_SIMPLE,
|
type: THEME_ENUM.LAYOUT_TYPE_SIMPLE,
|
||||||
sideNavCollapse: false,
|
sideNavCollapse: false,
|
||||||
},
|
},
|
||||||
|
style: 'dx.material.blue.light.compact',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const availableNavColorLayouts = [LAYOUT_TYPE_DECKED, LAYOUT_TYPE_SIMPLE, LAYOUT_TYPE_BLANK]
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
// DynamicRouter.tsx
|
// DynamicRouter.tsx
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { Routes, Route, Navigate } from 'react-router-dom'
|
import { Routes, Route, Navigate, useLocation } from 'react-router-dom'
|
||||||
import { mapDynamicRoutes } from './dynamicRouteLoader'
|
import { mapDynamicRoutes } from './dynamicRouteLoader'
|
||||||
import { useDynamicRoutes } from './dynamicRoutesContext'
|
import { useDynamicRoutes } from './dynamicRoutesContext'
|
||||||
import { useComponents } from '@/contexts/ComponentContext'
|
import { useComponents } from '@/contexts/ComponentContext'
|
||||||
|
|
@ -13,15 +13,18 @@ import { hasSubdomain } from '@/utils/subdomain'
|
||||||
// AccessDenied ve NotFound'u dinamiklikten çıkarıyoruz
|
// AccessDenied ve NotFound'u dinamiklikten çıkarıyoruz
|
||||||
const AccessDenied = React.lazy(() => import('@/views/AccessDenied'))
|
const AccessDenied = React.lazy(() => import('@/views/AccessDenied'))
|
||||||
const NotFound = React.lazy(() => import('@/views/NotFound'))
|
const NotFound = React.lazy(() => import('@/views/NotFound'))
|
||||||
|
const DatabaseSetup = React.lazy(() => import('@/views/setup/DatabaseSetup'))
|
||||||
|
|
||||||
export const DynamicRouter: React.FC = () => {
|
export const DynamicRouter: React.FC = () => {
|
||||||
const { routes, loading, error } = useDynamicRoutes()
|
const { routes, loading, error } = useDynamicRoutes()
|
||||||
const { registeredComponents, renderComponent, isComponentRegistered } = useComponents()
|
const { registeredComponents, renderComponent, isComponentRegistered } = useComponents()
|
||||||
|
const location = useLocation()
|
||||||
|
|
||||||
const dynamicRoutes = React.useMemo(() => mapDynamicRoutes(routes), [routes])
|
const dynamicRoutes = React.useMemo(() => mapDynamicRoutes(routes), [routes])
|
||||||
|
|
||||||
if (loading) return <div>Loading...</div>
|
// /setup path'inde loading bekleme — setup route her zaman erişilebilir olmalı
|
||||||
if (error) return <div>Hata: {error}</div>
|
if (loading && location.pathname !== '/setup') return <div>Loading...</div>
|
||||||
|
if (error && location.pathname !== '/setup') return <div>Hata: {error}</div>
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Routes>
|
<Routes>
|
||||||
|
|
@ -126,6 +129,16 @@ export const DynamicRouter: React.FC = () => {
|
||||||
</React.Suspense>
|
</React.Suspense>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{/* İlk kurulum — veritabanı mevcut değilse gösterilir */}
|
||||||
|
<Route
|
||||||
|
path={ROUTES_ENUM.setup}
|
||||||
|
element={
|
||||||
|
<React.Suspense fallback={<div>Loading...</div>}>
|
||||||
|
<DatabaseSetup />
|
||||||
|
</React.Suspense>
|
||||||
|
}
|
||||||
|
/>
|
||||||
</Routes>
|
</Routes>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ export const useDynamicRoutes = () => {
|
||||||
|
|
||||||
export const DynamicRoutesProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
export const DynamicRoutesProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
||||||
const extraProperties = useStoreState((state) => state.abpConfig?.config?.extraProperties)
|
const extraProperties = useStoreState((state) => state.abpConfig?.config?.extraProperties)
|
||||||
|
const setupMode = useStoreState((state) => state.base.common.setupMode)
|
||||||
const [routes, setRoutes] = useState<RouteDto[]>([])
|
const [routes, setRoutes] = useState<RouteDto[]>([])
|
||||||
const [loading, setLoading] = useState(true)
|
const [loading, setLoading] = useState(true)
|
||||||
const [error, setError] = useState<string | null>(null)
|
const [error, setError] = useState<string | null>(null)
|
||||||
|
|
@ -48,8 +49,12 @@ export const DynamicRoutesProvider: React.FC<{ children: React.ReactNode }> = ({
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (extraProperties) {
|
if (extraProperties) {
|
||||||
loadRoutesFromConfig()
|
loadRoutesFromConfig()
|
||||||
|
} else if (setupMode) {
|
||||||
|
// Veritabanı mevcut değil — setup modunda loading'i kapat
|
||||||
|
setLoading(false)
|
||||||
|
setRoutes([])
|
||||||
}
|
}
|
||||||
}, [extraProperties])
|
}, [extraProperties, setupMode])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DynamicRoutesContext.Provider value={{ routes, loading, error, reload: loadRoutesFromConfig }}>
|
<DynamicRoutesContext.Provider value={{ routes, loading, error, reload: loadRoutesFromConfig }}>
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
export const ROUTES_ENUM = {
|
export const ROUTES_ENUM = {
|
||||||
|
setup: '/setup',
|
||||||
public: {
|
public: {
|
||||||
home: '/home',
|
home: '/home',
|
||||||
about: '/about',
|
about: '/about',
|
||||||
|
|
|
||||||
|
|
@ -5,10 +5,13 @@ import {
|
||||||
} from '../proxy/config/models'
|
} from '../proxy/config/models'
|
||||||
import apiService from './api.service'
|
import apiService from './api.service'
|
||||||
|
|
||||||
|
export const applicationConfigurationUrl = (includeLocalizationResources: boolean) =>
|
||||||
|
`/api/abp/application-configuration?includeLocalizationResources=${includeLocalizationResources}`
|
||||||
|
|
||||||
export const getAppConfig = (includeLocalizationResources: boolean) =>
|
export const getAppConfig = (includeLocalizationResources: boolean) =>
|
||||||
apiService.fetchData<ApplicationConfigurationDto>({
|
apiService.fetchData<ApplicationConfigurationDto>({
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
url: `/api/abp/application-configuration?includeLocalizationResources=${includeLocalizationResources}`,
|
url: applicationConfigurationUrl(includeLocalizationResources),
|
||||||
})
|
})
|
||||||
|
|
||||||
export const getLocalizations = ({
|
export const getLocalizations = ({
|
||||||
|
|
|
||||||
|
|
@ -1,573 +0,0 @@
|
||||||
import { ClassroomAttendanceDto, ClassroomChatDto, HandRaiseDto } from '@/proxy/classroom/models'
|
|
||||||
import { ROUTES_ENUM } from '@/routes/route.constant'
|
|
||||||
import { store } from '@/store/store'
|
|
||||||
import * as signalR from '@microsoft/signalr'
|
|
||||||
import { toast } from '@/components/ui'
|
|
||||||
import Notification from '@/components/ui/Notification'
|
|
||||||
|
|
||||||
export class SignalRService {
|
|
||||||
private connection!: signalR.HubConnection
|
|
||||||
private isConnected: boolean = false
|
|
||||||
private currentSessionId?: string
|
|
||||||
private isKicked: boolean = false
|
|
||||||
|
|
||||||
private onAttendanceUpdate?: (record: ClassroomAttendanceDto) => void
|
|
||||||
private onParticipantJoined?: (
|
|
||||||
userId: string,
|
|
||||||
name: string,
|
|
||||||
isTeacher: boolean,
|
|
||||||
isActive: boolean,
|
|
||||||
) => void
|
|
||||||
private onParticipantLeft?: (payload: {
|
|
||||||
userId: string
|
|
||||||
sessionId: string
|
|
||||||
userName: string
|
|
||||||
}) => void
|
|
||||||
private onChatMessage?: (message: ClassroomChatDto) => void
|
|
||||||
private onParticipantMuted?: (userId: string, isMuted: boolean) => void
|
|
||||||
private onHandRaiseReceived?: (studentId: string) => void
|
|
||||||
private onHandRaiseDismissed?: (studentId: string) => void
|
|
||||||
private onOfferReceived?: (fromUserId: string, offer: RTCSessionDescriptionInit) => void
|
|
||||||
private onAnswerReceived?: (fromUserId: string, answer: RTCSessionDescriptionInit) => void
|
|
||||||
private onIceCandidateReceived?: (fromUserId: string, candidate: RTCIceCandidateInit) => void
|
|
||||||
private onForceCleanup?: () => void
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
const { auth } = store.getState()
|
|
||||||
|
|
||||||
this.connection = new signalR.HubConnectionBuilder()
|
|
||||||
.withUrl(`${import.meta.env.VITE_API_URL}/classroomhub`, {
|
|
||||||
accessTokenFactory: () => auth.session.token || '',
|
|
||||||
})
|
|
||||||
.configureLogging(signalR.LogLevel.Information)
|
|
||||||
.build()
|
|
||||||
|
|
||||||
this.setupEventHandlers()
|
|
||||||
}
|
|
||||||
|
|
||||||
private setupEventHandlers() {
|
|
||||||
if (!this.connection) return
|
|
||||||
|
|
||||||
this.connection.on('AttendanceUpdated', (record: ClassroomAttendanceDto) => {
|
|
||||||
this.onAttendanceUpdate?.(record)
|
|
||||||
})
|
|
||||||
|
|
||||||
this.connection.on(
|
|
||||||
'ParticipantJoined',
|
|
||||||
(userId: string, name: string, isTeacher: boolean, isActive: boolean) => {
|
|
||||||
this.onParticipantJoined?.(userId, name, isTeacher, isActive)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
this.connection.on(
|
|
||||||
'ParticipantLeft',
|
|
||||||
(payload: { userId: string; sessionId: string; userName: string }) => {
|
|
||||||
this.onParticipantLeft?.(payload)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
this.connection.on('ChatMessage', (message: any) => {
|
|
||||||
this.onChatMessage?.(message)
|
|
||||||
})
|
|
||||||
|
|
||||||
this.connection.on('ParticipantMuted', (userId: string, isMuted: boolean) => {
|
|
||||||
this.onParticipantMuted?.(userId, isMuted)
|
|
||||||
})
|
|
||||||
|
|
||||||
this.connection.on('HandRaiseReceived', (payload: any) => {
|
|
||||||
this.onHandRaiseReceived?.(payload.studentId)
|
|
||||||
})
|
|
||||||
|
|
||||||
this.connection.on('HandRaiseDismissed', (payload: any) => {
|
|
||||||
this.onHandRaiseDismissed?.(payload.studentId)
|
|
||||||
})
|
|
||||||
|
|
||||||
this.connection.on('ReceiveOffer', (fromUserId: string, offer: RTCSessionDescriptionInit) => {
|
|
||||||
this.onOfferReceived?.(fromUserId, offer)
|
|
||||||
})
|
|
||||||
|
|
||||||
this.connection.on('ReceiveAnswer', (fromUserId: string, answer: RTCSessionDescriptionInit) => {
|
|
||||||
this.onAnswerReceived?.(fromUserId, answer)
|
|
||||||
})
|
|
||||||
|
|
||||||
this.connection.on(
|
|
||||||
'ReceiveIceCandidate',
|
|
||||||
(fromUserId: string, candidate: RTCIceCandidateInit) => {
|
|
||||||
this.onIceCandidateReceived?.(fromUserId, candidate)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
this.connection.onreconnected(async () => {
|
|
||||||
this.isConnected = true
|
|
||||||
toast.push(<Notification title="🔄 Bağlantı tekrar kuruldu" type="success" />, {
|
|
||||||
placement: 'top-end',
|
|
||||||
})
|
|
||||||
|
|
||||||
if (this.currentSessionId && store.getState().auth.user) {
|
|
||||||
const u = store.getState().auth.user
|
|
||||||
await this.joinClass(this.currentSessionId, u.id, u.name, u.role === 'teacher', true)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
this.connection.onclose(async () => {
|
|
||||||
if (this.isKicked) {
|
|
||||||
toast.push(
|
|
||||||
<Notification title="⚠️ Bağlantı koptu, yeniden bağlanılıyor..." type="warning" />,
|
|
||||||
{ placement: 'top-end' },
|
|
||||||
)
|
|
||||||
this.isConnected = false
|
|
||||||
this.currentSessionId = undefined
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
this.isConnected = false
|
|
||||||
try {
|
|
||||||
if (this.currentSessionId) {
|
|
||||||
await this.connection.invoke('LeaveClass', this.currentSessionId)
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
this.currentSessionId = undefined
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
this.connection.on('Error', (message: string) => {
|
|
||||||
toast.push(<Notification title={`❌ Hata: ${message}`} type="danger" />, {
|
|
||||||
placement: 'top-end',
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
this.connection.on('Warning', (message: string) => {
|
|
||||||
toast.push(<Notification title={`⚠️ Uyarı: ${message}`} type="warning" />, {
|
|
||||||
placement: 'top-end',
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
this.connection.on('Info', (message: string) => {
|
|
||||||
toast.push(<Notification title={`ℹ️ Bilgi: ${message}`} type="info" />, {
|
|
||||||
placement: 'top-end',
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
this.connection.onreconnecting(() => {
|
|
||||||
if (this.isKicked) {
|
|
||||||
toast.push(
|
|
||||||
<Notification
|
|
||||||
title="❌ Sınıftan çıkarıldığınız için yeniden bağlanma engellendi"
|
|
||||||
type="danger"
|
|
||||||
/>,
|
|
||||||
)
|
|
||||||
this.connection.stop()
|
|
||||||
throw new Error('Reconnect blocked after kick')
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
this.connection.on('ForceDisconnect', async (message: string) => {
|
|
||||||
this.isKicked = true
|
|
||||||
toast.push(<Notification title={`❌ Sınıftan çıkarıldınız: ${message}`} type="danger" />, {
|
|
||||||
placement: 'top-end',
|
|
||||||
})
|
|
||||||
|
|
||||||
if (this.onForceCleanup) {
|
|
||||||
this.onForceCleanup()
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
await this.connection.stop()
|
|
||||||
} catch {}
|
|
||||||
|
|
||||||
this.isConnected = false
|
|
||||||
|
|
||||||
if (this.currentSessionId && store.getState().auth.user) {
|
|
||||||
this.onParticipantLeft?.({
|
|
||||||
userId: store.getState().auth.user.id,
|
|
||||||
sessionId: this.currentSessionId,
|
|
||||||
userName: store.getState().auth.user.name,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
this.currentSessionId = undefined
|
|
||||||
window.location.href = ROUTES_ENUM.protected.coordinator.classroom.classes
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
async start(): Promise<void> {
|
|
||||||
try {
|
|
||||||
const startPromise = this.connection.start()
|
|
||||||
const timeout = new Promise((_, reject) =>
|
|
||||||
setTimeout(() => reject(new Error('Bağlantı zaman aşımına uğradı')), 10000),
|
|
||||||
)
|
|
||||||
|
|
||||||
await Promise.race([startPromise, timeout])
|
|
||||||
this.isConnected = true
|
|
||||||
toast.push(<Notification title="✅ Bağlantı kuruldu" type="success" />, {
|
|
||||||
placement: 'top-end',
|
|
||||||
})
|
|
||||||
} catch {
|
|
||||||
toast.push(
|
|
||||||
<Notification
|
|
||||||
title="⚠️ Sunucuya bağlanılamadı. Lütfen sayfayı yenileyin veya internet bağlantınızı kontrol edin."
|
|
||||||
type="danger"
|
|
||||||
/>,
|
|
||||||
{ placement: 'top-end' },
|
|
||||||
)
|
|
||||||
this.isConnected = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async joinClass(
|
|
||||||
sessionId: string,
|
|
||||||
userId: string,
|
|
||||||
userName: string,
|
|
||||||
isTeacher: boolean,
|
|
||||||
isActive: boolean,
|
|
||||||
): Promise<void> {
|
|
||||||
if (!this.isConnected) {
|
|
||||||
toast.push(
|
|
||||||
<Notification
|
|
||||||
title="⚠️ Bağlantı yok. Sınıfa katılmadan önce bağlantıyı kontrol edin."
|
|
||||||
type="warning"
|
|
||||||
/>,
|
|
||||||
)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
this.currentSessionId = sessionId
|
|
||||||
try {
|
|
||||||
await this.connection.invoke('JoinClass', sessionId, userId, userName, isTeacher, isActive)
|
|
||||||
} catch {
|
|
||||||
toast.push(<Notification title="❌ Sınıfa katılamadı" type="danger" />, {
|
|
||||||
placement: 'top-end',
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async leaveClass(sessionId: string): Promise<void> {
|
|
||||||
const { auth } = store.getState()
|
|
||||||
|
|
||||||
if (!this.isConnected) {
|
|
||||||
this.onParticipantLeft?.({ userId: auth.user.id, sessionId, userName: auth.user.name })
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
await this.connection.invoke('LeaveClass', sessionId)
|
|
||||||
this.currentSessionId = undefined
|
|
||||||
} catch {
|
|
||||||
toast.push(<Notification title="⚠️ Çıkış başarısız" type="warning" />, {
|
|
||||||
placement: 'top-end',
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async sendChatMessage(
|
|
||||||
sessionId: string,
|
|
||||||
senderId: string,
|
|
||||||
senderName: string,
|
|
||||||
message: string,
|
|
||||||
isTeacher: boolean,
|
|
||||||
): Promise<void> {
|
|
||||||
if (!this.isConnected) {
|
|
||||||
const chatMessage: ClassroomChatDto = {
|
|
||||||
id: crypto.randomUUID(),
|
|
||||||
sessionId,
|
|
||||||
senderId,
|
|
||||||
senderName,
|
|
||||||
message,
|
|
||||||
timestamp: new Date().toISOString(),
|
|
||||||
isTeacher,
|
|
||||||
messageType: 'public',
|
|
||||||
}
|
|
||||||
setTimeout(() => {
|
|
||||||
this.onChatMessage?.(chatMessage)
|
|
||||||
}, 100)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
await this.connection.invoke(
|
|
||||||
'SendChatMessage',
|
|
||||||
sessionId,
|
|
||||||
senderId,
|
|
||||||
senderName,
|
|
||||||
message,
|
|
||||||
isTeacher,
|
|
||||||
'public',
|
|
||||||
)
|
|
||||||
} catch {
|
|
||||||
toast.push(<Notification title="❌ Mesaj gönderilemedi" type="danger" />, {
|
|
||||||
placement: 'top-end',
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async sendPrivateMessage(
|
|
||||||
sessionId: string,
|
|
||||||
senderId: string,
|
|
||||||
senderName: string,
|
|
||||||
message: string,
|
|
||||||
recipientId: string,
|
|
||||||
recipientName: string,
|
|
||||||
isTeacher: boolean,
|
|
||||||
): Promise<void> {
|
|
||||||
if (!this.isConnected) {
|
|
||||||
const chatMessage: ClassroomChatDto = {
|
|
||||||
id: crypto.randomUUID(),
|
|
||||||
sessionId,
|
|
||||||
senderId,
|
|
||||||
senderName,
|
|
||||||
message,
|
|
||||||
timestamp: new Date().toISOString(),
|
|
||||||
isTeacher,
|
|
||||||
recipientId,
|
|
||||||
recipientName,
|
|
||||||
messageType: 'private',
|
|
||||||
}
|
|
||||||
setTimeout(() => {
|
|
||||||
this.onChatMessage?.(chatMessage)
|
|
||||||
}, 100)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
await this.connection.invoke(
|
|
||||||
'SendPrivateMessage',
|
|
||||||
sessionId,
|
|
||||||
senderId,
|
|
||||||
senderName,
|
|
||||||
message,
|
|
||||||
recipientId,
|
|
||||||
recipientName,
|
|
||||||
isTeacher,
|
|
||||||
'private',
|
|
||||||
)
|
|
||||||
} catch {
|
|
||||||
toast.push(<Notification title="❌ Özel mesaj gönderilemedi" type="danger" />, {
|
|
||||||
placement: 'top-end',
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async sendAnnouncement(
|
|
||||||
sessionId: string,
|
|
||||||
senderId: string,
|
|
||||||
senderName: string,
|
|
||||||
message: string,
|
|
||||||
isTeacher: boolean,
|
|
||||||
): Promise<void> {
|
|
||||||
if (!this.isConnected) {
|
|
||||||
const chatMessage: ClassroomChatDto = {
|
|
||||||
id: crypto.randomUUID(),
|
|
||||||
sessionId,
|
|
||||||
senderId,
|
|
||||||
senderName,
|
|
||||||
message,
|
|
||||||
timestamp: new Date().toISOString(),
|
|
||||||
isTeacher,
|
|
||||||
messageType: 'announcement',
|
|
||||||
}
|
|
||||||
setTimeout(() => {
|
|
||||||
this.onChatMessage?.(chatMessage)
|
|
||||||
}, 100)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
await this.connection.invoke(
|
|
||||||
'SendAnnouncement',
|
|
||||||
sessionId,
|
|
||||||
senderId,
|
|
||||||
senderName,
|
|
||||||
message,
|
|
||||||
isTeacher,
|
|
||||||
)
|
|
||||||
} catch {
|
|
||||||
toast.push(<Notification title="❌ Duyuru gönderilemedi" type="danger" />, {
|
|
||||||
placement: 'top-end',
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async muteParticipant(
|
|
||||||
sessionId: string,
|
|
||||||
userId: string,
|
|
||||||
isMuted: boolean,
|
|
||||||
isTeacher: boolean,
|
|
||||||
): Promise<void> {
|
|
||||||
if (!this.isConnected) {
|
|
||||||
setTimeout(() => {
|
|
||||||
this.onParticipantMuted?.(userId, isMuted)
|
|
||||||
}, 100)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
await this.connection.invoke('MuteParticipant', sessionId, userId, isMuted, isTeacher)
|
|
||||||
} catch {
|
|
||||||
toast.push(<Notification title="⚠️ Katılımcı susturulamadı" type="warning" />, {
|
|
||||||
placement: 'top-end',
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async raiseHand(sessionId: string, studentId: string, studentName: string): Promise<void> {
|
|
||||||
if (!this.isConnected) {
|
|
||||||
setTimeout(() => {
|
|
||||||
this.onHandRaiseReceived?.(studentId)
|
|
||||||
}, 100)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
await this.connection.invoke('RaiseHand', sessionId, studentId, studentName)
|
|
||||||
} catch {
|
|
||||||
toast.push(<Notification title="❌ El kaldırma başarısız" type="danger" />, {
|
|
||||||
placement: 'top-end',
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async kickParticipant(sessionId: string, participantId: string, userName: string): Promise<void> {
|
|
||||||
if (!this.isConnected) {
|
|
||||||
setTimeout(() => {
|
|
||||||
this.onParticipantLeft?.({ userId: participantId, sessionId, userName })
|
|
||||||
}, 100)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
await this.connection.invoke('KickParticipant', sessionId, participantId)
|
|
||||||
} catch {
|
|
||||||
toast.push(<Notification title="❌ Katılımcı atılamadı" type="danger" />, {
|
|
||||||
placement: 'top-end',
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async approveHandRaise(sessionId: string, studentId: string): Promise<void> {
|
|
||||||
if (!this.isConnected) {
|
|
||||||
setTimeout(() => {
|
|
||||||
this.onHandRaiseDismissed?.(studentId)
|
|
||||||
}, 100)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
await this.connection.invoke('ApproveHandRaise', sessionId, studentId)
|
|
||||||
} catch {
|
|
||||||
toast.push(<Notification title="⚠️ El kaldırma onayı başarısız" type="warning" />, {
|
|
||||||
placement: 'top-end',
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async dismissHandRaise(sessionId: string, studentId: string): Promise<void> {
|
|
||||||
if (!this.isConnected) {
|
|
||||||
setTimeout(() => {
|
|
||||||
this.onHandRaiseDismissed?.(studentId)
|
|
||||||
}, 100)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
await this.connection.invoke('DismissHandRaise', sessionId, studentId)
|
|
||||||
} catch {
|
|
||||||
toast.push(<Notification title="⚠️ El indirme başarısız" type="warning" />, {
|
|
||||||
placement: 'top-end',
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async sendOffer(sessionId: string, targetUserId: string, offer: RTCSessionDescriptionInit) {
|
|
||||||
if (!this.isConnected) return
|
|
||||||
await this.connection.invoke('SendOffer', sessionId, targetUserId, offer)
|
|
||||||
}
|
|
||||||
|
|
||||||
async sendAnswer(sessionId: string, targetUserId: string, answer: RTCSessionDescriptionInit) {
|
|
||||||
if (!this.isConnected) return
|
|
||||||
await this.connection.invoke('SendAnswer', sessionId, targetUserId, answer)
|
|
||||||
}
|
|
||||||
|
|
||||||
async sendIceCandidate(sessionId: string, targetUserId: string, candidate: RTCIceCandidateInit) {
|
|
||||||
if (!this.isConnected) return
|
|
||||||
await this.connection.invoke('SendIceCandidate', sessionId, targetUserId, candidate)
|
|
||||||
}
|
|
||||||
|
|
||||||
setExistingParticipantsHandler(callback: (participants: any[]) => void) {
|
|
||||||
this.connection.on('ExistingParticipants', callback)
|
|
||||||
}
|
|
||||||
|
|
||||||
setAttendanceUpdatedHandler(callback: (record: ClassroomAttendanceDto) => void) {
|
|
||||||
this.onAttendanceUpdate = callback
|
|
||||||
}
|
|
||||||
|
|
||||||
setParticipantJoinHandler(
|
|
||||||
callback: (userId: string, name: string, isTeacher: boolean, isActive: boolean) => void,
|
|
||||||
) {
|
|
||||||
this.onParticipantJoined = callback
|
|
||||||
}
|
|
||||||
|
|
||||||
setParticipantLeaveHandler(
|
|
||||||
callback: (payload: { userId: string; sessionId: string; userName: string }) => void,
|
|
||||||
) {
|
|
||||||
this.onParticipantLeft = callback
|
|
||||||
}
|
|
||||||
|
|
||||||
setChatMessageReceivedHandler(callback: (message: ClassroomChatDto) => void) {
|
|
||||||
this.onChatMessage = callback
|
|
||||||
}
|
|
||||||
|
|
||||||
setParticipantMutedHandler(callback: (userId: string, isMuted: boolean) => void) {
|
|
||||||
this.onParticipantMuted = callback
|
|
||||||
}
|
|
||||||
|
|
||||||
setHandRaiseReceivedHandler(callback: (studentId: string) => void) {
|
|
||||||
this.onHandRaiseReceived = callback
|
|
||||||
}
|
|
||||||
|
|
||||||
setHandRaiseDismissedHandler(callback: (studentId: string) => void) {
|
|
||||||
this.onHandRaiseDismissed = callback
|
|
||||||
}
|
|
||||||
|
|
||||||
setOfferReceivedHandler(
|
|
||||||
callback: (fromUserId: string, offer: RTCSessionDescriptionInit) => void,
|
|
||||||
) {
|
|
||||||
this.onOfferReceived = callback
|
|
||||||
}
|
|
||||||
|
|
||||||
setAnswerReceivedHandler(
|
|
||||||
callback: (fromUserId: string, answer: RTCSessionDescriptionInit) => void,
|
|
||||||
) {
|
|
||||||
this.onAnswerReceived = callback
|
|
||||||
}
|
|
||||||
|
|
||||||
setIceCandidateReceivedHandler(
|
|
||||||
callback: (fromUserId: string, candidate: RTCIceCandidateInit) => void,
|
|
||||||
) {
|
|
||||||
this.onIceCandidateReceived = callback
|
|
||||||
}
|
|
||||||
|
|
||||||
async disconnect(): Promise<void> {
|
|
||||||
if (this.isConnected && this.currentSessionId) {
|
|
||||||
try {
|
|
||||||
await this.connection.invoke('LeaveClass', this.currentSessionId)
|
|
||||||
} catch {
|
|
||||||
toast.push(<Notification title="⚠️ Bağlantı koparılırken hata" type="warning" />, {
|
|
||||||
placement: 'top-end',
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (this.connection) {
|
|
||||||
await this.connection.stop()
|
|
||||||
}
|
|
||||||
this.isConnected = false
|
|
||||||
this.currentSessionId = undefined
|
|
||||||
}
|
|
||||||
|
|
||||||
getConnectionState(): boolean {
|
|
||||||
return this.isConnected
|
|
||||||
}
|
|
||||||
|
|
||||||
setForceCleanupHandler(callback: () => void) {
|
|
||||||
this.onForceCleanup = callback
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,358 +0,0 @@
|
||||||
import { toast } from '@/components/ui'
|
|
||||||
import Notification from '@/components/ui/Notification'
|
|
||||||
|
|
||||||
export class WebRTCService {
|
|
||||||
private peerConnections: Map<string, RTCPeerConnection> = new Map()
|
|
||||||
private retryCounts: Map<string, number> = new Map()
|
|
||||||
private maxRetries = 3
|
|
||||||
private signalRService: any
|
|
||||||
private sessionId: string = ''
|
|
||||||
|
|
||||||
private localStream: MediaStream | null = null
|
|
||||||
private onRemoteStream?: (userId: string, stream: MediaStream) => void
|
|
||||||
private onIceCandidate?: (userId: string, candidate: RTCIceCandidateInit) => void
|
|
||||||
private candidateBuffer: Map<string, RTCIceCandidateInit[]> = new Map()
|
|
||||||
|
|
||||||
private rtcConfiguration: RTCConfiguration = {
|
|
||||||
iceServers: [
|
|
||||||
{
|
|
||||||
urls: [
|
|
||||||
'stun:turn.sozsoft.com:3478',
|
|
||||||
'turn:turn.sozsoft.com:3478?transport=udp',
|
|
||||||
'turn:turn.sozsoft.com:3478?transport=tcp',
|
|
||||||
'turns:turn.sozsoft.com:5349?transport=tcp',
|
|
||||||
],
|
|
||||||
username: 'webrtc',
|
|
||||||
credential: 'strongpassword123',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}
|
|
||||||
|
|
||||||
async initializeLocalStream(enableAudio: boolean, enableVideo: boolean): Promise<MediaStream> {
|
|
||||||
try {
|
|
||||||
this.localStream = await navigator.mediaDevices.getUserMedia({
|
|
||||||
video: {
|
|
||||||
width: { ideal: 1280 },
|
|
||||||
height: { ideal: 720 },
|
|
||||||
frameRate: { ideal: 30 },
|
|
||||||
},
|
|
||||||
audio: {
|
|
||||||
echoCancellation: true,
|
|
||||||
noiseSuppression: true,
|
|
||||||
autoGainControl: true,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
this.localStream.getAudioTracks().forEach((track) => (track.enabled = enableAudio))
|
|
||||||
this.localStream.getVideoTracks().forEach((track) => (track.enabled = enableVideo))
|
|
||||||
|
|
||||||
return this.localStream
|
|
||||||
} catch {
|
|
||||||
toast.push(
|
|
||||||
<Notification
|
|
||||||
title="❌ Kamera/Mikrofon erişilemedi. Tarayıcı ayarlarınızı veya izinleri kontrol edin."
|
|
||||||
type="danger"
|
|
||||||
/>,
|
|
||||||
{ placement: 'top-end' },
|
|
||||||
)
|
|
||||||
throw new Error('Media devices access failed')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async createPeerConnection(userId: string): Promise<RTCPeerConnection> {
|
|
||||||
const peerConnection = new RTCPeerConnection(this.rtcConfiguration)
|
|
||||||
this.peerConnections.set(userId, peerConnection)
|
|
||||||
this.retryCounts.set(userId, 0)
|
|
||||||
|
|
||||||
if (this.localStream) {
|
|
||||||
this.localStream.getTracks().forEach((track) => {
|
|
||||||
peerConnection.addTrack(track, this.localStream!)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
peerConnection.ontrack = (event) => {
|
|
||||||
const [remoteStream] = event.streams
|
|
||||||
this.onRemoteStream?.(userId, remoteStream)
|
|
||||||
}
|
|
||||||
|
|
||||||
peerConnection.onicecandidate = (event) => {
|
|
||||||
if (event.candidate) {
|
|
||||||
this.onIceCandidate?.(userId, event.candidate)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
peerConnection.onconnectionstatechange = async () => {
|
|
||||||
const state = peerConnection.connectionState
|
|
||||||
|
|
||||||
if (state === 'closed') {
|
|
||||||
this.closePeerConnection(userId)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (state === 'failed') {
|
|
||||||
let retries = this.retryCounts.get(userId) ?? 0
|
|
||||||
if (retries < this.maxRetries) {
|
|
||||||
toast.push(
|
|
||||||
<Notification
|
|
||||||
title={`⚠️ Bağlantı başarısız, yeniden deneniyor (${retries + 1}/${this.maxRetries})`}
|
|
||||||
type="warning"
|
|
||||||
/>,
|
|
||||||
)
|
|
||||||
this.retryCounts.set(userId, retries + 1)
|
|
||||||
await this.restartIce(peerConnection, userId)
|
|
||||||
} else {
|
|
||||||
toast.push(
|
|
||||||
<Notification
|
|
||||||
title={`❌ Bağlantı kurulamadı (${this.maxRetries} deneme başarısız).`}
|
|
||||||
type="danger"
|
|
||||||
/>,
|
|
||||||
{ placement: 'top-end' },
|
|
||||||
)
|
|
||||||
this.closePeerConnection(userId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.candidateBuffer.has(userId)) {
|
|
||||||
for (const cand of this.candidateBuffer.get(userId)!) {
|
|
||||||
try {
|
|
||||||
await peerConnection.addIceCandidate(cand)
|
|
||||||
} catch {
|
|
||||||
toast.push(
|
|
||||||
<Notification
|
|
||||||
title={`⚠️ ICE candidate eklenemedi. Kullanıcı: ${userId}`}
|
|
||||||
type="warning"
|
|
||||||
/>,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.candidateBuffer.delete(userId)
|
|
||||||
}
|
|
||||||
|
|
||||||
return peerConnection
|
|
||||||
}
|
|
||||||
|
|
||||||
setSignalRService(signalRService: any, sessionId: string) {
|
|
||||||
this.signalRService = signalRService
|
|
||||||
this.sessionId = sessionId
|
|
||||||
}
|
|
||||||
|
|
||||||
setIceCandidateHandler(callback: (userId: string, candidate: RTCIceCandidateInit) => void) {
|
|
||||||
this.onIceCandidate = callback
|
|
||||||
}
|
|
||||||
|
|
||||||
async createOffer(userId: string): Promise<RTCSessionDescriptionInit> {
|
|
||||||
const pc = this.peerConnections.get(userId)
|
|
||||||
if (!pc) throw new Error('Peer connection not found')
|
|
||||||
|
|
||||||
try {
|
|
||||||
const offer = await pc.createOffer()
|
|
||||||
await pc.setLocalDescription(offer)
|
|
||||||
return offer
|
|
||||||
} catch {
|
|
||||||
toast.push(<Notification title="❌ Offer oluşturulamadı" type="danger" />, {
|
|
||||||
placement: 'top-end',
|
|
||||||
})
|
|
||||||
throw new Error('Offer creation failed')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async createAnswer(
|
|
||||||
userId: string,
|
|
||||||
offer: RTCSessionDescriptionInit,
|
|
||||||
): Promise<RTCSessionDescriptionInit> {
|
|
||||||
const pc = this.peerConnections.get(userId)
|
|
||||||
if (!pc) throw new Error('Peer connection not found')
|
|
||||||
|
|
||||||
try {
|
|
||||||
await pc.setRemoteDescription(offer)
|
|
||||||
const answer = await pc.createAnswer()
|
|
||||||
await pc.setLocalDescription(answer)
|
|
||||||
return answer
|
|
||||||
} catch {
|
|
||||||
toast.push(<Notification title="❌ Answer oluşturulamadı" type="danger" />, {
|
|
||||||
placement: 'top-end',
|
|
||||||
})
|
|
||||||
throw new Error('Answer creation failed')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async handleAnswer(userId: string, answer: RTCSessionDescriptionInit): Promise<void> {
|
|
||||||
const peerConnection = this.peerConnections.get(userId)
|
|
||||||
if (!peerConnection) throw new Error('Peer connection not found')
|
|
||||||
await peerConnection.setRemoteDescription(answer)
|
|
||||||
}
|
|
||||||
|
|
||||||
async addIceCandidate(userId: string, candidate: RTCIceCandidateInit): Promise<void> {
|
|
||||||
const pc = this.peerConnections.get(userId)
|
|
||||||
if (!pc) {
|
|
||||||
if (!this.candidateBuffer.has(userId)) {
|
|
||||||
this.candidateBuffer.set(userId, [])
|
|
||||||
}
|
|
||||||
this.candidateBuffer.get(userId)!.push(candidate)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pc.signalingState === 'stable' || pc.signalingState === 'have-remote-offer') {
|
|
||||||
try {
|
|
||||||
await pc.addIceCandidate(candidate)
|
|
||||||
} catch {
|
|
||||||
toast.push(
|
|
||||||
<Notification
|
|
||||||
title={`⚠️ ICE candidate eklenemedi. Kullanıcı: ${userId}`}
|
|
||||||
type="warning"
|
|
||||||
/>,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (!this.candidateBuffer.has(userId)) {
|
|
||||||
this.candidateBuffer.set(userId, [])
|
|
||||||
}
|
|
||||||
this.candidateBuffer.get(userId)!.push(candidate)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onRemoteStreamReceived(callback: (userId: string, stream: MediaStream) => void) {
|
|
||||||
this.onRemoteStream = callback
|
|
||||||
}
|
|
||||||
|
|
||||||
async toggleVideo(enabled: boolean): Promise<void> {
|
|
||||||
if (!this.localStream) return
|
|
||||||
let videoTrack = this.localStream.getVideoTracks()[0]
|
|
||||||
|
|
||||||
if (videoTrack) {
|
|
||||||
videoTrack.enabled = enabled
|
|
||||||
} else if (enabled) {
|
|
||||||
try {
|
|
||||||
const stream = await navigator.mediaDevices.getUserMedia({ video: true })
|
|
||||||
const newTrack = stream.getVideoTracks()[0]
|
|
||||||
if (newTrack) {
|
|
||||||
this.localStream!.addTrack(newTrack)
|
|
||||||
this.peerConnections.forEach((pc) => {
|
|
||||||
const sender = pc.getSenders().find((s) => s.track?.kind === newTrack.kind)
|
|
||||||
if (sender) {
|
|
||||||
sender.replaceTrack(newTrack)
|
|
||||||
} else {
|
|
||||||
pc.addTrack(newTrack, this.localStream!)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
toast.push(<Notification title="❌ Kamera açılamadı" type="danger" />, {
|
|
||||||
placement: 'top-end',
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async toggleAudio(enabled: boolean): Promise<void> {
|
|
||||||
if (!this.localStream) return
|
|
||||||
let audioTrack = this.localStream.getAudioTracks()[0]
|
|
||||||
|
|
||||||
if (audioTrack) {
|
|
||||||
audioTrack.enabled = enabled
|
|
||||||
} else if (enabled) {
|
|
||||||
try {
|
|
||||||
const stream = await navigator.mediaDevices.getUserMedia({ audio: true })
|
|
||||||
const newTrack = stream.getAudioTracks()[0]
|
|
||||||
if (newTrack) {
|
|
||||||
this.localStream!.addTrack(newTrack)
|
|
||||||
this.peerConnections.forEach((pc) => {
|
|
||||||
const sender = pc.getSenders().find((s) => s.track?.kind === newTrack.kind)
|
|
||||||
if (sender) {
|
|
||||||
sender.replaceTrack(newTrack)
|
|
||||||
} else {
|
|
||||||
pc.addTrack(newTrack, this.localStream!)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
toast.push(<Notification title="❌ Mikrofon açılamadı" type="danger" />, {
|
|
||||||
placement: 'top-end',
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
getLocalStream(): MediaStream | null {
|
|
||||||
return this.localStream
|
|
||||||
}
|
|
||||||
|
|
||||||
private async restartIce(peerConnection: RTCPeerConnection, userId: string) {
|
|
||||||
try {
|
|
||||||
const offer = await peerConnection.createOffer({ iceRestart: true })
|
|
||||||
await peerConnection.setLocalDescription(offer)
|
|
||||||
|
|
||||||
if (this.signalRService) {
|
|
||||||
await this.signalRService.sendOffer(this.sessionId, userId, offer)
|
|
||||||
} else {
|
|
||||||
toast.push(<Notification title="⚠️ Tekrar bağlanma başarısız" type="warning" />, {
|
|
||||||
placement: 'top-end',
|
|
||||||
})
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
toast.push(<Notification title="❌ ICE restart başarısız" type="danger" />, {
|
|
||||||
placement: 'top-end',
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
closePeerConnection(userId: string): void {
|
|
||||||
const peerConnection = this.peerConnections.get(userId)
|
|
||||||
if (peerConnection) {
|
|
||||||
peerConnection.getSenders().forEach((sender) => sender.track?.stop())
|
|
||||||
peerConnection.close()
|
|
||||||
this.peerConnections.delete(userId)
|
|
||||||
this.retryCounts.delete(userId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
getPeerConnection(userId: string): RTCPeerConnection | undefined {
|
|
||||||
return this.peerConnections.get(userId)
|
|
||||||
}
|
|
||||||
|
|
||||||
closeAllConnections(): void {
|
|
||||||
this.peerConnections.forEach((pc) => {
|
|
||||||
pc.getSenders().forEach((sender) => sender.track?.stop())
|
|
||||||
pc.close()
|
|
||||||
})
|
|
||||||
this.peerConnections.clear()
|
|
||||||
|
|
||||||
if (this.localStream) {
|
|
||||||
this.localStream.getTracks().forEach((track) => track.stop())
|
|
||||||
this.localStream = null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
addStreamToPeers(stream: MediaStream) {
|
|
||||||
this.peerConnections.forEach((pc) => {
|
|
||||||
stream.getTracks().forEach((track) => {
|
|
||||||
const alreadyHas = pc.getSenders().some((s) => s.track?.id === track.id)
|
|
||||||
if (!alreadyHas) {
|
|
||||||
pc.addTrack(track, stream)
|
|
||||||
track.onended = () => {
|
|
||||||
this.removeTrackFromPeers(track)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
removeTrackFromPeers(track: MediaStreamTrack) {
|
|
||||||
this.peerConnections.forEach((pc) => {
|
|
||||||
pc.getSenders().forEach((sender) => {
|
|
||||||
if (sender.track === track) {
|
|
||||||
try {
|
|
||||||
pc.removeTrack(sender)
|
|
||||||
} catch {
|
|
||||||
toast.push(<Notification title="⚠️ Track silinemedi" type="warning" />, {
|
|
||||||
placement: 'top-end',
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if (sender.track?.readyState !== 'ended') {
|
|
||||||
sender.track?.stop()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -7,7 +7,7 @@ import {
|
||||||
UserClaimModel,
|
UserClaimModel,
|
||||||
UserInfoViewModel,
|
UserInfoViewModel,
|
||||||
} from '@/proxy/admin/models'
|
} from '@/proxy/admin/models'
|
||||||
import { ListResultDto } from '../proxy'
|
import { ListResultDto, PagedAndSortedResultRequestDto, PagedResultDto } from '../proxy'
|
||||||
import { AuditLogDto } from '../proxy/auditLog/audit-log'
|
import { AuditLogDto } from '../proxy/auditLog/audit-log'
|
||||||
import apiService from './api.service'
|
import apiService from './api.service'
|
||||||
|
|
||||||
|
|
@ -74,6 +74,13 @@ export const getAuditLogs = (id: string) =>
|
||||||
url: `/api/app/audit-log/${id}`,
|
url: `/api/app/audit-log/${id}`,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
export const getAuditLogList = (input: PagedAndSortedResultRequestDto) =>
|
||||||
|
apiService.fetchData<PagedResultDto<AuditLogDto>, PagedAndSortedResultRequestDto>({
|
||||||
|
method: 'GET',
|
||||||
|
url: '/api/app/audit-log',
|
||||||
|
params: input,
|
||||||
|
})
|
||||||
|
|
||||||
export const postClaimUser = (input: UserClaimModel) =>
|
export const postClaimUser = (input: UserClaimModel) =>
|
||||||
apiService.fetchData({
|
apiService.fetchData({
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
|
|
|
||||||
21
ui/src/services/setup.service.ts
Normal file
21
ui/src/services/setup.service.ts
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
import apiService from './api.service'
|
||||||
|
|
||||||
|
export interface SetupStatusDto {
|
||||||
|
dbExists: boolean
|
||||||
|
error?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getSetupStatus = () =>
|
||||||
|
apiService.fetchData<SetupStatusDto>({
|
||||||
|
method: 'GET',
|
||||||
|
url: '/api/setup/status',
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the SSE URL for migration streaming.
|
||||||
|
* Usage: new EventSource(getMigrateUrl())
|
||||||
|
*/
|
||||||
|
export const getMigrateUrl = (): string => {
|
||||||
|
const base = import.meta.env.VITE_API_URL ?? ''
|
||||||
|
return `${base}/api/setup/migrate`
|
||||||
|
}
|
||||||
|
|
@ -41,7 +41,7 @@ export const abpConfigModel: AbpConfigModel = {
|
||||||
const currentCulture = helpers.getState().config?.localization.currentCulture.cultureName
|
const currentCulture = helpers.getState().config?.localization.currentCulture.cultureName
|
||||||
const isCultureDifferent = newCulture && currentCulture !== newCulture
|
const isCultureDifferent = newCulture && currentCulture !== newCulture
|
||||||
const isTextsEmpty = !helpers.getState().texts
|
const isTextsEmpty = !helpers.getState().texts
|
||||||
if (isCultureDifferent || isTextsEmpty) {
|
if (payload || isCultureDifferent || isTextsEmpty) {
|
||||||
await actions.getTexts({
|
await actions.getTexts({
|
||||||
cultureName: newCulture ?? currentCulture ?? appConfig.locale,
|
cultureName: newCulture ?? currentCulture ?? appConfig.locale,
|
||||||
onlyDynamics: false,
|
onlyDynamics: false,
|
||||||
|
|
|
||||||
|
|
@ -4,12 +4,16 @@ import { Injections } from './store'
|
||||||
import { GridOptionsEditDto } from '../proxy/form/models'
|
import { GridOptionsEditDto } from '../proxy/form/models'
|
||||||
import setNull from '../utils/setNull'
|
import setNull from '../utils/setNull'
|
||||||
import { ListState } from '@/proxy/admin/list-form/models'
|
import { ListState } from '@/proxy/admin/list-form/models'
|
||||||
|
import { Message } from '@/proxy/ai/models'
|
||||||
|
|
||||||
export interface AdminStoreModel {
|
export interface AdminStoreModel {
|
||||||
lists: {
|
lists: {
|
||||||
values: GridOptionsEditDto | undefined
|
values: GridOptionsEditDto | undefined
|
||||||
states: ListState[]
|
states: ListState[]
|
||||||
}
|
}
|
||||||
|
messages: {
|
||||||
|
aiPosts: Message[]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AdminStoreActions {
|
export interface AdminStoreActions {
|
||||||
|
|
@ -23,6 +27,10 @@ export interface AdminStoreActions {
|
||||||
>
|
>
|
||||||
setStates: Action<AdminStoreModel['lists'], ListState>
|
setStates: Action<AdminStoreModel['lists'], ListState>
|
||||||
}
|
}
|
||||||
|
messages: {
|
||||||
|
addAiPost: Action<AdminStoreModel['messages'], Message>
|
||||||
|
setAiPosts: Action<AdminStoreModel['messages'], Message[]>
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export type AdminModel = AdminStoreModel & AdminStoreActions
|
export type AdminModel = AdminStoreModel & AdminStoreActions
|
||||||
|
|
@ -32,6 +40,9 @@ const initialState: AdminStoreModel = {
|
||||||
values: undefined,
|
values: undefined,
|
||||||
states: [],
|
states: [],
|
||||||
},
|
},
|
||||||
|
messages: {
|
||||||
|
aiPosts: [],
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
export const adminModel: AdminModel = {
|
export const adminModel: AdminModel = {
|
||||||
|
|
@ -62,4 +73,14 @@ export const adminModel: AdminModel = {
|
||||||
actions.setListFormValues(result.data)
|
actions.setListFormValues(result.data)
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
|
messages: {
|
||||||
|
...initialState.messages,
|
||||||
|
addAiPost: action((state, payload) => {
|
||||||
|
state.aiPosts = [...state.aiPosts, payload]
|
||||||
|
}),
|
||||||
|
|
||||||
|
setAiPosts: action((state, payload) => {
|
||||||
|
state.aiPosts = payload
|
||||||
|
}),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,24 +1,6 @@
|
||||||
import type { Action } from 'easy-peasy'
|
import type { Action } from 'easy-peasy'
|
||||||
import { action } from 'easy-peasy'
|
import { action } from 'easy-peasy'
|
||||||
|
|
||||||
type ChatType = 'chat' | 'query' | 'analyze'
|
|
||||||
|
|
||||||
interface BaseContent {
|
|
||||||
type: ChatType
|
|
||||||
question: string
|
|
||||||
sql: string | null
|
|
||||||
answer: string | any[]
|
|
||||||
chart?: string
|
|
||||||
error?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
type MessageContent = string | BaseContent
|
|
||||||
|
|
||||||
export interface Message {
|
|
||||||
role: 'user' | 'assistant'
|
|
||||||
content: MessageContent
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface StoreError {
|
export interface StoreError {
|
||||||
id: string
|
id: string
|
||||||
title: string
|
title: string
|
||||||
|
|
@ -31,12 +13,11 @@ export interface BaseStoreModel {
|
||||||
common: {
|
common: {
|
||||||
currentRouteKey: string
|
currentRouteKey: string
|
||||||
tabHasFocus: boolean
|
tabHasFocus: boolean
|
||||||
|
setupMode: boolean /** Veritabanı mevcut değilse true — setup sayfasına yönlendirme için */
|
||||||
}
|
}
|
||||||
messages: {
|
messages: {
|
||||||
errors: StoreError[]
|
errors: StoreError[]
|
||||||
// success: string[]
|
|
||||||
warning: string[]
|
warning: string[]
|
||||||
aiPosts: Message[]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -44,27 +25,25 @@ export interface BaseStoreActions {
|
||||||
common: {
|
common: {
|
||||||
setCurrentRouteKey: Action<BaseStoreModel['common'], string>
|
setCurrentRouteKey: Action<BaseStoreModel['common'], string>
|
||||||
setTabHasFocus: Action<BaseStoreModel['common'], boolean>
|
setTabHasFocus: Action<BaseStoreModel['common'], boolean>
|
||||||
|
setSetupMode: Action<BaseStoreModel['common'], boolean>
|
||||||
}
|
}
|
||||||
messages: {
|
messages: {
|
||||||
addError: Action<BaseStoreModel['messages'], StoreError>
|
addError: Action<BaseStoreModel['messages'], StoreError>
|
||||||
removeError: Action<BaseStoreModel['messages'], string>
|
removeError: Action<BaseStoreModel['messages'], string>
|
||||||
// setSuccess: Action<BaseStoreModel, string>
|
// setSuccess: Action<BaseStoreModel, string>
|
||||||
setWarning: Action<BaseStoreModel['messages'], string>
|
setWarning: Action<BaseStoreModel['messages'], string>
|
||||||
addAiPost: Action<BaseStoreModel['messages'], Message>
|
|
||||||
setAiPosts: Action<BaseStoreModel['messages'], Message[]>
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export type BaseModel = BaseStoreModel & BaseStoreActions
|
export type BaseModel = BaseStoreModel & BaseStoreActions
|
||||||
|
|
||||||
const initialState: BaseStoreModel = {
|
const initialState: BaseStoreModel = {
|
||||||
common: { currentRouteKey: '', tabHasFocus: false },
|
common: { currentRouteKey: '', tabHasFocus: false, setupMode: false },
|
||||||
messages: {
|
messages: {
|
||||||
errors: [],
|
errors: [],
|
||||||
// success: [],
|
// success: [],
|
||||||
warning: [],
|
warning: [],
|
||||||
aiPosts: [],
|
}
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const baseModel: BaseModel = {
|
export const baseModel: BaseModel = {
|
||||||
|
|
@ -76,6 +55,9 @@ export const baseModel: BaseModel = {
|
||||||
setTabHasFocus: action((state, payload) => {
|
setTabHasFocus: action((state, payload) => {
|
||||||
state.tabHasFocus = payload
|
state.tabHasFocus = payload
|
||||||
}),
|
}),
|
||||||
|
setSetupMode: action((state, payload) => {
|
||||||
|
state.setupMode = payload
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
messages: {
|
messages: {
|
||||||
...initialState.messages,
|
...initialState.messages,
|
||||||
|
|
@ -92,12 +74,5 @@ export const baseModel: BaseModel = {
|
||||||
state.warning = []
|
state.warning = []
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
addAiPost: action((state, payload) => {
|
|
||||||
state.aiPosts = [...state.aiPosts, payload]
|
|
||||||
}),
|
|
||||||
|
|
||||||
setAiPosts: action((state, payload) => {
|
|
||||||
state.aiPosts = payload
|
|
||||||
}),
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,8 @@
|
||||||
import type { Action } from 'easy-peasy'
|
import type { Action } from 'easy-peasy'
|
||||||
import { action } from 'easy-peasy'
|
import { action } from 'easy-peasy'
|
||||||
import { themeConfig } from '../proxy/theme/theme.config'
|
import { availableNavColorLayouts, themeConfig } from '../proxy/theme/theme.config'
|
||||||
import {
|
import {
|
||||||
LAYOUT_TYPE_CLASSIC,
|
|
||||||
LAYOUT_TYPE_DECKED,
|
|
||||||
LAYOUT_TYPE_MODERN,
|
LAYOUT_TYPE_MODERN,
|
||||||
LAYOUT_TYPE_STACKED_SIDE,
|
|
||||||
MODE_DARK,
|
|
||||||
MODE_LIGHT,
|
|
||||||
NAV_MODE_DARK,
|
|
||||||
NAV_MODE_LIGHT,
|
|
||||||
NAV_MODE_THEMED,
|
|
||||||
NAV_MODE_TRANSPARENT,
|
NAV_MODE_TRANSPARENT,
|
||||||
} from '../constants/theme.constant'
|
} from '../constants/theme.constant'
|
||||||
import { Direction, Mode, NavMode } from '../proxy/theme/models'
|
import { Direction, Mode, NavMode } from '../proxy/theme/models'
|
||||||
|
|
@ -29,6 +21,7 @@ export interface ThemeStoreModel {
|
||||||
type: string
|
type: string
|
||||||
sideNavCollapse: boolean
|
sideNavCollapse: boolean
|
||||||
}
|
}
|
||||||
|
style: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ThemeStoreActions {
|
export interface ThemeStoreActions {
|
||||||
|
|
@ -37,34 +30,26 @@ export interface ThemeStoreActions {
|
||||||
setLayout: Action<ThemeStoreModel, string>
|
setLayout: Action<ThemeStoreModel, string>
|
||||||
setPreviousLayout: Action<ThemeStoreModel, string>
|
setPreviousLayout: Action<ThemeStoreModel, string>
|
||||||
setSideNavCollapse: Action<ThemeStoreModel, boolean>
|
setSideNavCollapse: Action<ThemeStoreModel, boolean>
|
||||||
setNavMode: Action<ThemeStoreModel, NavMode | 'default'>
|
setNavMode: Action<ThemeStoreModel, NavMode>
|
||||||
setPanelExpand: Action<ThemeStoreModel, boolean>
|
setPanelExpand: Action<ThemeStoreModel, boolean>
|
||||||
setThemeColor: Action<ThemeStoreModel, string>
|
setThemeColor: Action<ThemeStoreModel, string>
|
||||||
setThemeColorLevel: Action<ThemeStoreModel, number>
|
setThemeColorLevel: Action<ThemeStoreModel, number>
|
||||||
|
setStyle: Action<ThemeStoreModel, string>
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ThemeModel = ThemeStoreModel & ThemeStoreActions
|
export type ThemeModel = ThemeStoreModel & ThemeStoreActions
|
||||||
|
|
||||||
const availableNavColorLayouts = [LAYOUT_TYPE_CLASSIC, LAYOUT_TYPE_STACKED_SIDE, LAYOUT_TYPE_DECKED]
|
|
||||||
|
|
||||||
const initialNavMode = () => {
|
|
||||||
if (themeConfig.layout.type === LAYOUT_TYPE_MODERN && themeConfig.navMode !== NAV_MODE_THEMED) {
|
|
||||||
return NAV_MODE_TRANSPARENT
|
|
||||||
}
|
|
||||||
|
|
||||||
return themeConfig.navMode
|
|
||||||
}
|
|
||||||
|
|
||||||
const initialState: ThemeStoreModel = {
|
const initialState: ThemeStoreModel = {
|
||||||
themeColor: themeConfig.themeColor,
|
themeColor: themeConfig.themeColor,
|
||||||
direction: themeConfig.direction,
|
direction: themeConfig.direction,
|
||||||
mode: themeConfig.mode,
|
mode: themeConfig.mode,
|
||||||
primaryColorLevel: themeConfig.primaryColorLevel,
|
primaryColorLevel: themeConfig.primaryColorLevel,
|
||||||
panelExpand: themeConfig.panelExpand,
|
panelExpand: themeConfig.panelExpand,
|
||||||
navMode: initialNavMode(),
|
navMode: NAV_MODE_TRANSPARENT,
|
||||||
layout: themeConfig.layout,
|
layout: themeConfig.layout,
|
||||||
cardBordered: true,
|
cardBordered: true,
|
||||||
controlSize: 'md',
|
controlSize: 'md',
|
||||||
|
style: themeConfig.style,
|
||||||
}
|
}
|
||||||
|
|
||||||
export const themeModel: ThemeModel = {
|
export const themeModel: ThemeModel = {
|
||||||
|
|
@ -73,32 +58,15 @@ export const themeModel: ThemeModel = {
|
||||||
state.direction = payload
|
state.direction = payload
|
||||||
}),
|
}),
|
||||||
setMode: action((state, payload) => {
|
setMode: action((state, payload) => {
|
||||||
const availableColorNav = availableNavColorLayouts.includes(state.layout.type)
|
|
||||||
|
|
||||||
if (availableColorNav && payload === MODE_DARK && state.navMode !== NAV_MODE_THEMED) {
|
|
||||||
state.navMode = NAV_MODE_DARK
|
|
||||||
}
|
|
||||||
if (availableColorNav && payload === MODE_LIGHT && state.navMode !== NAV_MODE_THEMED) {
|
|
||||||
state.navMode = NAV_MODE_LIGHT
|
|
||||||
}
|
|
||||||
state.mode = payload
|
state.mode = payload
|
||||||
}),
|
}),
|
||||||
setLayout: action((state, payload) => {
|
setLayout: action((state, payload) => {
|
||||||
state.cardBordered = payload === LAYOUT_TYPE_MODERN
|
state.cardBordered = payload === LAYOUT_TYPE_MODERN
|
||||||
if (payload === LAYOUT_TYPE_MODERN) {
|
|
||||||
|
if (availableNavColorLayouts.includes(payload)) {
|
||||||
state.navMode = NAV_MODE_TRANSPARENT
|
state.navMode = NAV_MODE_TRANSPARENT
|
||||||
}
|
}
|
||||||
|
|
||||||
const availableColorNav = availableNavColorLayouts.includes(payload)
|
|
||||||
|
|
||||||
if (availableColorNav && state.mode === MODE_LIGHT) {
|
|
||||||
state.navMode = NAV_MODE_LIGHT
|
|
||||||
}
|
|
||||||
|
|
||||||
if (availableColorNav && state.mode === MODE_DARK) {
|
|
||||||
state.navMode = NAV_MODE_DARK
|
|
||||||
}
|
|
||||||
|
|
||||||
state.layout.type = payload
|
state.layout.type = payload
|
||||||
}),
|
}),
|
||||||
setPreviousLayout: action((state, payload) => {
|
setPreviousLayout: action((state, payload) => {
|
||||||
|
|
@ -108,23 +76,7 @@ export const themeModel: ThemeModel = {
|
||||||
state.layout.sideNavCollapse = payload
|
state.layout.sideNavCollapse = payload
|
||||||
}),
|
}),
|
||||||
setNavMode: action((state, payload) => {
|
setNavMode: action((state, payload) => {
|
||||||
if (payload !== 'default') {
|
|
||||||
state.navMode = payload
|
state.navMode = payload
|
||||||
} else {
|
|
||||||
if (state.layout.type === LAYOUT_TYPE_MODERN) {
|
|
||||||
state.navMode = NAV_MODE_TRANSPARENT
|
|
||||||
}
|
|
||||||
|
|
||||||
const availableColorNav = availableNavColorLayouts.includes(state.layout.type)
|
|
||||||
|
|
||||||
if (availableColorNav && state.mode === MODE_LIGHT) {
|
|
||||||
state.navMode = NAV_MODE_LIGHT
|
|
||||||
}
|
|
||||||
|
|
||||||
if (availableColorNav && state.mode === MODE_DARK) {
|
|
||||||
state.navMode = NAV_MODE_DARK
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}),
|
}),
|
||||||
setPanelExpand: action((state, payload) => {
|
setPanelExpand: action((state, payload) => {
|
||||||
state.panelExpand = payload
|
state.panelExpand = payload
|
||||||
|
|
@ -135,4 +87,7 @@ export const themeModel: ThemeModel = {
|
||||||
setThemeColorLevel: action((state, payload) => {
|
setThemeColorLevel: action((state, payload) => {
|
||||||
state.primaryColorLevel = payload
|
state.primaryColorLevel = payload
|
||||||
}),
|
}),
|
||||||
|
setStyle: action((state, payload) => {
|
||||||
|
state.style = payload
|
||||||
|
}),
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,16 @@ import { FaUser } from 'react-icons/fa'
|
||||||
import navigationIcon from '@/proxy/menus/navigation-icon.config'
|
import navigationIcon from '@/proxy/menus/navigation-icon.config'
|
||||||
import { navigationTreeToFlat } from '@/utils/navigation'
|
import { navigationTreeToFlat } from '@/utils/navigation'
|
||||||
|
|
||||||
|
const extractListFormCode = (path: string): string | null => {
|
||||||
|
const p = (path ?? '').toLowerCase()
|
||||||
|
|
||||||
|
// /admin/form/<code>/..., /admin/list/<code>/..., /admin/chart/<code>/..., /admin/pivot/<code>/...
|
||||||
|
let m = p.match(/\/admin\/(?:list|form|chart|pivot)\/([^/?#]+)/)
|
||||||
|
if (m?.[1]) return m[1]
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
export function useCurrentMenuIcon(className = 'w-6 h-6'): JSX.Element {
|
export function useCurrentMenuIcon(className = 'w-6 h-6'): JSX.Element {
|
||||||
const mainMenu = useStoreState((state) => state.abpConfig.menu.mainMenu)
|
const mainMenu = useStoreState((state) => state.abpConfig.menu.mainMenu)
|
||||||
const location = useLocation()
|
const location = useLocation()
|
||||||
|
|
@ -18,6 +28,12 @@ export function useCurrentMenuIcon(className = 'w-6 h-6'): JSX.Element {
|
||||||
// Exact match
|
// Exact match
|
||||||
if (currentPath.startsWith(menuPath)) return true
|
if (currentPath.startsWith(menuPath)) return true
|
||||||
|
|
||||||
|
// Form/list/chart/pivot routes can include extra segments (e.g. /:id, /edit).
|
||||||
|
// Match by listFormCode extracted from both paths.
|
||||||
|
const currentCode = extractListFormCode(currentPath)
|
||||||
|
const menuCode = extractListFormCode(menuPath)
|
||||||
|
if (currentCode && menuCode && currentCode === menuCode) return true
|
||||||
|
|
||||||
// Extract the form code (e.g., "App.Definitions.Program" from path)
|
// Extract the form code (e.g., "App.Definitions.Program" from path)
|
||||||
const menuFormCode = menuPath.split('/').pop() || ''
|
const menuFormCode = menuPath.split('/').pop() || ''
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { Button } from '@/components/ui'
|
||||||
import { APP_NAME } from '@/constants/app.constant'
|
import { APP_NAME } from '@/constants/app.constant'
|
||||||
import { ROUTES_ENUM } from '@/routes/route.constant'
|
import { ROUTES_ENUM } from '@/routes/route.constant'
|
||||||
import { useLocalization } from '@/utils/hooks/useLocalization'
|
import { useLocalization } from '@/utils/hooks/useLocalization'
|
||||||
|
|
@ -27,14 +28,15 @@ const NotFoundPage = () => {
|
||||||
{translate('::Public.notFound.message')}
|
{translate('::Public.notFound.message')}
|
||||||
</p>
|
</p>
|
||||||
<div className="flex items-center justify-center font-inter">
|
<div className="flex items-center justify-center font-inter">
|
||||||
<button
|
<Button
|
||||||
|
variant='solid'
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
navigate(isAdminPath ? ROUTES_ENUM.protected.dashboard : ROUTES_ENUM.public.home)
|
navigate(isAdminPath ? ROUTES_ENUM.protected.dashboard : ROUTES_ENUM.public.home)
|
||||||
}
|
}
|
||||||
className="px-6 py-3 bg-blue-500 rounded-xl shadow hover:bg-blue-600 transition"
|
className="px-6 py-3 bg-blue-500 rounded-xl shadow hover:bg-blue-600 transition"
|
||||||
>
|
>
|
||||||
{translate('::Public.notFound.button')}
|
{translate('::Public.notFound.button')}
|
||||||
</button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
import AdaptableCard from '@/components/shared/AdaptableCard'
|
import AdaptableCard from '@/components/shared/AdaptableCard'
|
||||||
import Container from '@/components/shared/Container'
|
import Container from '@/components/shared/Container'
|
||||||
|
import Drawer from '@/components/ui/Drawer'
|
||||||
|
import Button from '@/components/ui/Button'
|
||||||
import NotificationChannels from '@/constants/notification-channel.enum'
|
import NotificationChannels from '@/constants/notification-channel.enum'
|
||||||
import { NotificationDto } from '@/proxy/notification/models'
|
import { NotificationDto } from '@/proxy/notification/models'
|
||||||
import { getList } from '@/services/notification.service'
|
import { getList } from '@/services/notification.service'
|
||||||
|
|
@ -9,13 +11,14 @@ import { Dictionary } from 'lodash'
|
||||||
import forOwn from 'lodash/forOwn'
|
import forOwn from 'lodash/forOwn'
|
||||||
import groupBy from 'lodash/groupBy'
|
import groupBy from 'lodash/groupBy'
|
||||||
import has from 'lodash/has'
|
import has from 'lodash/has'
|
||||||
import isEmpty from 'lodash/isEmpty'
|
|
||||||
import merge from 'lodash/merge'
|
import merge from 'lodash/merge'
|
||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import Log from './components/Log'
|
import Log from './components/Log'
|
||||||
import LogFilter from './components/LogFilter'
|
import LogFilter from './components/LogFilter'
|
||||||
import { Helmet } from 'react-helmet'
|
import { Helmet } from 'react-helmet'
|
||||||
import { APP_NAME } from '@/constants/app.constant'
|
import { APP_NAME } from '@/constants/app.constant'
|
||||||
|
import { DIR_RTL } from '@/constants/theme.constant'
|
||||||
|
import { useStoreState } from '@/store'
|
||||||
|
|
||||||
const itemsPerPage = 10
|
const itemsPerPage = 10
|
||||||
|
|
||||||
|
|
@ -26,12 +29,13 @@ const ActivityLog = () => {
|
||||||
const [notifications, setNotifications] = useState<Dictionary<NotificationDto[]>>({})
|
const [notifications, setNotifications] = useState<Dictionary<NotificationDto[]>>({})
|
||||||
const [page, setPage] = useState(0)
|
const [page, setPage] = useState(0)
|
||||||
const [hasMore, setHasMore] = useState(false)
|
const [hasMore, setHasMore] = useState(false)
|
||||||
|
const [isFilterDrawerOpen, setIsFilterDrawerOpen] = useState(false)
|
||||||
|
const direction = useStoreState((state) => state.theme.direction)
|
||||||
const [filter, setFilter] = useState<string[]>([
|
const [filter, setFilter] = useState<string[]>([
|
||||||
NotificationChannels.Desktop,
|
NotificationChannels.Desktop,
|
||||||
NotificationChannels.Mail,
|
NotificationChannels.Mail,
|
||||||
NotificationChannels.Rocket,
|
NotificationChannels.Rocket,
|
||||||
NotificationChannels.Sms,
|
NotificationChannels.Sms,
|
||||||
NotificationChannels.Telegram,
|
|
||||||
NotificationChannels.UiActivity,
|
NotificationChannels.UiActivity,
|
||||||
NotificationChannels.UiToast,
|
NotificationChannels.UiToast,
|
||||||
NotificationChannels.WhatsApp,
|
NotificationChannels.WhatsApp,
|
||||||
|
|
@ -67,18 +71,16 @@ const ActivityLog = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isEmpty(notifications)) {
|
fetchData(page > 0)
|
||||||
fetchData()
|
}, [page, filter])
|
||||||
|
|
||||||
|
const handleFilterChange = (value: string[]) => {
|
||||||
|
setPage(0)
|
||||||
|
setNotifications({})
|
||||||
|
setHasMore(false)
|
||||||
|
setFilter(value)
|
||||||
|
setIsFilterDrawerOpen(false)
|
||||||
}
|
}
|
||||||
}, [])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
fetchData(true)
|
|
||||||
}, [page])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
fetchData()
|
|
||||||
}, [filter])
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container>
|
<Container>
|
||||||
|
|
@ -88,19 +90,53 @@ const ActivityLog = () => {
|
||||||
defaultTitle={APP_NAME}
|
defaultTitle={APP_NAME}
|
||||||
></Helmet>
|
></Helmet>
|
||||||
|
|
||||||
<AdaptableCard>
|
<AdaptableCard className="overflow-hidden">
|
||||||
<div className="grid lg:grid-cols-5 gap-8">
|
<div className="w-full">
|
||||||
<div className="col-span-4">
|
<div className="mb-5 flex items-center justify-between gap-3">
|
||||||
<h3 className="mb-6">{translate('::Abp.Identity.ActivityLogs')}</h3>
|
<h3 className="text-xl font-semibold md:text-2xl">
|
||||||
|
{translate('::Abp.Identity.ActivityLogs')}
|
||||||
|
</h3>
|
||||||
|
<Button
|
||||||
|
className="lg:hidden"
|
||||||
|
size="sm"
|
||||||
|
variant="twoTone"
|
||||||
|
onClick={() => setIsFilterDrawerOpen(true)}
|
||||||
|
>
|
||||||
|
{translate('::Abp.Identity.ActivityLogs.Filters')}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 gap-6 lg:grid-cols-[minmax(0,1fr)_340px]">
|
||||||
|
<div className="min-w-0 rounded-xl border border-gray-200 bg-white p-4 lg:p-6">
|
||||||
<Log
|
<Log
|
||||||
notifications={notifications}
|
notifications={notifications}
|
||||||
isLoading={loading}
|
isLoading={loading}
|
||||||
onLoadMore={() => setPage(page + 1)}
|
onLoadMore={() => setPage((prev) => prev + 1)}
|
||||||
loadable={hasMore}
|
loadable={hasMore}
|
||||||
></Log>
|
></Log>
|
||||||
</div>
|
</div>
|
||||||
<LogFilter filter={filter} onFilterChange={(value: string[]) => setFilter(value)} />
|
|
||||||
|
<div className="hidden lg:block">
|
||||||
|
<LogFilter filter={filter} onFilterChange={handleFilterChange} useAffix />
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Drawer
|
||||||
|
title={translate('::Abp.Identity.ActivityLogs.Filters')}
|
||||||
|
isOpen={isFilterDrawerOpen}
|
||||||
|
width={340}
|
||||||
|
placement={direction === DIR_RTL ? 'right' : 'left'}
|
||||||
|
onClose={() => setIsFilterDrawerOpen(false)}
|
||||||
|
onRequestClose={() => setIsFilterDrawerOpen(false)}
|
||||||
|
>
|
||||||
|
<LogFilter
|
||||||
|
filter={filter}
|
||||||
|
onFilterChange={handleFilterChange}
|
||||||
|
useAffix={false}
|
||||||
|
className="border-none p-0"
|
||||||
|
/>
|
||||||
|
</Drawer>
|
||||||
</AdaptableCard>
|
</AdaptableCard>
|
||||||
</Container>
|
</Container>
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ const Log = ({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Loading type="cover" loading={isLoading}>
|
<Loading type="cover" loading={isLoading}>
|
||||||
<div className="max-w-[900px]">
|
<div className="w-full">
|
||||||
{keys(notifications).map((group) => (
|
{keys(notifications).map((group) => (
|
||||||
<div key={group} className="mb-8">
|
<div key={group} className="mb-8">
|
||||||
<div className="mb-4 font-semibold uppercase">
|
<div className="mb-4 font-semibold uppercase">
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,6 @@ const ticketCheckboxes = [
|
||||||
{ label: NotificationChannels.UiActivity, value: NotificationChannels.UiActivity },
|
{ label: NotificationChannels.UiActivity, value: NotificationChannels.UiActivity },
|
||||||
{ label: NotificationChannels.UiToast, value: NotificationChannels.UiToast },
|
{ label: NotificationChannels.UiToast, value: NotificationChannels.UiToast },
|
||||||
{ label: NotificationChannels.WhatsApp, value: NotificationChannels.WhatsApp },
|
{ label: NotificationChannels.WhatsApp, value: NotificationChannels.WhatsApp },
|
||||||
{ label: NotificationChannels.Telegram, value: NotificationChannels.Telegram },
|
|
||||||
]
|
]
|
||||||
|
|
||||||
const CategoryTitle = ({ children, className }: CategoryTitleProps) => {
|
const CategoryTitle = ({ children, className }: CategoryTitleProps) => {
|
||||||
|
|
@ -34,16 +33,19 @@ const CategoryTitle = ({ children, className }: CategoryTitleProps) => {
|
||||||
const LogFilter = ({
|
const LogFilter = ({
|
||||||
filter,
|
filter,
|
||||||
onFilterChange,
|
onFilterChange,
|
||||||
|
useAffix = true,
|
||||||
|
className,
|
||||||
}: {
|
}: {
|
||||||
filter: string[]
|
filter: string[]
|
||||||
onFilterChange: (value: string[]) => void
|
onFilterChange: (value: string[]) => void
|
||||||
|
useAffix?: boolean
|
||||||
|
className?: string
|
||||||
}) => {
|
}) => {
|
||||||
const { translate } = useLocalization()
|
const { translate } = useLocalization()
|
||||||
|
|
||||||
return (
|
const content = (
|
||||||
<div>
|
<div className={classNames('rounded-xl border border-gray-200 bg-white p-4', className)}>
|
||||||
<Affix className="hidden lg:block" offset={80}>
|
<h5 className="mb-4 text-base font-semibold">{translate('::Abp.Identity.ActivityLogs.Filters')}</h5>
|
||||||
<h5 className="mb-4">{translate('::Abp.Identity.ActivityLogs.Filters')}</h5>
|
|
||||||
<Checkbox.Group
|
<Checkbox.Group
|
||||||
vertical
|
vertical
|
||||||
value={filter}
|
value={filter}
|
||||||
|
|
@ -51,18 +53,23 @@ const LogFilter = ({
|
||||||
onFilterChange(value as string[])
|
onFilterChange(value as string[])
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<CategoryTitle className="mb-3">
|
<CategoryTitle className="mb-3 text-gray-500">
|
||||||
{translate('::Abp.Identity.ActivityLogs.Channels')}
|
{translate('::Abp.Identity.ActivityLogs.Channels')}
|
||||||
</CategoryTitle>
|
</CategoryTitle>
|
||||||
{ticketCheckboxes.map((checkbox) => (
|
{ticketCheckboxes.map((checkbox) => (
|
||||||
<Checkbox key={checkbox.value} className="mb-4" value={checkbox.value}>
|
<Checkbox key={checkbox.value} className="mb-3" value={checkbox.value}>
|
||||||
{checkbox.label}
|
{checkbox.label}
|
||||||
</Checkbox>
|
</Checkbox>
|
||||||
))}
|
))}
|
||||||
</Checkbox.Group>
|
</Checkbox.Group>
|
||||||
</Affix>
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if (useAffix) {
|
||||||
|
return <Affix offset={80}>{content}</Affix>
|
||||||
|
}
|
||||||
|
|
||||||
|
return content
|
||||||
}
|
}
|
||||||
|
|
||||||
export default LogFilter
|
export default LogFilter
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import { useState, useEffect, useCallback, useRef } from 'react'
|
import { useState, useEffect, useCallback, useRef } from 'react'
|
||||||
import { Helmet } from 'react-helmet'
|
import { Helmet } from 'react-helmet'
|
||||||
import { Button, Input, Select, toast, Notification, Spinner } from '@/components/ui'
|
import { Button, Input, Select, toast, Notification, Spinner } from '@/components/ui'
|
||||||
|
import { useStoreState } from '@/store'
|
||||||
import {
|
import {
|
||||||
FaFolder,
|
FaFolder,
|
||||||
FaCloudUploadAlt,
|
FaCloudUploadAlt,
|
||||||
|
|
@ -43,6 +44,10 @@ import { APP_NAME } from '@/constants/app.constant'
|
||||||
const FileManager = () => {
|
const FileManager = () => {
|
||||||
const { translate } = useLocalization()
|
const { translate } = useLocalization()
|
||||||
|
|
||||||
|
const authTenantId = useStoreState((state) => state.auth.tenant?.tenantId)
|
||||||
|
const authTenantName = useStoreState((state) => state.auth.tenant?.tenantName)
|
||||||
|
const isHostContext = !authTenantId
|
||||||
|
|
||||||
// State
|
// State
|
||||||
const [loading, setLoading] = useState(true)
|
const [loading, setLoading] = useState(true)
|
||||||
const [items, setItems] = useState<FileItemType[]>([])
|
const [items, setItems] = useState<FileItemType[]>([])
|
||||||
|
|
@ -71,7 +76,7 @@ const FileManager = () => {
|
||||||
const [tenants, setTenants] = useState<TenantDto[]>([])
|
const [tenants, setTenants] = useState<TenantDto[]>([])
|
||||||
const [tenantsLoading, setTenantsLoading] = useState(false)
|
const [tenantsLoading, setTenantsLoading] = useState(false)
|
||||||
const [selectedTenant, setSelectedTenant] = useState<{ id: string; name: string } | undefined>(
|
const [selectedTenant, setSelectedTenant] = useState<{ id: string; name: string } | undefined>(
|
||||||
undefined,
|
authTenantId ? { id: authTenantId, name: authTenantName || '' } : undefined,
|
||||||
)
|
)
|
||||||
// Tracks mid-flight tenant change so the fetch effect doesn't fire with a stale folderId
|
// Tracks mid-flight tenant change so the fetch effect doesn't fire with a stale folderId
|
||||||
const pendingTenantChange = useRef(false)
|
const pendingTenantChange = useRef(false)
|
||||||
|
|
@ -96,8 +101,20 @@ const FileManager = () => {
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (isHostContext) {
|
||||||
fetchTenants()
|
fetchTenants()
|
||||||
}, [fetchTenants])
|
}
|
||||||
|
}, [fetchTenants, isHostContext])
|
||||||
|
|
||||||
|
// If user is in a tenant context, lock selection to that tenant.
|
||||||
|
useEffect(() => {
|
||||||
|
if (!authTenantId) return
|
||||||
|
|
||||||
|
setSelectedTenant((prev) => {
|
||||||
|
if (prev?.id === authTenantId) return prev
|
||||||
|
return { id: authTenantId, name: authTenantName || prev?.name || '' }
|
||||||
|
})
|
||||||
|
}, [authTenantId, authTenantName])
|
||||||
|
|
||||||
// Reset navigation when tenant changes
|
// Reset navigation when tenant changes
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -124,20 +141,6 @@ const FileManager = () => {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// console.log('Fetched items:', protectedItems)
|
|
||||||
// console.log(
|
|
||||||
// 'Protected folders check:',
|
|
||||||
// protectedItems.filter((item) => item.isReadOnly),
|
|
||||||
// )
|
|
||||||
// console.log(
|
|
||||||
// 'Folders with childCount:',
|
|
||||||
// protectedItems.filter((item) => item.type === 'folder').map(item => ({
|
|
||||||
// name: item.name,
|
|
||||||
// childCount: item.childCount,
|
|
||||||
// hasChildCount: 'childCount' in item,
|
|
||||||
// type: typeof item.childCount
|
|
||||||
// }))
|
|
||||||
// )
|
|
||||||
setItems(protectedItems)
|
setItems(protectedItems)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to fetch items:', error)
|
console.error('Failed to fetch items:', error)
|
||||||
|
|
@ -668,14 +671,14 @@ const FileManager = () => {
|
||||||
await fileManagementService.copyItems(itemIds, currentFolderId, selectedTenant?.id)
|
await fileManagementService.copyItems(itemIds, currentFolderId, selectedTenant?.id)
|
||||||
await fetchItems(currentFolderId)
|
await fetchItems(currentFolderId)
|
||||||
toast.push(
|
toast.push(
|
||||||
<Notification title="Success" type="success">
|
<Notification title={translate('::App.Platform.Success')} type="success">
|
||||||
{itemIds.length} item(s) copied successfully
|
{itemIds.length} item(s) copied successfully
|
||||||
</Notification>,
|
</Notification>,
|
||||||
)
|
)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Copy failed:', error)
|
console.error('Copy failed:', error)
|
||||||
toast.push(
|
toast.push(
|
||||||
<Notification title="Error" type="danger">
|
<Notification title={translate('::App.Platform.Error')} type="danger">
|
||||||
Failed to copy items
|
Failed to copy items
|
||||||
</Notification>,
|
</Notification>,
|
||||||
)
|
)
|
||||||
|
|
@ -701,14 +704,14 @@ const FileManager = () => {
|
||||||
localStorage.removeItem('fileManager_clipboard')
|
localStorage.removeItem('fileManager_clipboard')
|
||||||
setHasClipboardData(false)
|
setHasClipboardData(false)
|
||||||
toast.push(
|
toast.push(
|
||||||
<Notification title="Success" type="success">
|
<Notification title={translate('::App.Platform.Success')} type="success">
|
||||||
{itemIds.length} item(s) moved successfully
|
{itemIds.length} item(s) moved successfully
|
||||||
</Notification>,
|
</Notification>,
|
||||||
)
|
)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Move failed:', error)
|
console.error('Move failed:', error)
|
||||||
toast.push(
|
toast.push(
|
||||||
<Notification title="Error" type="danger">
|
<Notification title={translate('::App.Platform.Error')} type="danger">
|
||||||
Failed to move items
|
Failed to move items
|
||||||
</Notification>,
|
</Notification>,
|
||||||
)
|
)
|
||||||
|
|
@ -742,6 +745,7 @@ const FileManager = () => {
|
||||||
{/* Tenant Selector Row */}
|
{/* Tenant Selector Row */}
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<FaBuilding className="text-gray-500 flex-shrink-0" />
|
<FaBuilding className="text-gray-500 flex-shrink-0" />
|
||||||
|
{isHostContext ? (
|
||||||
<Select
|
<Select
|
||||||
size="xs"
|
size="xs"
|
||||||
isLoading={tenantsLoading}
|
isLoading={tenantsLoading}
|
||||||
|
|
@ -765,6 +769,14 @@ const FileManager = () => {
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
) : (
|
||||||
|
<div
|
||||||
|
className="text-sm font-medium text-gray-700 dark:text-gray-200 truncate max-w-[220px]"
|
||||||
|
title={authTenantName || selectedTenant?.name || ''}
|
||||||
|
>
|
||||||
|
{authTenantName || selectedTenant?.name || ''}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* File Operations */}
|
{/* File Operations */}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import { forwardRef } from 'react'
|
||||||
import classNames from 'classnames'
|
import classNames from 'classnames'
|
||||||
import { FaChevronRight, FaFolder, FaHome } from 'react-icons/fa'
|
import { FaChevronRight, FaFolder, FaHome } from 'react-icons/fa'
|
||||||
import type { BreadcrumbItem } from '@/types/fileManagement'
|
import type { BreadcrumbItem } from '@/types/fileManagement'
|
||||||
|
import { Button } from '@/components/ui'
|
||||||
|
|
||||||
export interface BreadcrumbProps {
|
export interface BreadcrumbProps {
|
||||||
items: BreadcrumbItem[]
|
items: BreadcrumbItem[]
|
||||||
|
|
@ -17,7 +18,8 @@ const Breadcrumb = forwardRef<HTMLDivElement, BreadcrumbProps>((props, ref) => {
|
||||||
{items.map((item, index) => (
|
{items.map((item, index) => (
|
||||||
<div key={item.path} className="flex items-center">
|
<div key={item.path} className="flex items-center">
|
||||||
{index > 0 && <FaChevronRight className="mx-2 h-4 w-4 text-gray-400" />}
|
{index > 0 && <FaChevronRight className="mx-2 h-4 w-4 text-gray-400" />}
|
||||||
<button
|
<Button
|
||||||
|
size="xs"
|
||||||
onClick={() => onNavigate(item)}
|
onClick={() => onNavigate(item)}
|
||||||
className={classNames(
|
className={classNames(
|
||||||
'flex items-center px-2 py-1 rounded hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors',
|
'flex items-center px-2 py-1 rounded hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors',
|
||||||
|
|
@ -33,7 +35,7 @@ const Breadcrumb = forwardRef<HTMLDivElement, BreadcrumbProps>((props, ref) => {
|
||||||
<FaFolder className="h-4 w-4 mr-1" />
|
<FaFolder className="h-4 w-4 mr-1" />
|
||||||
)}
|
)}
|
||||||
<span className="truncate max-w-32">{item.name}</span>
|
<span className="truncate max-w-32">{item.name}</span>
|
||||||
</button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -324,6 +324,8 @@ const FileItem = forwardRef<HTMLDivElement, FileItemProps>((props, ref) => {
|
||||||
const ImagePreview = ({ src, alt }: { src: string; alt: string }) => {
|
const ImagePreview = ({ src, alt }: { src: string; alt: string }) => {
|
||||||
const [imageError, setImageError] = useState(false)
|
const [imageError, setImageError] = useState(false)
|
||||||
|
|
||||||
|
console.log('Rendering ImagePreview with src:', src) // Debug için
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full h-full bg-gray-100 dark:bg-gray-700 rounded flex items-center justify-center overflow-hidden">
|
<div className="w-full h-full bg-gray-100 dark:bg-gray-700 rounded flex items-center justify-center overflow-hidden">
|
||||||
{!imageError ? (
|
{!imageError ? (
|
||||||
|
|
@ -378,7 +380,7 @@ const FileItem = forwardRef<HTMLDivElement, FileItemProps>((props, ref) => {
|
||||||
<div className="w-8 h-8">
|
<div className="w-8 h-8">
|
||||||
{item.type === 'file' && item.mimeType?.startsWith('image/') ? (
|
{item.type === 'file' && item.mimeType?.startsWith('image/') ? (
|
||||||
<ImagePreview
|
<ImagePreview
|
||||||
src={`/api/app/file-management/${item.id}/download-file`}
|
src={FILE_URL(item.path, item.tenantId)}
|
||||||
alt={item.name}
|
alt={item.name}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
|
|
@ -477,7 +479,7 @@ const FileItem = forwardRef<HTMLDivElement, FileItemProps>((props, ref) => {
|
||||||
{item.type === 'file' && item.mimeType?.startsWith('image/') ? (
|
{item.type === 'file' && item.mimeType?.startsWith('image/') ? (
|
||||||
<div className="w-16 h-16">
|
<div className="w-16 h-16">
|
||||||
<ImagePreview
|
<ImagePreview
|
||||||
src={`/api/app/file-management/${item.id}/download-file`}
|
src={FILE_URL(item.path, item.tenantId)}
|
||||||
alt={item.name}
|
alt={item.name}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -137,6 +137,20 @@ const Wizard = () => {
|
||||||
// ── Editing Form Groups (Step 3) ──
|
// ── Editing Form Groups (Step 3) ──
|
||||||
const [editingGroups, setEditingGroups] = useState<WizardGroup[]>([])
|
const [editingGroups, setEditingGroups] = useState<WizardGroup[]>([])
|
||||||
|
|
||||||
|
// Audit columns that should not be selected by default
|
||||||
|
const AUDIT_COLUMNS = new Set([
|
||||||
|
'creationtime',
|
||||||
|
'creatorid',
|
||||||
|
'lastmodificationtime',
|
||||||
|
'lastmodifierid',
|
||||||
|
'isdeleted',
|
||||||
|
'deletiontime',
|
||||||
|
'deleterid',
|
||||||
|
])
|
||||||
|
|
||||||
|
const isAuditColumn = (columnName: string) =>
|
||||||
|
AUDIT_COLUMNS.has(columnName.toLowerCase())
|
||||||
|
|
||||||
const loadColumns = async (dsCode: string, schema: string, name: string) => {
|
const loadColumns = async (dsCode: string, schema: string, name: string) => {
|
||||||
if (!dsCode || !name) {
|
if (!dsCode || !name) {
|
||||||
setSelectCommandColumns([])
|
setSelectCommandColumns([])
|
||||||
|
|
@ -149,7 +163,8 @@ const Wizard = () => {
|
||||||
const res = await sqlObjectManagerService.getTableColumns(dsCode, schema, name)
|
const res = await sqlObjectManagerService.getTableColumns(dsCode, schema, name)
|
||||||
const cols = res.data ?? []
|
const cols = res.data ?? []
|
||||||
setSelectCommandColumns(cols)
|
setSelectCommandColumns(cols)
|
||||||
setSelectedColumns(new Set(cols.map((c) => c.columnName)))
|
const selectableColumns = cols.filter((c) => !isAuditColumn(c.columnName))
|
||||||
|
setSelectedColumns(new Set(selectableColumns.map((c) => c.columnName)))
|
||||||
setEditingGroups([])
|
setEditingGroups([])
|
||||||
// Auto-select first column as key field
|
// Auto-select first column as key field
|
||||||
if (cols.length > 0) {
|
if (cols.length > 0) {
|
||||||
|
|
@ -176,7 +191,15 @@ const Wizard = () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
const toggleAllColumns = (all: boolean) =>
|
const toggleAllColumns = (all: boolean) =>
|
||||||
setSelectedColumns(all ? new Set(selectCommandColumns.map((c) => c.columnName)) : new Set())
|
setSelectedColumns(
|
||||||
|
all
|
||||||
|
? new Set(
|
||||||
|
selectCommandColumns
|
||||||
|
.filter((c) => !isAuditColumn(c.columnName))
|
||||||
|
.map((c) => c.columnName),
|
||||||
|
)
|
||||||
|
: new Set(),
|
||||||
|
)
|
||||||
|
|
||||||
const getDataSourceList = async () => {
|
const getDataSourceList = async () => {
|
||||||
setIsLoadingDataSource(true)
|
setIsLoadingDataSource(true)
|
||||||
|
|
@ -235,11 +258,26 @@ const Wizard = () => {
|
||||||
return sanitized ? `App.Wizard.${sanitized}` : ''
|
return sanitized ? `App.Wizard.${sanitized}` : ''
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const toSpacedLabel = (value: string) =>
|
||||||
|
value
|
||||||
|
.replace(/([a-z0-9])([A-Z])/g, '$1 $2')
|
||||||
|
.replace(/([A-Z])([A-Z][a-z])/g, '$1 $2')
|
||||||
|
.trim()
|
||||||
|
|
||||||
|
|
||||||
const handleWizardNameChange = (name: string) => {
|
const handleWizardNameChange = (name: string) => {
|
||||||
formikRef.current?.setFieldValue('wizardName', name)
|
const spacedLabel = toSpacedLabel(name)
|
||||||
const derived = deriveListFormCode(name)
|
const derived = deriveListFormCode(name)
|
||||||
|
|
||||||
|
formikRef.current?.setFieldValue('wizardName', name)
|
||||||
formikRef.current?.setFieldValue('listFormCode', derived)
|
formikRef.current?.setFieldValue('listFormCode', derived)
|
||||||
formikRef.current?.setFieldValue('menuCode', derived)
|
formikRef.current?.setFieldValue('menuCode', derived)
|
||||||
|
formikRef.current?.setFieldValue('languageTextMenuEn', spacedLabel)
|
||||||
|
formikRef.current?.setFieldValue('languageTextMenuTr', spacedLabel)
|
||||||
|
formikRef.current?.setFieldValue('languageTextTitleEn', spacedLabel)
|
||||||
|
formikRef.current?.setFieldValue('languageTextTitleTr', spacedLabel)
|
||||||
|
formikRef.current?.setFieldValue('languageTextDescEn', spacedLabel)
|
||||||
|
formikRef.current?.setFieldValue('languageTextDescTr', spacedLabel)
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleMenuParentChange = (code: string) => {
|
const handleMenuParentChange = (code: string) => {
|
||||||
|
|
@ -313,8 +351,12 @@ const Wizard = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleDeploy = async () => {
|
const handleDeploy = async () => {
|
||||||
|
try {
|
||||||
if (!formikRef.current) throw new Error('Form bulunamadı')
|
if (!formikRef.current) throw new Error('Form bulunamadı')
|
||||||
|
|
||||||
const values = formikRef.current.values
|
const values = formikRef.current.values
|
||||||
|
|
||||||
|
// 🔴 Önce kayıt işlemi TAMAMLANSIN
|
||||||
await postListFormWizard({
|
await postListFormWizard({
|
||||||
...values,
|
...values,
|
||||||
groups: editingGroups.map((g) => ({
|
groups: editingGroups.map((g) => ({
|
||||||
|
|
@ -322,6 +364,7 @@ const Wizard = () => {
|
||||||
colCount: g.colCount,
|
colCount: g.colCount,
|
||||||
items: g.items.map((item) => {
|
items: g.items.map((item) => {
|
||||||
const col = selectCommandColumns.find((c) => c.columnName === item.dataField)
|
const col = selectCommandColumns.find((c) => c.columnName === item.dataField)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
dataField: item.dataField,
|
dataField: item.dataField,
|
||||||
editorType: item.editorType,
|
editorType: item.editorType,
|
||||||
|
|
@ -329,25 +372,30 @@ const Wizard = () => {
|
||||||
editorScript: item.editorScript ?? '',
|
editorScript: item.editorScript ?? '',
|
||||||
colSpan: item.colSpan,
|
colSpan: item.colSpan,
|
||||||
isRequired: item.isRequired,
|
isRequired: item.isRequired,
|
||||||
dbSourceType: col ? sqlDataTypeToDbType(col.dataType) : 12, // 12 = DbType.String
|
dbSourceType: col ? sqlDataTypeToDbType(col.dataType) : 12,
|
||||||
turkishCaption: item.turkishCaption,
|
turkishCaption: item.turkishCaption,
|
||||||
englishCaption: item.englishCaption,
|
englishCaption: item.englishCaption,
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
})),
|
})),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// ✅ sonra config çek
|
||||||
|
await getConfig(true)
|
||||||
|
|
||||||
|
// ✅ sonra navigate
|
||||||
|
navigate(ROUTES_ENUM.protected.admin.list.replace(':listFormCode', values.listFormCode), { replace: true })
|
||||||
|
|
||||||
|
// ✅ en son kullanıcıya mesaj
|
||||||
toast.push(
|
toast.push(
|
||||||
<Notification type="success" duration={2000}>
|
<Notification type="success" duration={2000}>
|
||||||
{translate('::ListForms.FormBilgileriKaydedildi')}
|
{translate('::ListForms.FormBilgileriKaydedildi')}
|
||||||
</Notification>,
|
</Notification>,
|
||||||
{ placement: 'top-end' },
|
{ placement: 'top-end' },
|
||||||
)
|
)
|
||||||
setTimeout(async () => {
|
} catch (err) {
|
||||||
getConfig(true)
|
console.error(err)
|
||||||
|
}
|
||||||
navigate(ROUTES_ENUM.protected.admin.list.replace(':listFormCode', values.listFormCode))
|
|
||||||
}, 6000)
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -367,7 +415,7 @@ const Wizard = () => {
|
||||||
<Steps.Item
|
<Steps.Item
|
||||||
title={translate('::ListForms.Wizard.ListFormFields') || 'List Form Fields'}
|
title={translate('::ListForms.Wizard.ListFormFields') || 'List Form Fields'}
|
||||||
/>
|
/>
|
||||||
<Steps.Item title={translate('::ListForms.Wizard.Deploy') || 'Deploy'} />
|
<Steps.Item title={translate('::App.Platform.Deploy') || 'Deploy'} />
|
||||||
</Steps>
|
</Steps>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -377,25 +425,30 @@ const Wizard = () => {
|
||||||
validationSchema={listFormValidationSchema}
|
validationSchema={listFormValidationSchema}
|
||||||
onSubmit={async (values, { setSubmitting }) => {
|
onSubmit={async (values, { setSubmitting }) => {
|
||||||
setSubmitting(true)
|
setSubmitting(true)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// 🔴 1. Kaydet (bekle)
|
||||||
await postListFormWizard({ ...values })
|
await postListFormWizard({ ...values })
|
||||||
|
|
||||||
|
// 🔴 2. Config güncelle (bekle)
|
||||||
|
await getConfig(true)
|
||||||
|
|
||||||
|
// 🔴 3. Navigate
|
||||||
|
navigate(ROUTES_ENUM.protected.admin.list.replace(':listFormCode', values.listFormCode), { replace: true })
|
||||||
|
|
||||||
|
// 🔴 4. Toast (istersen navigate öncesi de olabilir)
|
||||||
toast.push(
|
toast.push(
|
||||||
<Notification type="success" duration={2000}>
|
<Notification type="success" duration={2000}>
|
||||||
{translate('::ListForms.FormBilgileriKaydedildi')}
|
{translate('::ListForms.FormBilgileriKaydedildi')}
|
||||||
</Notification>,
|
</Notification>,
|
||||||
{ placement: 'top-end' },
|
{ placement: 'top-end' },
|
||||||
)
|
)
|
||||||
setSubmitting(false)
|
|
||||||
setTimeout(async () => {
|
|
||||||
getConfig(true)
|
|
||||||
|
|
||||||
navigate(ROUTES_ENUM.protected.admin.list.replace(':listFormCode', values.listFormCode))
|
|
||||||
}, 6000)
|
|
||||||
|
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
toast.push(<Notification title={error.message} type="danger" />, {
|
toast.push(<Notification title={error.message} type="danger" />, {
|
||||||
placement: 'top-end',
|
placement: 'top-end',
|
||||||
})
|
})
|
||||||
|
} finally {
|
||||||
|
setSubmitting(false)
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ import navigationIcon from '@/proxy/menus/navigation-icon.config'
|
||||||
import { MenuItem } from '@/proxy/menus/menu'
|
import { MenuItem } from '@/proxy/menus/menu'
|
||||||
import { MenuService } from '@/services/menu.service'
|
import { MenuService } from '@/services/menu.service'
|
||||||
import { Field, FieldProps, FormikErrors, FormikTouched } from 'formik'
|
import { Field, FieldProps, FormikErrors, FormikTouched } from 'formik'
|
||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useMemo, useState } from 'react'
|
||||||
import CreatableSelect from 'react-select/creatable'
|
import CreatableSelect from 'react-select/creatable'
|
||||||
import {
|
import {
|
||||||
FaArrowRight,
|
FaArrowRight,
|
||||||
|
|
@ -411,7 +411,15 @@ const WizardStep1 = ({
|
||||||
}: WizardStep1Props) => {
|
}: WizardStep1Props) => {
|
||||||
const [menuDialogOpen, setMenuDialogOpen] = useState(false)
|
const [menuDialogOpen, setMenuDialogOpen] = useState(false)
|
||||||
const [menuDialogParentCode, setMenuDialogParentCode] = useState('')
|
const [menuDialogParentCode, setMenuDialogParentCode] = useState('')
|
||||||
const [menuDialogInitialOrder, setMenuDialogInitialOrder] = useState(999)
|
|
||||||
|
const menuDialogInitialOrder = useMemo(() => {
|
||||||
|
const maxOrder = rawMenuItems.reduce((max, item) => {
|
||||||
|
const order = typeof item.order === 'number' ? item.order : 0
|
||||||
|
return order > max ? order : max
|
||||||
|
}, 0)
|
||||||
|
|
||||||
|
return maxOrder + 100
|
||||||
|
}, [rawMenuItems])
|
||||||
|
|
||||||
const step1Missing = [
|
const step1Missing = [
|
||||||
!wizardName && translate('::ListForms.Wizard.Step1.WizardName'),
|
!wizardName && translate('::ListForms.Wizard.Step1.WizardName'),
|
||||||
|
|
@ -419,6 +427,7 @@ const WizardStep1 = ({
|
||||||
!values.permissionGroupName && translate('::ListForms.Wizard.Step1.PermissionGroupName'),
|
!values.permissionGroupName && translate('::ListForms.Wizard.Step1.PermissionGroupName'),
|
||||||
!values.languageTextMenuEn && translate('::ListForms.Wizard.Step4.MenuEn'),
|
!values.languageTextMenuEn && translate('::ListForms.Wizard.Step4.MenuEn'),
|
||||||
!values.languageTextMenuTr && translate('::ListForms.Wizard.Step4.MenuTr'),
|
!values.languageTextMenuTr && translate('::ListForms.Wizard.Step4.MenuTr'),
|
||||||
|
!values.menuIcon && translate('::ListForms.Wizard.Step4.MenuIcon'),
|
||||||
].filter(Boolean) as string[]
|
].filter(Boolean) as string[]
|
||||||
const step1CanGo = step1Missing.length === 0
|
const step1CanGo = step1Missing.length === 0
|
||||||
|
|
||||||
|
|
@ -464,8 +473,6 @@ const WizardStep1 = ({
|
||||||
? findRootCode(rawMenuItems, values.menuParentCode)
|
? findRootCode(rawMenuItems, values.menuParentCode)
|
||||||
: '',
|
: '',
|
||||||
)
|
)
|
||||||
const selectedItem = rawMenuItems.find((i) => i.code === values.menuParentCode)
|
|
||||||
setMenuDialogInitialOrder(selectedItem?.order ?? 999)
|
|
||||||
setMenuDialogOpen(true)
|
setMenuDialogOpen(true)
|
||||||
}}
|
}}
|
||||||
className="flex items-center gap-1 px-2 py-0.5 text-xs rounded bg-green-500 text-white hover:bg-green-600"
|
className="flex items-center gap-1 px-2 py-0.5 text-xs rounded bg-green-500 text-white hover:bg-green-600"
|
||||||
|
|
|
||||||
|
|
@ -62,7 +62,7 @@ const WizardStep2 = ({
|
||||||
onNext,
|
onNext,
|
||||||
}: WizardStep2Props) => {
|
}: WizardStep2Props) => {
|
||||||
const step2Missing = [
|
const step2Missing = [
|
||||||
!values.listFormCode && translate('::ListForms.Wizard.Step2.ListFormCode'),
|
!values.listFormCode && translate('::App.Listform.ListformField.ListFormCode'),
|
||||||
!values.dataSourceCode && translate('::ListForms.Wizard.Step4.DataSource'),
|
!values.dataSourceCode && translate('::ListForms.Wizard.Step4.DataSource'),
|
||||||
!values.selectCommand && translate('::ListForms.Wizard.Step2.SelectCommand'),
|
!values.selectCommand && translate('::ListForms.Wizard.Step2.SelectCommand'),
|
||||||
!values.keyFieldName && translate('::ListForms.Wizard.Step4.KeyField'),
|
!values.keyFieldName && translate('::ListForms.Wizard.Step4.KeyField'),
|
||||||
|
|
@ -80,7 +80,7 @@ const WizardStep2 = ({
|
||||||
{/* ListForm Code + Data Source */}
|
{/* ListForm Code + Data Source */}
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-x-6">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-x-6">
|
||||||
<FormItem
|
<FormItem
|
||||||
label={translate('::ListForms.Wizard.Step2.ListFormCode')}
|
label={translate('::App.Listform.ListformField.ListFormCode')}
|
||||||
invalid={!!(errors.listFormCode && touched.listFormCode)}
|
invalid={!!(errors.listFormCode && touched.listFormCode)}
|
||||||
errorMessage={errors.listFormCode}
|
errorMessage={errors.listFormCode}
|
||||||
asterisk={true}
|
asterisk={true}
|
||||||
|
|
@ -102,7 +102,7 @@ const WizardStep2 = ({
|
||||||
</FormItem>
|
</FormItem>
|
||||||
|
|
||||||
<FormItem
|
<FormItem
|
||||||
label={translate('::ListForms.Wizard.Step2.DataSourceCode')}
|
label={translate('::App.Listform.ListformField.DataSourceCode')}
|
||||||
asterisk={true}
|
asterisk={true}
|
||||||
invalid={!!(errors.dataSourceCode && touched.dataSourceCode)}
|
invalid={!!(errors.dataSourceCode && touched.dataSourceCode)}
|
||||||
errorMessage={errors.dataSourceCode}
|
errorMessage={errors.dataSourceCode}
|
||||||
|
|
@ -112,7 +112,7 @@ const WizardStep2 = ({
|
||||||
<Select
|
<Select
|
||||||
field={field}
|
field={field}
|
||||||
form={form}
|
form={form}
|
||||||
placeholder={translate('::ListForms.Wizard.Step2.DataSourceCode')}
|
placeholder={translate('::App.Listform.ListformField.DataSourceCode')}
|
||||||
isClearable={true}
|
isClearable={true}
|
||||||
isLoading={isLoadingDataSource}
|
isLoading={isLoadingDataSource}
|
||||||
options={dataSourceList}
|
options={dataSourceList}
|
||||||
|
|
@ -137,7 +137,7 @@ const WizardStep2 = ({
|
||||||
|
|
||||||
{isDataSourceNew && (
|
{isDataSourceNew && (
|
||||||
<FormItem
|
<FormItem
|
||||||
label={translate('::ListForms.Wizard.Step2.ConnectionString')}
|
label={translate('::App.Listform.ListformField.ConnectionString')}
|
||||||
invalid={!!(errors.dataSourceConnectionString && touched.dataSourceConnectionString)}
|
invalid={!!(errors.dataSourceConnectionString && touched.dataSourceConnectionString)}
|
||||||
errorMessage={errors.dataSourceConnectionString}
|
errorMessage={errors.dataSourceConnectionString}
|
||||||
>
|
>
|
||||||
|
|
@ -145,7 +145,7 @@ const WizardStep2 = ({
|
||||||
type="text"
|
type="text"
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
name="dataSourceConnectionString"
|
name="dataSourceConnectionString"
|
||||||
placeholder={translate('::ListForms.Wizard.Step2.ConnectionString')}
|
placeholder={translate('::App.Listform.ListformField.ConnectionString')}
|
||||||
component={Input}
|
component={Input}
|
||||||
/>
|
/>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
|
|
@ -195,7 +195,7 @@ const WizardStep2 = ({
|
||||||
})),
|
})),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: translate('::ListForms.Wizard.Step2.Views') || 'Views',
|
label: translate('::App.Platform.Views') || 'Views',
|
||||||
options: dbObjects.views.map((v) => ({
|
options: dbObjects.views.map((v) => ({
|
||||||
label: v.objectName,
|
label: v.objectName,
|
||||||
value: v.objectName,
|
value: v.objectName,
|
||||||
|
|
@ -362,7 +362,7 @@ const WizardStep2 = ({
|
||||||
</FormItem>
|
</FormItem>
|
||||||
|
|
||||||
<FormItem
|
<FormItem
|
||||||
label={translate('::ListForms.Wizard.Step2.AllowEditing')}
|
label={translate('::ListForms.ListFormEdit.AllowEditing')}
|
||||||
invalid={!!(errors.allowEditing && touched.allowEditing)}
|
invalid={!!(errors.allowEditing && touched.allowEditing)}
|
||||||
errorMessage={errors.allowEditing}
|
errorMessage={errors.allowEditing}
|
||||||
>
|
>
|
||||||
|
|
@ -473,23 +473,23 @@ const WizardStep2 = ({
|
||||||
extra={
|
extra={
|
||||||
selectCommandColumns.length > 0 ? (
|
selectCommandColumns.length > 0 ? (
|
||||||
<div className="flex items-center gap-2 ml-3">
|
<div className="flex items-center gap-2 ml-3">
|
||||||
<button
|
<Button
|
||||||
type="button"
|
variant='solid'
|
||||||
onClick={() => onToggleAllColumns(true)}
|
onClick={() => onToggleAllColumns(true)}
|
||||||
className="text-xs px-2 py-0.5 rounded bg-indigo-500 text-white hover:bg-indigo-600"
|
className="text-xs px-2 py-0.5 rounded bg-indigo-500 text-white hover:bg-indigo-600"
|
||||||
>
|
>
|
||||||
{translate('::ListForms.Wizard.Step2.SelectAll') || 'Tümünü Seç'}
|
{translate('::ListForms.Wizard.Step2.SelectAll') || 'Tümünü Seç'}
|
||||||
</button>
|
</Button>
|
||||||
<button
|
<Button
|
||||||
type="button"
|
variant='default'
|
||||||
onClick={() => onToggleAllColumns(false)}
|
onClick={() => onToggleAllColumns(false)}
|
||||||
className="text-xs px-2 py-0.5 rounded border border-gray-300 dark:border-gray-600 text-gray-500 hover:text-red-500 hover:border-red-400"
|
className="text-xs px-2 py-0.5 rounded border border-gray-300 dark:border-gray-600 text-gray-500 hover:text-red-500 hover:border-red-400"
|
||||||
>
|
>
|
||||||
{translate('::ListForms.Wizard.Step2.ClearAll') || 'Tümünü Kaldır'}
|
{translate('::ListForms.Wizard.Step2.ClearAll') || 'Tümünü Kaldır'}
|
||||||
</button>
|
</Button>
|
||||||
<span className="text-xs text-gray-400">
|
<span className="text-xs text-gray-400">
|
||||||
{selectedColumns.size}/{selectCommandColumns.length}{' '}
|
{selectedColumns.size}/{selectCommandColumns.length}{' '}
|
||||||
{translate('::ListForms.Wizard.Step4.StatColumn')}
|
{translate('::App.Listform.ListformField.Column')}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
) : null
|
) : null
|
||||||
|
|
|
||||||
|
|
@ -84,7 +84,8 @@ const formatLabel = (text: string) => {
|
||||||
.join(" ");
|
.join(" ");
|
||||||
};
|
};
|
||||||
|
|
||||||
function newGroupItem(colName: string, sqlType = ''): WizardGroupItem {
|
function newGroupItem(colName: string, meta?: DatabaseColumnDto): WizardGroupItem {
|
||||||
|
const sqlType = meta?.dataType ?? ''
|
||||||
return {
|
return {
|
||||||
id: `${colName}_${Date.now()}`,
|
id: `${colName}_${Date.now()}`,
|
||||||
dataField: colName,
|
dataField: colName,
|
||||||
|
|
@ -92,7 +93,7 @@ function newGroupItem(colName: string, sqlType = ''): WizardGroupItem {
|
||||||
editorOptions: '',
|
editorOptions: '',
|
||||||
editorScript: '',
|
editorScript: '',
|
||||||
colSpan: 1,
|
colSpan: 1,
|
||||||
isRequired: false,
|
isRequired: meta?.isNullable === false,
|
||||||
turkishCaption: formatLabel(colName),
|
turkishCaption: formatLabel(colName),
|
||||||
englishCaption: formatLabel(colName),
|
englishCaption: formatLabel(colName),
|
||||||
}
|
}
|
||||||
|
|
@ -467,8 +468,7 @@ const WizardStep3 = ({
|
||||||
const availableColumns = [...selectedColumns].filter((c) => !placedColumns.has(c))
|
const availableColumns = [...selectedColumns].filter((c) => !placedColumns.has(c))
|
||||||
|
|
||||||
// ── Helpers ───────────────────────────────────────────────────────────────
|
// ── Helpers ───────────────────────────────────────────────────────────────
|
||||||
const colMeta = (name: string) =>
|
const colMeta = (name: string) => selectCommandColumns.find((c) => c.columnName === name)
|
||||||
selectCommandColumns.find((c) => c.columnName === name)?.dataType ?? ''
|
|
||||||
|
|
||||||
const addColumnToGroup = (colName: string, targetGroupId: string) => {
|
const addColumnToGroup = (colName: string, targetGroupId: string) => {
|
||||||
onGroupsChange(
|
onGroupsChange(
|
||||||
|
|
@ -679,7 +679,7 @@ const WizardStep3 = ({
|
||||||
<div className="sticky top-4">
|
<div className="sticky top-4">
|
||||||
<div className="flex items-center justify-between mb-2">
|
<div className="flex items-center justify-between mb-2">
|
||||||
<span className="text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wide">
|
<span className="text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wide">
|
||||||
{translate('::ListForms.Wizard.Step4.StatColumn')}
|
{translate('::App.Listform.ListformField.Column')}
|
||||||
</span>
|
</span>
|
||||||
<span className="text-xs text-gray-400">
|
<span className="text-xs text-gray-400">
|
||||||
{availableColumns.length}/{selectedColumns.size}
|
{availableColumns.length}/{selectedColumns.size}
|
||||||
|
|
|
||||||
|
|
@ -207,7 +207,7 @@ const WizardStep4 = ({
|
||||||
<Row label={translate('::ListForms.Wizard.Step4.MenuCode')} value={values.menuCode} />
|
<Row label={translate('::ListForms.Wizard.Step4.MenuCode')} value={values.menuCode} />
|
||||||
<Row label={translate('::ListForms.Wizard.Step4.MenuParent')} value={values.menuParentCode} />
|
<Row label={translate('::ListForms.Wizard.Step4.MenuParent')} value={values.menuParentCode} />
|
||||||
<Row label={translate('::ListForms.Wizard.Step4.PermissionGroup')} value={values.permissionGroupName} />
|
<Row label={translate('::ListForms.Wizard.Step4.PermissionGroup')} value={values.permissionGroupName} />
|
||||||
<Row label={translate('::ListForms.Wizard.Step4.Icon')} value={values.menuIcon} />
|
<Row label={translate('::App.Listform.ListformField.Icon')} value={values.menuIcon} />
|
||||||
<Row label={translate('::ListForms.Wizard.Step4.MenuTr')} value={values.languageTextMenuTr} />
|
<Row label={translate('::ListForms.Wizard.Step4.MenuTr')} value={values.languageTextMenuTr} />
|
||||||
<Row label={translate('::ListForms.Wizard.Step4.MenuEn')} value={values.languageTextMenuEn} />
|
<Row label={translate('::ListForms.Wizard.Step4.MenuEn')} value={values.languageTextMenuEn} />
|
||||||
<Row label={translate('::ListForms.Wizard.Step4.MenuParentTr')} value={values.languageTextMenuParentTr} />
|
<Row label={translate('::ListForms.Wizard.Step4.MenuParentTr')} value={values.languageTextMenuParentTr} />
|
||||||
|
|
@ -221,7 +221,7 @@ const WizardStep4 = ({
|
||||||
<Row label={translate('::ListForms.Wizard.Step4.DescTr')} value={values.languageTextDescTr} />
|
<Row label={translate('::ListForms.Wizard.Step4.DescTr')} value={values.languageTextDescTr} />
|
||||||
<Row label={translate('::ListForms.Wizard.Step4.DescEn')} value={values.languageTextDescEn} />
|
<Row label={translate('::ListForms.Wizard.Step4.DescEn')} value={values.languageTextDescEn} />
|
||||||
<Row label={translate('::ListForms.Wizard.Step4.DataSource')} value={values.dataSourceCode} />
|
<Row label={translate('::ListForms.Wizard.Step4.DataSource')} value={values.dataSourceCode} />
|
||||||
<Row label={translate('::ListForms.Wizard.Step4.ConnectionString')} value={values.dataSourceConnectionString} />
|
<Row label={translate('::App.Listform.ListformField.ConnectionString')} value={values.dataSourceConnectionString} />
|
||||||
<Row
|
<Row
|
||||||
label={translate('::ListForms.Wizard.Step4.CommandType')}
|
label={translate('::ListForms.Wizard.Step4.CommandType')}
|
||||||
value={
|
value={
|
||||||
|
|
@ -266,7 +266,7 @@ const WizardStep4 = ({
|
||||||
<Section
|
<Section
|
||||||
key={g.id}
|
key={g.id}
|
||||||
title={g.caption || `(${translate('::ListForms.Wizard.Step4.StatGroup')})`}
|
title={g.caption || `(${translate('::ListForms.Wizard.Step4.StatGroup')})`}
|
||||||
badge={`${g.items.length} ${translate('::ListForms.Wizard.Step4.StatField')} · ${g.colCount} ${translate('::ListForms.Wizard.Step4.StatColumn')}`}
|
badge={`${g.items.length} ${translate('::ListForms.Wizard.Step4.StatField')} · ${g.colCount} ${translate('::App.Listform.ListformField.Column')}`}
|
||||||
defaultOpen={false}
|
defaultOpen={false}
|
||||||
>
|
>
|
||||||
<div className="grid grid-cols-2 gap-2">
|
<div className="grid grid-cols-2 gap-2">
|
||||||
|
|
@ -307,7 +307,7 @@ const WizardStep4 = ({
|
||||||
{[
|
{[
|
||||||
{ label: translate('::ListForms.Wizard.Step4.StatGroup'), value: groups.length },
|
{ label: translate('::ListForms.Wizard.Step4.StatGroup'), value: groups.length },
|
||||||
{ label: translate('::ListForms.Wizard.Step4.StatField'), value: totalFields },
|
{ label: translate('::ListForms.Wizard.Step4.StatField'), value: totalFields },
|
||||||
{ label: translate('::ListForms.Wizard.Step4.StatColumn'), value: selectedColumns.size },
|
{ label: translate('::App.Listform.ListformField.Column'), value: selectedColumns.size },
|
||||||
].map((s) => (
|
].map((s) => (
|
||||||
<div
|
<div
|
||||||
key={s.label}
|
key={s.label}
|
||||||
|
|
|
||||||
|
|
@ -58,7 +58,7 @@ function FormTabCommands() {
|
||||||
<Th>{translate('::ListForms.ListFormEdit.CommandPosition')}</Th>
|
<Th>{translate('::ListForms.ListFormEdit.CommandPosition')}</Th>
|
||||||
<Th>{translate('::ListForms.ListFormEdit.CommandText')}</Th>
|
<Th>{translate('::ListForms.ListFormEdit.CommandText')}</Th>
|
||||||
<Th>{translate('::ListForms.ListFormEdit.CommandHint')}</Th>
|
<Th>{translate('::ListForms.ListFormEdit.CommandHint')}</Th>
|
||||||
<Th>{translate('::ListForms.ListFormEdit.CommandIcon')}</Th>
|
<Th>{translate('::App.Listform.ListformField.Icon')}</Th>
|
||||||
<Th>{translate('::ListForms.ListFormEdit.CommandAuthorizationType')}</Th>
|
<Th>{translate('::ListForms.ListFormEdit.CommandAuthorizationType')}</Th>
|
||||||
<Th>{translate('::ListForms.ListFormEdit.CommandUrlTarget')}</Th>
|
<Th>{translate('::ListForms.ListFormEdit.CommandUrlTarget')}</Th>
|
||||||
<Th>{translate('::ListForms.ListFormEdit.CommandUrl')}</Th>
|
<Th>{translate('::ListForms.ListFormEdit.CommandUrl')}</Th>
|
||||||
|
|
|
||||||
|
|
@ -104,12 +104,12 @@ function FormTabDatabaseDataSource(props: FormEditProps) {
|
||||||
|
|
||||||
const table = dbObjects.tables.find((t) => t.tableName === cmd)
|
const table = dbObjects.tables.find((t) => t.tableName === cmd)
|
||||||
if (table) { loadColumns(dsCode, table.schemaName, table.tableName); return }
|
if (table) { loadColumns(dsCode, table.schemaName, table.tableName); return }
|
||||||
const view = dbObjects.views.find((v) => v.viewName === cmd)
|
const view = dbObjects.views.find((v) => v.objectName === cmd)
|
||||||
if (view) { loadColumns(dsCode, view.schemaName, view.viewName); return }
|
if (view) { loadColumns(dsCode, view.schemaName, view.objectName); return }
|
||||||
const fn = dbObjects.functions.find((f) => f.functionName === cmd)
|
const fn = dbObjects.functions.find((f) => f.objectName === cmd)
|
||||||
if (fn) { loadColumns(dsCode, fn.schemaName, fn.functionName); return }
|
if (fn) { loadColumns(dsCode, fn.schemaName, fn.objectName); return }
|
||||||
const sp = dbObjects.storedProcedures.find((p) => p.procedureName === cmd)
|
const sp = dbObjects.storedProcedures.find((p) => p.objectName === cmd)
|
||||||
if (sp) { loadColumns(dsCode, sp.schemaName, sp.procedureName); return }
|
if (sp) { loadColumns(dsCode, sp.schemaName, sp.objectName); return }
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [dbObjects])
|
}, [dbObjects])
|
||||||
|
|
||||||
|
|
@ -167,7 +167,7 @@ function FormTabDatabaseDataSource(props: FormEditProps) {
|
||||||
/>
|
/>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
<FormItem
|
<FormItem
|
||||||
label={translate('::ListForms.ListFormEdit.DatabaseDataSourceCode')}
|
label={translate('::App.Listform.ListformField.DataSourceCode')}
|
||||||
invalid={errors.dataSourceCode && touched.dataSourceCode}
|
invalid={errors.dataSourceCode && touched.dataSourceCode}
|
||||||
errorMessage={errors.dataSourceCode}
|
errorMessage={errors.dataSourceCode}
|
||||||
>
|
>
|
||||||
|
|
@ -175,7 +175,7 @@ function FormTabDatabaseDataSource(props: FormEditProps) {
|
||||||
type="text"
|
type="text"
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
name="dataSourceCode"
|
name="dataSourceCode"
|
||||||
placeholder={translate('::ListForms.ListFormEdit.DatabaseDataSourceCode')}
|
placeholder={translate('::App.Listform.ListformField.DataSourceCode')}
|
||||||
>
|
>
|
||||||
{({ field, form }: FieldProps<DataSourceTypeEnum>) => (
|
{({ field, form }: FieldProps<DataSourceTypeEnum>) => (
|
||||||
<Select
|
<Select
|
||||||
|
|
@ -232,31 +232,31 @@ function FormTabDatabaseDataSource(props: FormEditProps) {
|
||||||
{
|
{
|
||||||
label: 'Views',
|
label: 'Views',
|
||||||
options: dbObjects.views.map((v) => ({
|
options: dbObjects.views.map((v) => ({
|
||||||
label: v.viewName,
|
label: v.objectName,
|
||||||
value: v.viewName,
|
value: v.objectName,
|
||||||
__type: SelectCommandTypeEnum.View,
|
__type: SelectCommandTypeEnum.View,
|
||||||
__schema: v.schemaName,
|
__schema: v.schemaName,
|
||||||
__rawName: v.viewName,
|
__rawName: v.objectName,
|
||||||
})),
|
})),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Functions',
|
label: 'Functions',
|
||||||
options: dbObjects.functions.map((f) => ({
|
options: dbObjects.functions.map((f) => ({
|
||||||
label: f.functionName,
|
label: f.objectName,
|
||||||
value: f.functionName,
|
value: f.objectName,
|
||||||
__type: SelectCommandTypeEnum.TableValuedFunction,
|
__type: SelectCommandTypeEnum.TableValuedFunction,
|
||||||
__schema: f.schemaName,
|
__schema: f.schemaName,
|
||||||
__rawName: f.functionName,
|
__rawName: f.objectName,
|
||||||
})),
|
})),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Stored Procedures',
|
label: 'Stored Procedures',
|
||||||
options: dbObjects.storedProcedures.map((p) => ({
|
options: dbObjects.storedProcedures.map((p) => ({
|
||||||
label: p.procedureName,
|
label: p.objectName,
|
||||||
value: p.procedureName,
|
value: p.objectName,
|
||||||
__type: SelectCommandTypeEnum.StoredProcedure,
|
__type: SelectCommandTypeEnum.StoredProcedure,
|
||||||
__schema: p.schemaName,
|
__schema: p.schemaName,
|
||||||
__rawName: p.procedureName,
|
__rawName: p.objectName,
|
||||||
})),
|
})),
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
@ -312,7 +312,7 @@ function FormTabDatabaseDataSource(props: FormEditProps) {
|
||||||
</Field>
|
</Field>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
<FormItem
|
<FormItem
|
||||||
label={translate('::ListForms.ListFormEdit.DatabaseDataSourceTableName')}
|
label={translate('::App.Listform.ListformField.TableName')}
|
||||||
invalid={errors.tableName && touched.tableName}
|
invalid={errors.tableName && touched.tableName}
|
||||||
errorMessage={errors.tableName}
|
errorMessage={errors.tableName}
|
||||||
>
|
>
|
||||||
|
|
@ -320,12 +320,12 @@ function FormTabDatabaseDataSource(props: FormEditProps) {
|
||||||
type="text"
|
type="text"
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
name="tableName"
|
name="tableName"
|
||||||
placeholder={translate('::ListForms.ListFormEdit.DatabaseDataSourceTableName')}
|
placeholder={translate('::App.Listform.ListformField.TableName')}
|
||||||
component={Input}
|
component={Input}
|
||||||
/>
|
/>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
<FormItem
|
<FormItem
|
||||||
label={translate('::ListForms.ListFormEdit.DatabaseDataSourceKeyFieldName')}
|
label={translate('::App.Listform.ListformField.KeyFieldName')}
|
||||||
invalid={errors.keyFieldName && touched.keyFieldName}
|
invalid={errors.keyFieldName && touched.keyFieldName}
|
||||||
errorMessage={errors.keyFieldName}
|
errorMessage={errors.keyFieldName}
|
||||||
extra={
|
extra={
|
||||||
|
|
@ -355,7 +355,7 @@ function FormTabDatabaseDataSource(props: FormEditProps) {
|
||||||
isLoadingColumns
|
isLoadingColumns
|
||||||
? translate('::Loading')
|
? translate('::Loading')
|
||||||
: translate(
|
: translate(
|
||||||
'::ListForms.ListFormEdit.DatabaseDataSourceKeyFieldName',
|
'::App.Listform.ListformField.KeyFieldName',
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
options={selectCommandColumns.map((c) => ({
|
options={selectCommandColumns.map((c) => ({
|
||||||
|
|
|
||||||
|
|
@ -154,7 +154,7 @@ function FormTabDatabaseDelete({
|
||||||
<Th>{translate('::ListForms.ListFormFieldEdit.FieldName')}</Th>
|
<Th>{translate('::ListForms.ListFormFieldEdit.FieldName')}</Th>
|
||||||
<Th>{translate('::ListForms.ListFormEdit.FieldDbType')}</Th>
|
<Th>{translate('::ListForms.ListFormEdit.FieldDbType')}</Th>
|
||||||
<Th>{translate('::ListForms.ListFormEdit.CustomValueType')}</Th>
|
<Th>{translate('::ListForms.ListFormEdit.CustomValueType')}</Th>
|
||||||
<Th>{translate('::ListForms.ListFormEdit.Value')}</Th>
|
<Th>{translate('::App.Listform.ListformField.Value')}</Th>
|
||||||
<Th>{translate('::ListForms.ListFormEdit.SqlQuery')}</Th>
|
<Th>{translate('::ListForms.ListFormEdit.SqlQuery')}</Th>
|
||||||
</Tr>
|
</Tr>
|
||||||
</THead>
|
</THead>
|
||||||
|
|
|
||||||
|
|
@ -152,7 +152,7 @@ function FormTabDatabaseInsert({
|
||||||
<Th>{translate('::ListForms.ListFormFieldEdit.FieldName')}</Th>
|
<Th>{translate('::ListForms.ListFormFieldEdit.FieldName')}</Th>
|
||||||
<Th>{translate('::ListForms.ListFormEdit.FieldDbType')}</Th>
|
<Th>{translate('::ListForms.ListFormEdit.FieldDbType')}</Th>
|
||||||
<Th>{translate('::ListForms.ListFormEdit.CustomValueType')}</Th>
|
<Th>{translate('::ListForms.ListFormEdit.CustomValueType')}</Th>
|
||||||
<Th>{translate('::ListForms.ListFormEdit.Value')}</Th>
|
<Th>{translate('::App.Listform.ListformField.Value')}</Th>
|
||||||
<Th>{translate('::ListForms.ListFormEdit.SqlQuery')}</Th>
|
<Th>{translate('::ListForms.ListFormEdit.SqlQuery')}</Th>
|
||||||
</Tr>
|
</Tr>
|
||||||
</THead>
|
</THead>
|
||||||
|
|
@ -251,7 +251,7 @@ function FormTabDatabaseInsert({
|
||||||
<Th>{translate('::ListForms.ListFormFieldEdit.FieldName')}</Th>
|
<Th>{translate('::ListForms.ListFormFieldEdit.FieldName')}</Th>
|
||||||
<Th>{translate('::ListForms.ListFormEdit.FieldDbType')}</Th>
|
<Th>{translate('::ListForms.ListFormEdit.FieldDbType')}</Th>
|
||||||
<Th>{translate('::ListForms.ListFormEdit.CustomValueType')}</Th>
|
<Th>{translate('::ListForms.ListFormEdit.CustomValueType')}</Th>
|
||||||
<Th>{translate('::ListForms.ListFormEdit.Value')}</Th>
|
<Th>{translate('::App.Listform.ListformField.Value')}</Th>
|
||||||
<Th>{translate('::ListForms.ListFormEdit.SqlQuery')}</Th>
|
<Th>{translate('::ListForms.ListFormEdit.SqlQuery')}</Th>
|
||||||
</Tr>
|
</Tr>
|
||||||
</THead>
|
</THead>
|
||||||
|
|
|
||||||
|
|
@ -107,7 +107,7 @@ function FormTabDatabaseSelect({
|
||||||
<Th>{translate('::ListForms.ListFormFieldEdit.FieldName')}</Th>
|
<Th>{translate('::ListForms.ListFormFieldEdit.FieldName')}</Th>
|
||||||
<Th>{translate('::ListForms.ListFormEdit.FieldDbType')}</Th>
|
<Th>{translate('::ListForms.ListFormEdit.FieldDbType')}</Th>
|
||||||
<Th>{translate('::ListForms.ListFormEdit.CustomValueType')}</Th>
|
<Th>{translate('::ListForms.ListFormEdit.CustomValueType')}</Th>
|
||||||
<Th>{translate('::ListForms.ListFormEdit.Value')}</Th>
|
<Th>{translate('::App.Listform.ListformField.Value')}</Th>
|
||||||
<Th>{translate('::ListForms.ListFormEdit.SqlQuery')}</Th>
|
<Th>{translate('::ListForms.ListFormEdit.SqlQuery')}</Th>
|
||||||
</Tr>
|
</Tr>
|
||||||
</THead>
|
</THead>
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue