Compare commits

..

No commits in common. "main" and "1.0.3" have entirely different histories.
main ... 1.0.3

109 changed files with 2554 additions and 2015 deletions

View file

@ -44,7 +44,6 @@ 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.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.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.TestBase/Sozsoft.Platform.TestBase.csproj" "test/Sozsoft.Platform.TestBase/"
RUN dotnet restore "src/Sozsoft.Platform.HttpApi.Host/Sozsoft.Platform.HttpApi.Host.csproj"
@ -52,7 +51,6 @@ RUN dotnet restore "src/Sozsoft.Platform.HttpApi.Host/Sozsoft.Platform.HttpApi.H
COPY . .
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.DbMigrator/Sozsoft.Platform.DbMigrator.csproj" -c Release -o /app/migrator
FROM mcr.microsoft.com/dotnet/aspnet:9.0-alpine AS final
@ -99,8 +97,4 @@ EXPOSE 443
WORKDIR /srv/app
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"]

View file

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Data;
using System.Text.Json;
using System.Text.Json.Serialization;
using Sozsoft.Platform.Enums;
using Volo.Abp.Application.Dtos;
namespace Sozsoft.Platform.ListForms;
@ -13,7 +14,6 @@ public class ColumnFormatDto : AuditedEntityDto<Guid>
public string FieldName { get; set; }
public string CaptionName { get; set; }
public string PlaceHolder { get; set; }
public bool ReadOnly { get; set; }
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

View file

@ -18,7 +18,6 @@ public class GridEditingDto
public bool AllowDeleting { get; set; } = false;
public bool AllowAllDeleting { get; set; } = false;
public bool AllowAdding { get; set; } = false;
public bool AllowDuplicate { get; set; } = false;
public bool UseIcons { get; set; } = false;
public bool ConfirmDelete { get; set; } = true;
/// <summary>Accepted Values: 'first' | 'last' | 'pageBottom' | 'pageTop' | 'viewportBottom' | 'viewportTop'

View file

@ -18,7 +18,7 @@ public interface IAuditLogAppService
}
[Authorize(AppCodes.IdentityManagement.AuditLogs)]
[Authorize(AppCodes.AuditLogs)]
public class AuditLogAppService
: CrudAppService<AuditLog, AuditLogDto, Guid>
, IAuditLogAppService

View file

@ -11,7 +11,7 @@ using Volo.Abp.Domain.Repositories;
namespace Sozsoft.Platform.GlobalSearchs;
[Authorize(PlatformConsts.AppCodes.Definitions.GlobalSearch)]
[Authorize(PlatformConsts.AppCodes.Settings.GlobalSearch)]
public class GlobalSearchAppService : PlatformAppService
{
private readonly IRepository<GlobalSearch, int> repo;

View file

@ -52,7 +52,6 @@ public class ListFormFieldsAppService : CrudAppService<
entity.FieldName = updateInput.FieldName;
entity.CultureName = updateInput.CultureName;
entity.CaptionName = updateInput.CaptionName;
entity.PlaceHolder = updateInput.PlaceHolder;
entity.BandName = updateInput.BandName;
entity.IsActive = updateInput.IsActive;
entity.Visible = updateInput.Visible;
@ -129,7 +128,6 @@ public class ListFormFieldsAppService : CrudAppService<
{
item.FieldName = input.FieldName;
item.CaptionName = input.CaptionName;
item.PlaceHolder = input.PlaceHolder;
item.BandName = input.BandName;
item.SourceDbType = input.SourceDbType;
item.Alignment = input.Alignment;
@ -253,7 +251,6 @@ public class ListFormFieldsAppService : CrudAppService<
{
field.BandName = sourceField.BandName;
field.CaptionName = sourceField.CaptionName;
field.PlaceHolder = sourceField.PlaceHolder;
field.SourceDbType = sourceField.SourceDbType;
}
if (input.CopiedFields.All || input.CopiedFields.Options)

View file

@ -83,7 +83,6 @@ public class ListFormQueryPreviewAppService : PlatformAppService
{
var authType = op switch
{
OperationEnum.Duplicate => AuthorizationTypeEnum.Create,
OperationEnum.Insert => AuthorizationTypeEnum.Create,
OperationEnum.Update => AuthorizationTypeEnum.Update,
OperationEnum.Delete => AuthorizationTypeEnum.Delete,
@ -105,7 +104,7 @@ public class ListFormQueryPreviewAppService : PlatformAppService
var (_, _, dataSourceType) = await dynamicDataManager.GetAsync(listForm.IsTenant, listForm.DataSourceCode);
return qManager.GenerateQuery(listForm, listFormFields, parameters, op, dataSourceType);
return qManager.GenerateQuery(listForm, parameters, op, dataSourceType);
}
}

View file

@ -6,8 +6,6 @@ using Sozsoft.Platform.Localization;
using Sozsoft.Platform.Queries;
using Microsoft.AspNetCore.Http;
using static Sozsoft.Platform.PlatformConsts;
using System.Collections.Generic;
using System.Text.Json;
namespace Sozsoft.Platform.ListForms.Select;
@ -16,18 +14,15 @@ public class ListFormDataAppService : PlatformAppService
private readonly IListFormAuthorizationManager authManager;
private readonly IQueryManager qManager;
private readonly IHttpContextAccessor httpContextAccessor;
private readonly IListFormSelectAppService listFormSelectAppService;
public ListFormDataAppService(
IListFormAuthorizationManager authManager,
IQueryManager qManager,
IHttpContextAccessor httpContextAccessor,
IListFormSelectAppService listFormSelectAppService)
IHttpContextAccessor httpContextAccessor)
{
this.authManager = authManager;
this.qManager = qManager;
this.httpContextAccessor = httpContextAccessor;
this.listFormSelectAppService = listFormSelectAppService;
LocalizationResource = typeof(PlatformResource);
}
@ -44,39 +39,6 @@ public class ListFormDataAppService : PlatformAppService
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)
{
// Izin logic process

View file

@ -289,7 +289,7 @@
{
"code": "App.Sender.Sms.PostaGuvercini.Url",
"nameKey": "App.Sender.Sms.PostaGuvercini.Url",
"descriptionKey": "App.Sender.Url.Description",
"descriptionKey": "App.Sender.Sms.PostaGuvercini.Url.Description",
"defaultValue": "https://www.postaguvercini.com/api_http",
"isVisibleToClients": false,
"providers": "T|G|D",
@ -337,7 +337,7 @@
{
"code": "App.Sender.WhatsApp.Url",
"nameKey": "App.Sender.WhatsApp.Url",
"descriptionKey": "App.Sender.Url.Description",
"descriptionKey": "App.Sender.WhatsApp.Url.Description",
"defaultValue": "https://graph.facebook.com/v21.0",
"isVisibleToClients": false,
"providers": "T|G|D",
@ -401,7 +401,7 @@
{
"code": "App.Sender.Rocket.Url",
"nameKey": "App.Sender.Rocket.Url",
"descriptionKey": "App.Sender.Url.Description",
"descriptionKey": "App.Sender.Rocket.Url.Description",
"defaultValue": "https://chat.sozsoft.com/api/v1",
"isVisibleToClients": false,
"providers": "G|D",

File diff suppressed because it is too large Load diff

View file

@ -903,7 +903,7 @@ public class ListFormSeeder_Administration : IDataSeedContributor, ITransientDep
CultureName = LanguageCodes.En,
SourceDbType = DbType.String,
FieldName = "Email",
CaptionName = "Abp.Account.EmailAddress",
CaptionName = "App.Listform.ListformField.Email",
Width = 300,
ListOrderNo = 2,
Visible = true,
@ -963,7 +963,7 @@ public class ListFormSeeder_Administration : IDataSeedContributor, ITransientDep
CultureName = LanguageCodes.En,
SourceDbType = DbType.String,
FieldName = "PhoneNumber",
CaptionName = "Abp.Identity.User.UserInformation.PhoneNumber",
CaptionName = "App.Listform.ListformField.PhoneNumber",
Width = 150,
ListOrderNo = 5,
Visible = true,
@ -1016,8 +1016,152 @@ public class ListFormSeeder_Administration : IDataSeedContributor, ITransientDep
}
#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
listFormName = AppCodes.IdentityManagement.AuditLogs;
listFormName = AppCodes.AuditLogs;
if (!await _listFormRepository.AnyAsync(a => a.ListFormCode == listFormName))
{
var listForm = await _listFormRepository.InsertAsync(
@ -1565,7 +1709,7 @@ public class ListFormSeeder_Administration : IDataSeedContributor, ITransientDep
SelectCommandType = SelectCommandTypeEnum.Table,
SelectCommand = TableNameResolver.GetFullTableName(nameof(TableNameEnum.Sector)),
KeyFieldName = "Id",
KeyFieldDbSourceType = DbType.Guid,
KeyFieldDbSourceType = DbType.String,
DefaultFilter = DefaultFilterJson,
SortMode = GridOptions.SortModeSingle,
FilterRowJson = DefaultFilterRowJson,
@ -1576,9 +1720,9 @@ public class ListFormSeeder_Administration : IDataSeedContributor, ITransientDep
ColumnOptionJson = DefaultColumnOptionJson(),
PermissionJson = DefaultPermissionJson(listFormName),
DeleteCommand = DefaultDeleteCommand(nameof(TableNameEnum.Sector)),
DeleteFieldsDefaultValueJson = DefaultDeleteFieldsDefaultValueJson(),
DeleteFieldsDefaultValueJson = DefaultDeleteFieldsDefaultValueJson(DbType.String),
PagerOptionJson = DefaultPagerOptionJson,
InsertFieldsDefaultValueJson = DefaultInsertFieldsDefaultValueJson(),
InsertFieldsDefaultValueJson = DefaultInsertFieldsDefaultValueJson(DbType.String, "Name"),
EditingOptionJson = DefaultEditingOptionJson(listFormName, 400, 200, true, true, true, true, false),
EditingFormJson = JsonSerializer.Serialize(new List<EditingFormDto>
{
@ -1635,7 +1779,7 @@ public class ListFormSeeder_Administration : IDataSeedContributor, ITransientDep
#endregion
#region WorkHour
listFormName = AppCodes.Restrictions.WorkHour;
listFormName = AppCodes.Definitions.WorkHour;
if (!await _listFormRepository.AnyAsync(a => a.ListFormCode == listFormName))
{
var listForm = await _listFormRepository.InsertAsync(
@ -1659,7 +1803,7 @@ public class ListFormSeeder_Administration : IDataSeedContributor, ITransientDep
SelectCommandType = SelectCommandTypeEnum.Table,
SelectCommand = TableNameResolver.GetFullTableName(nameof(TableNameEnum.WorkHour)),
KeyFieldName = "Id",
KeyFieldDbSourceType = DbType.Guid,
KeyFieldDbSourceType = DbType.String,
DefaultFilter = DefaultFilterJson,
SortMode = GridOptions.SortModeSingle,
FilterRowJson = DefaultFilterRowJson,
@ -1670,8 +1814,8 @@ public class ListFormSeeder_Administration : IDataSeedContributor, ITransientDep
ColumnOptionJson = DefaultColumnOptionJson(),
PermissionJson = DefaultPermissionJson(listFormName),
DeleteCommand = DefaultDeleteCommand(nameof(TableNameEnum.WorkHour)),
DeleteFieldsDefaultValueJson = DefaultDeleteFieldsDefaultValueJson(),
InsertFieldsDefaultValueJson = DefaultInsertFieldsDefaultValueJson(),
DeleteFieldsDefaultValueJson = DefaultDeleteFieldsDefaultValueJson(DbType.String),
InsertFieldsDefaultValueJson = DefaultInsertFieldsDefaultValueJson(DbType.String, "Name"),
PagerOptionJson = DefaultPagerOptionJson,
EditingOptionJson = DefaultEditingOptionJson(listFormName, 600, 600, true, true, true, true, false),
EditingFormJson = JsonSerializer.Serialize(new List<EditingFormDto>() {
@ -1890,150 +2034,6 @@ public class ListFormSeeder_Administration : IDataSeedContributor, ITransientDep
#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
}
}

View file

@ -426,7 +426,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
CultureName = LanguageCodes.En,
SourceDbType = DbType.String,
FieldName = "Email",
CaptionName = "Abp.Account.EmailAddress",
CaptionName = "App.Listform.ListformField.Email",
Width = 170,
ListOrderNo = 14,
Visible = true,
@ -479,7 +479,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
CultureName = LanguageCodes.En,
SourceDbType = DbType.String,
FieldName = "PhoneNumber",
CaptionName = "Abp.Identity.User.UserInformation.PhoneNumber",
CaptionName = "App.Listform.ListformField.PhoneNumber",
Width = 100,
ListOrderNo = 17,
Visible = true,
@ -684,7 +684,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
CultureName = LanguageCodes.En,
SourceDbType = DbType.String,
FieldName = "Code",
CaptionName = "App.Platform.Code",
CaptionName = "App.Listform.ListformField.Code",
Width = 100,
ListOrderNo = 2,
Visible = true,
@ -911,7 +911,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
CultureName = LanguageCodes.En,
SourceDbType = DbType.String,
FieldName = "Email",
CaptionName = "Abp.Account.EmailAddress",
CaptionName = "App.Listform.ListformField.Email",
Width = 170,
ListOrderNo = 13,
Visible = true,
@ -964,7 +964,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
CultureName = LanguageCodes.En,
SourceDbType = DbType.String,
FieldName = "PhoneNumber",
CaptionName = "Abp.Identity.User.UserInformation.PhoneNumber",
CaptionName = "App.Listform.ListformField.PhoneNumber",
Width = 100,
ListOrderNo = 16,
Visible = true,
@ -1016,7 +1016,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
#endregion
#region Global Search
listFormName = AppCodes.Definitions.GlobalSearch;
listFormName = AppCodes.Settings.GlobalSearch;
if (!await _listFormRepository.AnyAsync(a => a.ListFormCode == listFormName))
{
var listForm = await _listFormRepository.InsertAsync(
@ -1182,7 +1182,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
#endregion
#region AiBot
listFormName = AppCodes.Definitions.AiBot;
listFormName = AppCodes.AiBot;
if (!await _listFormRepository.AnyAsync(a => a.ListFormCode == listFormName))
{
var listForm = await _listFormRepository.InsertAsync(
@ -1218,10 +1218,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
PagerOptionJson = DefaultPagerOptionJson,
DeleteCommand = $"DELETE FROM \"{TableNameResolver.GetFullTableName(nameof(TableNameEnum.AiBot))}\" WHERE \"Id\"=@Id",
DeleteFieldsDefaultValueJson = JsonSerializer.Serialize(new FieldsDefaultValue[] {
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 }
new() { FieldName = "Id", FieldDbType = DbType.Int32, Value = "@ID", CustomValueType = FieldCustomValueTypeEnum.CustomKey }
}),
EditingOptionJson = DefaultEditingOptionJson(listFormName, 500, 450, true, true, true, true, false),
EditingFormJson = JsonSerializer.Serialize(new List<EditingFormDto>()
@ -2857,7 +2854,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
#endregion
#region SettingDefinition
listFormName = AppCodes.SettingDefinitions;
listFormName = AppCodes.Settings.SettingDefinitions;
if (!await _listFormRepository.AnyAsync(a => a.ListFormCode == listFormName))
{
var listForm = await _listFormRepository.InsertAsync(
@ -2946,7 +2943,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
CultureName = LanguageCodes.En,
SourceDbType = DbType.String,
FieldName = "Code",
CaptionName = "App.Platform.Code",
CaptionName = "App.Listform.ListformField.Code",
Width = 400,
ListOrderNo = 4,
Visible = true,
@ -3584,7 +3581,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
#endregion
#region Data Source
listFormName = AppCodes.DataSource;
listFormName = AppCodes.Listforms.DataSource;
if (!await _listFormRepository.AnyAsync(a => a.ListFormCode == listFormName))
{
var listForm = await _listFormRepository.InsertAsync(
@ -3658,7 +3655,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
CultureName = LanguageCodes.En,
SourceDbType = DbType.String,
FieldName = "Code",
CaptionName = "App.Platform.Code",
CaptionName = "App.Listform.ListformField.Code",
Width = 300,
ListOrderNo = 2,
Visible = true,
@ -4890,7 +4887,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
#endregion
#region Route
listFormName = AppCodes.Menus.Routes;
listFormName = AppCodes.Routes;
if (!await _listFormRepository.AnyAsync(a => a.ListFormCode == listFormName))
{
var listForm = await _listFormRepository.InsertAsync(
@ -5251,7 +5248,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
CultureName = LanguageCodes.En,
SourceDbType = DbType.String,
FieldName = "Code",
CaptionName = "App.Platform.Code",
CaptionName = "App.Listform.ListformField.Code",
Width = 300,
ListOrderNo = 2,
Visible = true,
@ -6508,7 +6505,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
CultureName = LanguageCodes.En,
SourceDbType = DbType.String,
FieldName = "PhoneNumber",
CaptionName = "Abp.Identity.User.UserInformation.PhoneNumber",
CaptionName = "App.Listform.ListformField.PhoneNumber",
Width = 100,
ListOrderNo = 11,
Visible = true,
@ -7375,7 +7372,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
CultureName = LanguageCodes.En,
SourceDbType = DbType.String,
FieldName = "Email",
CaptionName = "Abp.Account.EmailAddress",
CaptionName = "App.Listform.ListformField.Email",
Width = 250,
ListOrderNo = 4,
Visible = true,
@ -7393,7 +7390,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
CultureName = LanguageCodes.En,
SourceDbType = DbType.String,
FieldName = "PhoneNumber",
CaptionName = "Abp.Identity.User.UserInformation.PhoneNumber",
CaptionName = "App.Listform.ListformField.PhoneNumber",
Width = 100,
ListOrderNo = 5,
Visible = true,

View file

@ -21,6 +21,13 @@
"routeType": "public",
"authority": []
},
{
"key": "about",
"path": "/about",
"componentPath": "@/views/public/About",
"routeType": "public",
"authority": []
},
{
"key": "products",
"path": "/products",
@ -180,7 +187,7 @@
"path": "/admin/ai",
"componentPath": "@/views/ai/Assistant",
"routeType": "protected",
"authority": ["App.Definitions.AiBot.Asistant"]
"authority": ["App.AiBot.Asistant"]
},
{
"key": "admin.profile.general",
@ -445,22 +452,22 @@
},
{
"ParentCode": "App.Saas.Definitions",
"Code": "App.Definitions.AiBot",
"DisplayName": "App.Definitions.AiBot",
"Code": "App.AiBot",
"DisplayName": "App.AiBot",
"Order": 1,
"Url": "/admin/list/App.Definitions.AiBot",
"Url": "/admin/list/App.AiBot",
"Icon": "FcMindMap",
"RequiredPermissionName": "App.Definitions.AiBot",
"RequiredPermissionName": "App.AiBot",
"IsDisabled": false
},
{
"ParentCode": "App.Saas.Definitions",
"Code": "App.Definitions.GlobalSearch",
"DisplayName": "App.Definitions.GlobalSearch",
"Code": "App.Settings.GlobalSearch",
"DisplayName": "App.Settings.GlobalSearch",
"Order": 2,
"Url": "/admin/list/App.Definitions.GlobalSearch",
"Url": "/admin/list/App.Settings.GlobalSearch",
"Icon": "FcSearch",
"RequiredPermissionName": "App.Definitions.GlobalSearch",
"RequiredPermissionName": "App.Settings.GlobalSearch",
"IsDisabled": false
},
{
@ -545,12 +552,12 @@
},
{
"ParentCode": "App.Saas",
"Code": "App.SettingDefinitions",
"DisplayName": "App.SettingDefinitions",
"Code": "App.Settings.SettingDefinitions",
"DisplayName": "App.Settings.SettingDefinitions",
"Order": 5,
"Url": "/admin/list/App.SettingDefinitions",
"Url": "/admin/list/App.Settings.SettingDefinitions",
"Icon": "FcSupport",
"RequiredPermissionName": "App.SettingDefinitions",
"RequiredPermissionName": "App.Settings.SettingDefinitions",
"IsDisabled": false
},
{
@ -585,12 +592,12 @@
},
{
"ParentCode": "App.Saas",
"Code": "App.DataSource",
"DisplayName": "App.DataSource",
"Code": "App.Listforms.DataSource",
"DisplayName": "App.Listforms.DataSource",
"Order": 7,
"Url": "/admin/list/App.DataSource",
"Url": "/admin/list/App.Listforms.DataSource",
"Icon": "FcAcceptDatabase",
"RequiredPermissionName": "App.DataSource",
"RequiredPermissionName": "App.Listforms.DataSource",
"IsDisabled": false
},
{
@ -777,12 +784,12 @@
},
{
"ParentCode": "App.Menus",
"Code": "App.Menus.Routes",
"DisplayName": "App.Menus.Routes",
"Code": "App.Routes",
"DisplayName": "App.Routes",
"Order": 1,
"Url": "/admin/list/App.Menus.Routes",
"Url": "/admin/list/App.Routes",
"Icon": "FaSynagogue",
"RequiredPermissionName": "App.Menus.Routes",
"RequiredPermissionName": "App.Routes",
"IsDisabled": false
},
{
@ -938,40 +945,30 @@
"IsDisabled": false
},
{
"ParentCode": "App.Administration",
"Code": "App.Administration.Restrictions",
"DisplayName": "App.Restrictions",
"Order": 3,
"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",
"RequiredPermissionName": "App.Restrictions.WorkHour",
"IsDisabled": false
},
{
"ParentCode": "App.Administration.Restrictions",
"Code": "App.Restrictions.IpRestrictions",
"DisplayName": "App.Restrictions.IpRestrictions",
"ParentCode": "App.Administration.Definitions",
"Code": "App.Definitions.WorkHour",
"DisplayName": "App.Definitions.WorkHour",
"Order": 2,
"Url": "/admin/list/App.Restrictions.IpRestrictions",
"Url": "/admin/list/App.Definitions.WorkHour",
"Icon": "FcClock",
"RequiredPermissionName": "App.Definitions.WorkHour",
"IsDisabled": false
},
{
"ParentCode": "App.Administration.Definitions",
"Code": "App.IpRestrictions",
"DisplayName": "App.IpRestrictions",
"Order": 3,
"Url": "/admin/list/App.IpRestrictions",
"Icon": "FcNfcSign",
"RequiredPermissionName": "App.Restrictions.IpRestrictions",
"RequiredPermissionName": "App.IpRestrictions",
"IsDisabled": false
},
{
"ParentCode": "App.Administration",
"Code": "Abp.Identity",
"DisplayName": "Abp.Identity",
"Order": 4,
"Order": 3,
"Url": null,
"Icon": "FcConferenceCall",
"RequiredPermissionName": null,
@ -1039,19 +1036,19 @@
},
{
"ParentCode": "Abp.Identity",
"Code": "App.IdentityManagement.AuditLogs",
"DisplayName": "App.IdentityManagement.AuditLogs",
"Code": "App.AuditLogs",
"DisplayName": "App.AuditLogs",
"Order": 7,
"Url": "/admin/list/App.IdentityManagement.AuditLogs",
"Url": "/admin/list/App.AuditLogs",
"Icon": "FcMultipleInputs",
"RequiredPermissionName": "App.IdentityManagement.AuditLogs",
"RequiredPermissionName": "App.AuditLogs",
"IsDisabled": false
},
{
"ParentCode": "App.Administration",
"Code": "App.Reports.Management",
"DisplayName": "App.Reports.Management",
"Order": 5,
"Order": 4,
"Url": null,
"Icon": "FcDocument",
"RequiredPermissionName": null,
@ -1081,7 +1078,7 @@
"ParentCode": "App.Administration",
"Code": "App.Files",
"DisplayName": "App.Files",
"Order": 6,
"Order": 5,
"Url": "/admin/files",
"Icon": "FcFolder",
"RequiredPermissionName": "App.Files",
@ -1091,7 +1088,7 @@
"ParentCode": "App.Administration",
"Code": "App.Forum",
"DisplayName": "App.Forum",
"Order": 7,
"Order": 6,
"Url": "/admin/forum",
"Icon": "FcLink",
"RequiredPermissionName": "App.ForumManagement.Publish",

View file

@ -11,7 +11,6 @@ public enum OperationEnum
Delete,
DeleteBefore,
DeleteAfter,
Select,
Duplicate,
Select
}

View file

@ -290,80 +290,42 @@ public static class PlatformConsts
public static class AppCodes
{
public const string Home = Prefix.App + ".Home";
public const string Saas = Prefix.App + ".Saas";
public const string Branches = Prefix.App + ".Branches";
public static class Definitions
public static class Settings
{
public const string Default = Prefix.App + ".Definitions";
public const string AiBot = Default + ".AiBot";
public const string Default = Prefix.App + ".Settings";
public const string GlobalSearch = Default + ".GlobalSearch";
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 const string SettingDefinitions = Default + ".SettingDefinitions";
}
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 const string Default = Prefix.App + ".Languages";
public const string Language = Default + ".Language";
public const string LanguageText = Default + ".LanguageText";
}
public const string DataSource = Prefix.App + ".DataSource";
public const string Menus = Prefix.App + ".Menus";
public static class Listforms
{
public const string Default = Prefix.App + ".Listforms";
public const string Wizard = Default + ".Wizard";
public const string DataSource = Default + ".DataSource";
public const string Listform = Default + ".Listform";
public const string ListformField = Default + ".ListformField";
public const string Chart = Default + ".Chart";
}
public static class Notifications
{
public const string Default = Prefix.App + ".Notifications";
public const string NotificationRules = Default + ".NotificationRules";
public const string Notification = Default + ".Notification";
}
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 const string Default = Prefix.App + ".DeveloperKit";
public const string CustomEndpoints = Default + ".CustomEndpoints";
public const string Get = CustomEndpoints + ".Get";
@ -384,48 +346,63 @@ public static class PlatformConsts
public const string ViewCode = DynamicService + ".ViewCode";
}
}
//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 Blog = Prefix.App + ".Blog";
public const string Forum = Prefix.App + ".Forum";
public const string Administration = Prefix.App + ".Administration";
public const string Setting = Prefix.App + ".Setting";
public static class IdentityManagement
{
public const string Default = Prefix.App + ".IdentityManagement";
public const string ClaimTypes = Default + ".ClaimType";
public const string AuditLogs = Default + ".AuditLogs";
public const string ClaimTypes = Prefix.App + ".ClaimType";
public const string IpRestrictions = Prefix.App + ".IpRestrictions";
}
public static class Reports
public const string AuditLogs = Prefix.App + ".AuditLogs";
public static class Definitions
{
public const string Default = Prefix.App + ".Reports";
public const string Categories = Default + ".Categories";
public const string ReportTemplates = Default + ".ReportTemplates";
public const string ContactTag = Default + ".ContactTag";
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 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";
}
}

View file

@ -318,77 +318,70 @@ public static class SeedConsts
public static class AppCodes
{
public const string Home = Prefix.App + ".Home";
//Saas
public const string Saas = Prefix.App + ".Saas";
public const string Branches = Prefix.App + ".Branches";
public static class Definitions
public static class Settings
{
public const string Default = Prefix.App + ".Definitions";
public const string AiBot = Default + ".AiBot";
public const string Default = Prefix.App + ".Settings";
public const string GlobalSearch = Default + ".GlobalSearch";
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 SettingDefinitions = Default + ".SettingDefinitions";
}
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 const string AiBot = Prefix.App + ".AiBot";
public static class Languages
{
public const string Default = Prefix.App + ".Languages";
public const string Language = Default + ".Language";
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 DataSource = Prefix.App + ".DataSource";
public const string MenuGroup = Default + ".MenuGroup";
public const string Menu = Default + ".Menu";
public const string Manager = Default + ".Manager";
}
public static class Listforms
{
public const string Default = Prefix.App + ".Listforms";
public const string DataSource = Default + ".DataSource";
public const string Wizard = Default + ".Wizard";
public const string Listform = Default + ".Listform";
public const string ListformField = Default + ".ListformField";
public const string Chart = Default + ".Chart";
}
public static class Notifications
{
public const string Default = Prefix.App + ".Notifications";
public const string NotificationRules = Default + ".NotificationRules";
public const string Notification = Default + ".Notification";
}
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
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";
@ -404,43 +397,32 @@ public static class SeedConsts
public const string Demos = Prefix.App + ".Demos";
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
public const string Administration = Prefix.App + ".Administration";
public const string Setting = Prefix.App + ".Setting";
public static class IdentityManagement
{
public const string Default = Prefix.App + ".IdentityManagement";
public const string ClaimTypes = Default + ".ClaimType";
public const string AuditLogs = Default + ".AuditLogs";
public const string ClaimTypes = Prefix.App + ".ClaimType";
public const string IpRestrictions = Prefix.App + ".IpRestrictions";
}
public static class Reports
public const string AuditLogs = Prefix.App + ".AuditLogs";
public static class Definitions
{
public const string Default = Prefix.App + ".Reports";
public const string Default = Prefix.App + ".Definitions";
public const string Categories = Default + ".Categories";
public const string ReportTemplates = Default + ".ReportTemplates";
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 Sector = Default + ".Sector";
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 WorkHour = Default + ".WorkHour";
}
}

View file

@ -13,7 +13,6 @@ public class ListFormField : FullAuditedEntity<Guid>
public string CultureName { get; set; } // Bu tanım hangi dil için (“tr”, “en”)
public string FieldName { get; set; } // Kaynaktaki sutun adi
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? IsActive { get; set; } = true; // Sadece IsActive olan alanlar sorguya dahil edilir
public int? Width { get; set; } // Sütunun listedeki genişliği

View file

@ -7,6 +7,7 @@ using Microsoft.Extensions.Localization;
using Microsoft.Extensions.Primitives;
using System;
using System.Collections.Generic;
using System.Data;
using System.Globalization;
using System.Linq;
using System.Text.Json;
@ -94,7 +95,6 @@ public class ListFormManager : PlatformDomainService, IListFormManager
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)
)
{

View file

@ -52,7 +52,6 @@ public class DefaultValueManager : PlatformDomainService, IDefaultValueManager
var defaultFieldsJson = op switch
{
OperationEnum.Insert => listForm.InsertFieldsDefaultValueJson,
OperationEnum.Duplicate => listForm.InsertFieldsDefaultValueJson,
OperationEnum.Update => listForm.UpdateFieldsDefaultValueJson,
OperationEnum.Delete => listForm.DeleteFieldsDefaultValueJson,
OperationEnum.Select => listForm.FormFieldsDefaultValueJson,
@ -104,7 +103,6 @@ public class DefaultValueManager : PlatformDomainService, IDefaultValueManager
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)
)
{

View file

@ -27,7 +27,6 @@ public interface IQueryManager
string GenerateQuery(
ListForm listForm,
List<ListFormField> listFormFields,
Dictionary<string, object> parameters,
OperationEnum op,
DataSourceTypeEnum dataSourceType,
@ -78,20 +77,24 @@ public class QueryManager : PlatformDomainService, IQueryManager
var listFormFields = await listFormFieldManager.GetUserListFormFields(listFormCode);
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 sql = GenerateQuery(listForm, listFormFields, parameters, op, dataSourceType, keys);
var sql = GenerateQuery(listForm, parameters, op, dataSourceType, keys);
// Sorguyu calistir
if (!string.IsNullOrEmpty(sql))
{
// TODO: Log
if (op == OperationEnum.Insert || op == OperationEnum.Duplicate)
if (op == OperationEnum.Insert)
{
if (!string.IsNullOrEmpty(listForm.InsertBeforeCommand))
{
var beforeSql = GenerateQuery(listForm, listFormFields, parameters, OperationEnum.InsertBefore, dataSourceType, keys);
var beforeSql = GenerateQuery(listForm, parameters, OperationEnum.InsertBefore, dataSourceType, keys);
await dynamicDataRepository.ExecuteAsync(beforeSql, connectionString, parameters);
}
@ -99,7 +102,7 @@ public class QueryManager : PlatformDomainService, IQueryManager
if (!string.IsNullOrEmpty(listForm.InsertAfterCommand))
{
var afterSql = GenerateQuery(listForm, listFormFields, parameters, OperationEnum.InsertAfter, dataSourceType, keys);
var afterSql = GenerateQuery(listForm, parameters, OperationEnum.InsertAfter, dataSourceType, keys);
await dynamicDataRepository.ExecuteAsync(afterSql, connectionString, parameters);
}
@ -110,12 +113,12 @@ public class QueryManager : PlatformDomainService, IQueryManager
// Before komutlari varsa calistir
if (op == OperationEnum.Update && !string.IsNullOrEmpty(listForm.UpdateBeforeCommand))
{
var beforeSql = GenerateQuery(listForm, listFormFields, parameters, OperationEnum.UpdateBefore, dataSourceType, keys);
var beforeSql = GenerateQuery(listForm, parameters, OperationEnum.UpdateBefore, dataSourceType, keys);
await dynamicDataRepository.ExecuteAsync(beforeSql, connectionString, parameters);
}
else if (op == OperationEnum.Delete && !string.IsNullOrEmpty(listForm.DeleteBeforeCommand))
{
var beforeSql = GenerateQuery(listForm, listFormFields, parameters, OperationEnum.DeleteBefore, dataSourceType, keys);
var beforeSql = GenerateQuery(listForm, parameters, OperationEnum.DeleteBefore, dataSourceType, keys);
await dynamicDataRepository.ExecuteAsync(beforeSql, connectionString, parameters);
}
@ -125,12 +128,12 @@ public class QueryManager : PlatformDomainService, IQueryManager
// After komutlari varsa calistir
if (op == OperationEnum.Update && !string.IsNullOrEmpty(listForm.UpdateAfterCommand))
{
var afterSql = GenerateQuery(listForm, listFormFields, parameters, OperationEnum.UpdateAfter, dataSourceType, keys);
var afterSql = GenerateQuery(listForm, parameters, OperationEnum.UpdateAfter, dataSourceType, keys);
await dynamicDataRepository.ExecuteAsync(afterSql, connectionString, parameters);
}
else if (op == OperationEnum.Delete && !string.IsNullOrEmpty(listForm.DeleteAfterCommand))
{
var afterSql = GenerateQuery(listForm, listFormFields, parameters, OperationEnum.DeleteAfter, dataSourceType, keys);
var afterSql = GenerateQuery(listForm, parameters, OperationEnum.DeleteAfter, dataSourceType, keys);
await dynamicDataRepository.ExecuteAsync(afterSql, connectionString, parameters);
}
@ -143,7 +146,6 @@ public class QueryManager : PlatformDomainService, IQueryManager
public string GenerateQuery(
ListForm listForm,
List<ListFormField> listFormField,
Dictionary<string, object> parameters,
OperationEnum op,
DataSourceTypeEnum dataSourceType,
@ -151,7 +153,6 @@ public class QueryManager : PlatformDomainService, IQueryManager
{
var command = op switch
{
OperationEnum.Duplicate => listForm.InsertCommand,
OperationEnum.Insert => listForm.InsertCommand,
OperationEnum.InsertBefore => listForm.InsertBeforeCommand,
OperationEnum.InsertAfter => listForm.InsertAfterCommand,
@ -176,7 +177,7 @@ public class QueryManager : PlatformDomainService, IQueryManager
var fieldString = string.Join(',', parameters.Keys.Select(a => $"\"{a}\"").ToList());
var fieldParams = string.Join(',', parameters.Keys.Select(a => $"@{a}"));
if (op == OperationEnum.Insert || op == OperationEnum.Duplicate)
if (op == OperationEnum.Insert)
{
sql = dataSourceType switch
{

View file

@ -365,7 +365,6 @@ public class PlatformDbContext :
b.Property(a => a.CultureName).HasMaxLength(10).IsRequired();
b.Property(a => a.FieldName).IsRequired().HasMaxLength(128);
b.Property(a => a.CaptionName).HasMaxLength(256);
b.Property(a => a.PlaceHolder).HasMaxLength(256);
// Varsayılan değerler
b.Property(a => a.AllowSearch).HasDefaultValue(false);

View file

@ -13,7 +13,7 @@ using Volo.Abp.EntityFrameworkCore;
namespace Sozsoft.Platform.Migrations
{
[DbContext(typeof(PlatformDbContext))]
[Migration("20260330120142_Initial")]
[Migration("20260317181749_Initial")]
partial class Initial
{
/// <inheritdoc />
@ -2656,10 +2656,6 @@ namespace Sozsoft.Platform.Migrations
b.Property<string>("PivotSettingsJson")
.HasColumnType("text");
b.Property<string>("PlaceHolder")
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.Property<string>("RoleId")
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");

View file

@ -2031,7 +2031,6 @@ namespace Sozsoft.Platform.Migrations
CultureName = table.Column<string>(type: "nvarchar(10)", maxLength: 10, nullable: false),
FieldName = table.Column<string>(type: "nvarchar(128)", maxLength: 128, nullable: false),
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),
IsActive = table.Column<bool>(type: "bit", nullable: true, defaultValue: true),
Width = table.Column<int>(type: "int", nullable: true, defaultValue: 100),

View file

@ -2653,10 +2653,6 @@ namespace Sozsoft.Platform.Migrations
b.Property<string>("PivotSettingsJson")
.HasColumnType("text");
b.Property<string>("PlaceHolder")
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.Property<string>("RoleId")
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");

View file

@ -1,281 +0,0 @@
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;
}
}

View file

@ -5,6 +5,7 @@ using System.Threading.Tasks;
using Sozsoft.Platform.Enums;
using Sozsoft.Platform.DynamicServices;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.SignalR;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
@ -25,24 +26,6 @@ public class Program
.AddJsonFile($"appsettings.{Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ?? ""}.json", true)
.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>
{

View file

@ -24,10 +24,6 @@
"StringEncryption": {
"DefaultPassPhrase": "UQpiYfT79zRZ3yYH"
},
"Setup": {
"MigratorPath": "/srv/Sozsoft.Platform.DbMigrator",
"MigratorArgs": "--Seed=true"
},
"Serilog": {
"MinimumLevel": {
"Default": "Information"

View file

@ -1,16 +0,0 @@
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 });
}

View file

@ -1,6 +1,5 @@
import React from 'react';
import { ComponentInfo } from '../../proxy/developerKit/componentInfo';
import { Button } from '../ui';
interface ComponentSelectorProps {
components: ComponentInfo[];
@ -21,15 +20,13 @@ const ComponentSelector: React.FC<ComponentSelectorProps> = ({
<label className="block text-sm font-medium text-gray-700">
Select Component
</label>
<Button
variant='solid'
size="xs"
<button
onClick={onRefresh}
className="px-3 py-1 bg-blue-500 text-white text-xs rounded hover:bg-blue-600 transition-colors"
title="Refresh component list"
>
Refresh
</Button>
</button>
</div>
<select
value={selectedComponentId || ''}

View file

@ -2,7 +2,6 @@ import { useState, useEffect } from "react";
import TailwindModal from "./TailwindModal";
import { ComponentInfo, HookInfo, PropertyInfo } from "../../proxy/developerKit/componentInfo";
import { getComponentDefinition } from "./data/componentDefinitions";
import { Button } from "../ui";
interface PropertyPanelProps {
selectedComponent: ComponentInfo | null;
@ -526,9 +525,7 @@ const PropertyPanel: React.FC<PropertyPanelProps> = ({
</div>
</div>
{/* Sil Butonu */}
<Button
variant="solid"
size="xs"
<button
className="mr-2 px-3 py-1 rounded bg-red-500 text-white hover:bg-red-600 transition-colors text-sm"
onClick={() => {
if (selectedComponent) {
@ -544,32 +541,28 @@ const PropertyPanel: React.FC<PropertyPanelProps> = ({
title="Komponenti Sil"
>
Sil
</Button>
</button>
</div>
{/* Footer Action Buttons - her iki tabda da sabit */}
<div className="p-4 border-t">
<div className="flex gap-2">
<Button
size="xs"
variant="solid"
<button
onClick={
activeTab === "props"
? handleApplyPropChanges
: handleApplyHookChanges
}
disabled={activeTab === "props" ? !hasChanges : !hasHookChanges}
className={`flex-1 rounded-md font-medium transition-colors ${
className={`flex-1 px-4 py-2 rounded-md font-medium transition-colors ${
(activeTab === "props" ? hasChanges : hasHookChanges)
? "bg-green-500 text-white hover:bg-green-600"
: "bg-gray-300 text-gray-500 cursor-not-allowed"
}`}
>
Uygula
</Button>
<Button
size="xs"
variant="default"
</button>
<button
onClick={
activeTab === "props"
? handleResetChanges
@ -579,20 +572,20 @@ const PropertyPanel: React.FC<PropertyPanelProps> = ({
}
}
disabled={activeTab === "props" ? !hasChanges : !hasHookChanges}
className={`flex-1 rounded-md font-medium transition-colors ${
className={`flex-1 px-4 py-2 rounded-md font-medium transition-colors ${
(activeTab === "props" ? hasChanges : hasHookChanges)
? "bg-red-500 text-white hover:bg-red-600"
: "bg-gray-300 text-gray-500 cursor-not-allowed"
}`}
>
İptal
</Button>
</button>
</div>
</div>
{/* Content */}
{activeTab === "props" && (
<div className="flex-1 text-black overflow-y-auto p-4 max-h-[calc(100vh-200px)]">
<div className="flex-1 overflow-y-auto p-4 max-h-[calc(100vh-200px)]">
<h3 className="text-md font-medium text-gray-800 mb-4">Properties</h3>
{/* Properties */}
{properties.length > 0 && (

View file

@ -1,6 +1,5 @@
import { useState, useEffect } from 'react';
import { searchTailwindClasses, TAILWIND_CLASSES } from './data/tailwindClasses';
import { Button } from '../ui';
interface TailwindModalProps {
isOpen: boolean;
@ -62,7 +61,7 @@ const TailwindModal: React.FC<TailwindModalProps> = ({
</div>
{/* Search and Filter */}
<div className="p-4 border-b bg-gray-50 text-black">
<div className="p-4 border-b bg-gray-50">
<div className="flex gap-4 mb-4">
<input
type="text"
@ -107,15 +106,25 @@ const TailwindModal: React.FC<TailwindModalProps> = ({
key={`${className}-${index}`}
className="group relative"
>
<Button
variant='default'
<button
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"
>
<span className="font-mono text-xs text-gray-600">
{className}
</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>
@ -128,20 +137,18 @@ const TailwindModal: React.FC<TailwindModalProps> = ({
{filteredClasses.length} classes found
</div>
<div className="flex gap-2">
<Button
variant='default'
<button
onClick={() => onSelectClass('')}
className="px-4 py-2 bg-gray-200 text-gray-700 rounded hover:bg-gray-300 transition-colors"
>
Clear
</Button>
<Button
variant='solid'
</button>
<button
onClick={onClose}
className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600 transition-colors"
>
Close
</Button>
</button>
</div>
</div>
</div>

View file

@ -283,7 +283,7 @@ export const ImportDashboard: React.FC<ImportDashboardProps> = ({ gridDto }) =>
<thead className="bg-slate-100 sticky top-0">
<tr>
<th className="px-4 py-2 text-left text-xs font-medium text-slate-500 uppercase">
{translate('::App.Listform.ListformField.Column')}
{translate('::App.Listforms.ImportManager.Column')}
</th>
<th className="px-4 py-2 text-left text-xs font-medium text-slate-500 uppercase">
{translate('::ListForms.ListFormEdit.Type')}

View file

@ -60,11 +60,6 @@ const Layout = () => {
}, [routes, currentPath])
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
if (isAdminPath) {
return layouts[layoutType]
@ -83,7 +78,7 @@ const Layout = () => {
return AuthLayout
}
return PublicLayout
}, [isAdminPath, route, layoutType, authenticated, currentPath])
}, [isAdminPath, route, layoutType, authenticated])
if (loading) {
return (

View file

@ -155,7 +155,7 @@ export const Cart: React.FC<CartProps> = ({
<div className="border-t border-gray-200 p-6">
<div className="flex justify-between items-center mb-4">
<span className="text-lg font-semibold text-gray-900">
{translate('::App.Listform.ListformField.Total')}
{translate('::Public.payment.summary.total')}
</span>
<span className="text-xl font-bold text-blue-600">
{formatPrice(cartState.total)}

View file

@ -436,7 +436,7 @@ export const PaymentForm: React.FC<PaymentFormProps> = ({
</div>
)}
<div className="flex justify-between text-base font-bold pt-2 text-gray-900">
<span>{translate('::App.Listform.ListformField.Total')}</span>
<span>{translate('::Public.payment.summary.total')}</span>
<span className="text-blue-600">{formatPrice(finalTotal)}</span>
</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"
>
<FaArrowLeft className="w-4 h-4 mr-2" />
{translate('::Back')}
{translate('::Public.payment.buttons.back')}
</button>
<button
type="submit"

View file

@ -154,7 +154,7 @@ export const ProductCard: React.FC<ProductCardProps> = ({
</div>
{globalPeriod > 1 && (
<div className="text-lg font-semibold text-blue-600 mt-1">
{translate('::App.Listform.ListformField.Total')} {formatPrice(getTotalPrice())}
{translate('::Public.payment.summary.total')} {formatPrice(getTotalPrice())}
<span className="text-sm font-normal text-gray-500 ml-1">{getPeriodText()}</span>
</div>
)}

View file

@ -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"
>
<FaArrowLeft className="w-4 h-4 mr-2" />
{translate('::Back')}
{translate('::Public.payment.buttons.back')}
</button>
<button
type="submit"

View file

@ -1,5 +1,4 @@
import Tooltip from '@/components/ui/Tooltip'
import { AI_ASSISTANT } from '@/constants/permission.constant'
import { ROUTES_ENUM } from '@/routes/route.constant'
import { useLocalization } from '@/utils/hooks/useLocalization'
import { usePermission } from '@/utils/hooks/usePermission'
@ -11,14 +10,14 @@ const AiAssistant = () => {
const navigate = useNavigate()
const { checkPermissions } = usePermission()
const canViewAi = checkPermissions([AI_ASSISTANT])
const canViewAi = checkPermissions(['App.AiBot.Asistant'])
if (!canViewAi) {
return null
}
return (
<Tooltip title={translate('::' + AI_ASSISTANT)}>
<Tooltip title={translate('::App.AiBot.Asistant')}>
<div
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"

View file

@ -8,43 +8,18 @@ import { useSetting } from '@/utils/hooks/useSetting'
import useTabFocus from '@/utils/hooks/useTabFocus'
import { useEffect } from 'react'
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
const Theme = (props: CommonProps) => {
// ABP App Config'i uygulama acilirken al
const { getConfig } = useStoreActions((a) => a.abpConfig)
const { setSetupMode } = useStoreActions((a) => a.base.common)
const navigate = useNavigate()
const location = useLocation()
useEffect(() => {
if (!didInit) {
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)
}
})
.catch(() => {
getConfig(false)
})
}
}, [])
function getThemeStyle() {

View file

@ -1,2 +1 @@
export const GLOBAL_SEARCH = 'App.Definitions.GlobalSearch'
export const AI_ASSISTANT = 'App.Definitions.AiBot.Asistant'
export const GLOBAL_SEARCH = 'App.Settings.GlobalSearch'

View file

@ -196,7 +196,6 @@ export interface ColumnFilterDto {
export interface ColumnFormatDto extends AuditedEntityDto<string> {
fieldName?: string
captionName?: string
placeHolder?: string
readOnly: boolean
visible: boolean
isActive: boolean
@ -445,7 +444,6 @@ export interface GridEditingDto {
allowDeleting: boolean
allowAllDeleting: boolean
allowAdding: boolean
allowDuplicate: boolean
useIcons: boolean
confirmDelete: boolean
newRowPosition?: NewRowPosition

View file

@ -1,6 +1,6 @@
// DynamicRouter.tsx
import React from 'react'
import { Routes, Route, Navigate, useLocation } from 'react-router-dom'
import { Routes, Route, Navigate } from 'react-router-dom'
import { mapDynamicRoutes } from './dynamicRouteLoader'
import { useDynamicRoutes } from './dynamicRoutesContext'
import { useComponents } from '@/contexts/ComponentContext'
@ -13,18 +13,15 @@ import { hasSubdomain } from '@/utils/subdomain'
// AccessDenied ve NotFound'u dinamiklikten çıkarıyoruz
const AccessDenied = React.lazy(() => import('@/views/AccessDenied'))
const NotFound = React.lazy(() => import('@/views/NotFound'))
const DatabaseSetup = React.lazy(() => import('@/views/setup/DatabaseSetup'))
export const DynamicRouter: React.FC = () => {
const { routes, loading, error } = useDynamicRoutes()
const { registeredComponents, renderComponent, isComponentRegistered } = useComponents()
const location = useLocation()
const dynamicRoutes = React.useMemo(() => mapDynamicRoutes(routes), [routes])
// /setup path'inde loading bekleme — setup route her zaman erişilebilir olmalı
if (loading && location.pathname !== '/setup') return <div>Loading...</div>
if (error && location.pathname !== '/setup') return <div>Hata: {error}</div>
if (loading) return <div>Loading...</div>
if (error) return <div>Hata: {error}</div>
return (
<Routes>
@ -129,16 +126,6 @@ export const DynamicRouter: React.FC = () => {
</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>
)
}

View file

@ -19,7 +19,6 @@ export const useDynamicRoutes = () => {
export const DynamicRoutesProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const extraProperties = useStoreState((state) => state.abpConfig?.config?.extraProperties)
const setupMode = useStoreState((state) => state.base.common.setupMode)
const [routes, setRoutes] = useState<RouteDto[]>([])
const [loading, setLoading] = useState(true)
const [error, setError] = useState<string | null>(null)
@ -49,12 +48,8 @@ export const DynamicRoutesProvider: React.FC<{ children: React.ReactNode }> = ({
useEffect(() => {
if (extraProperties) {
loadRoutesFromConfig()
} else if (setupMode) {
// Veritabanı mevcut değil — setup modunda loading'i kapat
setLoading(false)
setRoutes([])
}
}, [extraProperties, setupMode])
}, [extraProperties])
return (
<DynamicRoutesContext.Provider value={{ routes, loading, error, reload: loadRoutesFromConfig }}>

View file

@ -1,5 +1,4 @@
export const ROUTES_ENUM = {
setup: '/setup',
public: {
home: '/home',
about: '/about',

View file

@ -5,13 +5,10 @@ import {
} from '../proxy/config/models'
import apiService from './api.service'
export const applicationConfigurationUrl = (includeLocalizationResources: boolean) =>
`/api/abp/application-configuration?includeLocalizationResources=${includeLocalizationResources}`
export const getAppConfig = (includeLocalizationResources: boolean) =>
apiService.fetchData<ApplicationConfigurationDto>({
method: 'GET',
url: applicationConfigurationUrl(includeLocalizationResources),
url: `/api/abp/application-configuration?includeLocalizationResources=${includeLocalizationResources}`,
})
export const getLocalizations = ({

View file

@ -0,0 +1,573 @@
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
}
}

View file

@ -0,0 +1,358 @@
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()
}
}
})
})
}
}

View file

@ -1,21 +0,0 @@
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`
}

View file

@ -13,7 +13,6 @@ export interface BaseStoreModel {
common: {
currentRouteKey: string
tabHasFocus: boolean
setupMode: boolean /** Veritabanı mevcut değilse true — setup sayfasına yönlendirme için */
}
messages: {
errors: StoreError[]
@ -25,7 +24,6 @@ export interface BaseStoreActions {
common: {
setCurrentRouteKey: Action<BaseStoreModel['common'], string>
setTabHasFocus: Action<BaseStoreModel['common'], boolean>
setSetupMode: Action<BaseStoreModel['common'], boolean>
}
messages: {
addError: Action<BaseStoreModel['messages'], StoreError>
@ -38,12 +36,12 @@ export interface BaseStoreActions {
export type BaseModel = BaseStoreModel & BaseStoreActions
const initialState: BaseStoreModel = {
common: { currentRouteKey: '', tabHasFocus: false, setupMode: false },
common: { currentRouteKey: '', tabHasFocus: false },
messages: {
errors: [],
// success: [],
warning: [],
}
},
}
export const baseModel: BaseModel = {
@ -55,9 +53,6 @@ export const baseModel: BaseModel = {
setTabHasFocus: action((state, payload) => {
state.tabHasFocus = payload
}),
setSetupMode: action((state, payload) => {
state.setupMode = payload
}),
},
messages: {
...initialState.messages,

View file

@ -1,4 +1,3 @@
import { Button } from '@/components/ui'
import { APP_NAME } from '@/constants/app.constant'
import { ROUTES_ENUM } from '@/routes/route.constant'
import { useLocalization } from '@/utils/hooks/useLocalization'
@ -28,15 +27,14 @@ const NotFoundPage = () => {
{translate('::Public.notFound.message')}
</p>
<div className="flex items-center justify-center font-inter">
<Button
variant='solid'
<button
onClick={() =>
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"
>
{translate('::Public.notFound.button')}
</Button>
</button>
</div>
</div>
)

View file

@ -671,14 +671,14 @@ const FileManager = () => {
await fileManagementService.copyItems(itemIds, currentFolderId, selectedTenant?.id)
await fetchItems(currentFolderId)
toast.push(
<Notification title={translate('::App.Platform.Success')} type="success">
<Notification title="Success" type="success">
{itemIds.length} item(s) copied successfully
</Notification>,
)
} catch (error) {
console.error('Copy failed:', error)
toast.push(
<Notification title={translate('::App.Platform.Error')} type="danger">
<Notification title="Error" type="danger">
Failed to copy items
</Notification>,
)
@ -704,14 +704,14 @@ const FileManager = () => {
localStorage.removeItem('fileManager_clipboard')
setHasClipboardData(false)
toast.push(
<Notification title={translate('::App.Platform.Success')} type="success">
<Notification title="Success" type="success">
{itemIds.length} item(s) moved successfully
</Notification>,
)
} catch (error) {
console.error('Move failed:', error)
toast.push(
<Notification title={translate('::App.Platform.Error')} type="danger">
<Notification title="Error" type="danger">
Failed to move items
</Notification>,
)

View file

@ -2,7 +2,6 @@ import { forwardRef } from 'react'
import classNames from 'classnames'
import { FaChevronRight, FaFolder, FaHome } from 'react-icons/fa'
import type { BreadcrumbItem } from '@/types/fileManagement'
import { Button } from '@/components/ui'
export interface BreadcrumbProps {
items: BreadcrumbItem[]
@ -18,8 +17,7 @@ const Breadcrumb = forwardRef<HTMLDivElement, BreadcrumbProps>((props, ref) => {
{items.map((item, index) => (
<div key={item.path} className="flex items-center">
{index > 0 && <FaChevronRight className="mx-2 h-4 w-4 text-gray-400" />}
<Button
size="xs"
<button
onClick={() => onNavigate(item)}
className={classNames(
'flex items-center px-2 py-1 rounded hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors',
@ -35,7 +33,7 @@ const Breadcrumb = forwardRef<HTMLDivElement, BreadcrumbProps>((props, ref) => {
<FaFolder className="h-4 w-4 mr-1" />
)}
<span className="truncate max-w-32">{item.name}</span>
</Button>
</button>
</div>
))}
</div>

View file

@ -415,7 +415,7 @@ const Wizard = () => {
<Steps.Item
title={translate('::ListForms.Wizard.ListFormFields') || 'List Form Fields'}
/>
<Steps.Item title={translate('::App.Platform.Deploy') || 'Deploy'} />
<Steps.Item title={translate('::ListForms.Wizard.Deploy') || 'Deploy'} />
</Steps>
</div>

View file

@ -62,7 +62,7 @@ const WizardStep2 = ({
onNext,
}: WizardStep2Props) => {
const step2Missing = [
!values.listFormCode && translate('::App.Listform.ListformField.ListFormCode'),
!values.listFormCode && translate('::ListForms.Wizard.Step2.ListFormCode'),
!values.dataSourceCode && translate('::ListForms.Wizard.Step4.DataSource'),
!values.selectCommand && translate('::ListForms.Wizard.Step2.SelectCommand'),
!values.keyFieldName && translate('::ListForms.Wizard.Step4.KeyField'),
@ -80,7 +80,7 @@ const WizardStep2 = ({
{/* ListForm Code + Data Source */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-x-6">
<FormItem
label={translate('::App.Listform.ListformField.ListFormCode')}
label={translate('::ListForms.Wizard.Step2.ListFormCode')}
invalid={!!(errors.listFormCode && touched.listFormCode)}
errorMessage={errors.listFormCode}
asterisk={true}
@ -102,7 +102,7 @@ const WizardStep2 = ({
</FormItem>
<FormItem
label={translate('::App.Listform.ListformField.DataSourceCode')}
label={translate('::ListForms.Wizard.Step2.DataSourceCode')}
asterisk={true}
invalid={!!(errors.dataSourceCode && touched.dataSourceCode)}
errorMessage={errors.dataSourceCode}
@ -112,7 +112,7 @@ const WizardStep2 = ({
<Select
field={field}
form={form}
placeholder={translate('::App.Listform.ListformField.DataSourceCode')}
placeholder={translate('::ListForms.Wizard.Step2.DataSourceCode')}
isClearable={true}
isLoading={isLoadingDataSource}
options={dataSourceList}
@ -137,7 +137,7 @@ const WizardStep2 = ({
{isDataSourceNew && (
<FormItem
label={translate('::App.Listform.ListformField.ConnectionString')}
label={translate('::ListForms.Wizard.Step2.ConnectionString')}
invalid={!!(errors.dataSourceConnectionString && touched.dataSourceConnectionString)}
errorMessage={errors.dataSourceConnectionString}
>
@ -145,7 +145,7 @@ const WizardStep2 = ({
type="text"
autoComplete="off"
name="dataSourceConnectionString"
placeholder={translate('::App.Listform.ListformField.ConnectionString')}
placeholder={translate('::ListForms.Wizard.Step2.ConnectionString')}
component={Input}
/>
</FormItem>
@ -195,7 +195,7 @@ const WizardStep2 = ({
})),
},
{
label: translate('::App.Platform.Views') || 'Views',
label: translate('::ListForms.Wizard.Step2.Views') || 'Views',
options: dbObjects.views.map((v) => ({
label: v.objectName,
value: v.objectName,
@ -362,7 +362,7 @@ const WizardStep2 = ({
</FormItem>
<FormItem
label={translate('::ListForms.ListFormEdit.AllowEditing')}
label={translate('::ListForms.Wizard.Step2.AllowEditing')}
invalid={!!(errors.allowEditing && touched.allowEditing)}
errorMessage={errors.allowEditing}
>
@ -473,23 +473,23 @@ const WizardStep2 = ({
extra={
selectCommandColumns.length > 0 ? (
<div className="flex items-center gap-2 ml-3">
<Button
variant='solid'
<button
type="button"
onClick={() => onToggleAllColumns(true)}
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ç'}
</Button>
<Button
variant='default'
</button>
<button
type="button"
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"
>
{translate('::ListForms.Wizard.Step2.ClearAll') || 'Tümünü Kaldır'}
</Button>
</button>
<span className="text-xs text-gray-400">
{selectedColumns.size}/{selectCommandColumns.length}{' '}
{translate('::App.Listform.ListformField.Column')}
{translate('::ListForms.Wizard.Step4.StatColumn')}
</span>
</div>
) : null

View file

@ -679,7 +679,7 @@ const WizardStep3 = ({
<div className="sticky top-4">
<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">
{translate('::App.Listform.ListformField.Column')}
{translate('::ListForms.Wizard.Step4.StatColumn')}
</span>
<span className="text-xs text-gray-400">
{availableColumns.length}/{selectedColumns.size}

View file

@ -207,7 +207,7 @@ const WizardStep4 = ({
<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.PermissionGroup')} value={values.permissionGroupName} />
<Row label={translate('::App.Listform.ListformField.Icon')} value={values.menuIcon} />
<Row label={translate('::ListForms.Wizard.Step4.Icon')} value={values.menuIcon} />
<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.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.DescEn')} value={values.languageTextDescEn} />
<Row label={translate('::ListForms.Wizard.Step4.DataSource')} value={values.dataSourceCode} />
<Row label={translate('::App.Listform.ListformField.ConnectionString')} value={values.dataSourceConnectionString} />
<Row label={translate('::ListForms.Wizard.Step4.ConnectionString')} value={values.dataSourceConnectionString} />
<Row
label={translate('::ListForms.Wizard.Step4.CommandType')}
value={
@ -266,7 +266,7 @@ const WizardStep4 = ({
<Section
key={g.id}
title={g.caption || `(${translate('::ListForms.Wizard.Step4.StatGroup')})`}
badge={`${g.items.length} ${translate('::ListForms.Wizard.Step4.StatField')} · ${g.colCount} ${translate('::App.Listform.ListformField.Column')}`}
badge={`${g.items.length} ${translate('::ListForms.Wizard.Step4.StatField')} · ${g.colCount} ${translate('::ListForms.Wizard.Step4.StatColumn')}`}
defaultOpen={false}
>
<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.StatField'), value: totalFields },
{ label: translate('::App.Listform.ListformField.Column'), value: selectedColumns.size },
{ label: translate('::ListForms.Wizard.Step4.StatColumn'), value: selectedColumns.size },
].map((s) => (
<div
key={s.label}

View file

@ -58,7 +58,7 @@ function FormTabCommands() {
<Th>{translate('::ListForms.ListFormEdit.CommandPosition')}</Th>
<Th>{translate('::ListForms.ListFormEdit.CommandText')}</Th>
<Th>{translate('::ListForms.ListFormEdit.CommandHint')}</Th>
<Th>{translate('::App.Listform.ListformField.Icon')}</Th>
<Th>{translate('::ListForms.ListFormEdit.CommandIcon')}</Th>
<Th>{translate('::ListForms.ListFormEdit.CommandAuthorizationType')}</Th>
<Th>{translate('::ListForms.ListFormEdit.CommandUrlTarget')}</Th>
<Th>{translate('::ListForms.ListFormEdit.CommandUrl')}</Th>

View file

@ -104,12 +104,12 @@ function FormTabDatabaseDataSource(props: FormEditProps) {
const table = dbObjects.tables.find((t) => t.tableName === cmd)
if (table) { loadColumns(dsCode, table.schemaName, table.tableName); return }
const view = dbObjects.views.find((v) => v.objectName === cmd)
if (view) { loadColumns(dsCode, view.schemaName, view.objectName); return }
const fn = dbObjects.functions.find((f) => f.objectName === cmd)
if (fn) { loadColumns(dsCode, fn.schemaName, fn.objectName); return }
const sp = dbObjects.storedProcedures.find((p) => p.objectName === cmd)
if (sp) { loadColumns(dsCode, sp.schemaName, sp.objectName); return }
const view = dbObjects.views.find((v) => v.viewName === cmd)
if (view) { loadColumns(dsCode, view.schemaName, view.viewName); return }
const fn = dbObjects.functions.find((f) => f.functionName === cmd)
if (fn) { loadColumns(dsCode, fn.schemaName, fn.functionName); return }
const sp = dbObjects.storedProcedures.find((p) => p.procedureName === cmd)
if (sp) { loadColumns(dsCode, sp.schemaName, sp.procedureName); return }
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [dbObjects])
@ -167,7 +167,7 @@ function FormTabDatabaseDataSource(props: FormEditProps) {
/>
</FormItem>
<FormItem
label={translate('::App.Listform.ListformField.DataSourceCode')}
label={translate('::ListForms.ListFormEdit.DatabaseDataSourceCode')}
invalid={errors.dataSourceCode && touched.dataSourceCode}
errorMessage={errors.dataSourceCode}
>
@ -175,7 +175,7 @@ function FormTabDatabaseDataSource(props: FormEditProps) {
type="text"
autoComplete="off"
name="dataSourceCode"
placeholder={translate('::App.Listform.ListformField.DataSourceCode')}
placeholder={translate('::ListForms.ListFormEdit.DatabaseDataSourceCode')}
>
{({ field, form }: FieldProps<DataSourceTypeEnum>) => (
<Select
@ -232,31 +232,31 @@ function FormTabDatabaseDataSource(props: FormEditProps) {
{
label: 'Views',
options: dbObjects.views.map((v) => ({
label: v.objectName,
value: v.objectName,
label: v.viewName,
value: v.viewName,
__type: SelectCommandTypeEnum.View,
__schema: v.schemaName,
__rawName: v.objectName,
__rawName: v.viewName,
})),
},
{
label: 'Functions',
options: dbObjects.functions.map((f) => ({
label: f.objectName,
value: f.objectName,
label: f.functionName,
value: f.functionName,
__type: SelectCommandTypeEnum.TableValuedFunction,
__schema: f.schemaName,
__rawName: f.objectName,
__rawName: f.functionName,
})),
},
{
label: 'Stored Procedures',
options: dbObjects.storedProcedures.map((p) => ({
label: p.objectName,
value: p.objectName,
label: p.procedureName,
value: p.procedureName,
__type: SelectCommandTypeEnum.StoredProcedure,
__schema: p.schemaName,
__rawName: p.objectName,
__rawName: p.procedureName,
})),
},
]
@ -312,7 +312,7 @@ function FormTabDatabaseDataSource(props: FormEditProps) {
</Field>
</FormItem>
<FormItem
label={translate('::App.Listform.ListformField.TableName')}
label={translate('::ListForms.ListFormEdit.DatabaseDataSourceTableName')}
invalid={errors.tableName && touched.tableName}
errorMessage={errors.tableName}
>
@ -320,12 +320,12 @@ function FormTabDatabaseDataSource(props: FormEditProps) {
type="text"
autoComplete="off"
name="tableName"
placeholder={translate('::App.Listform.ListformField.TableName')}
placeholder={translate('::ListForms.ListFormEdit.DatabaseDataSourceTableName')}
component={Input}
/>
</FormItem>
<FormItem
label={translate('::App.Listform.ListformField.KeyFieldName')}
label={translate('::ListForms.ListFormEdit.DatabaseDataSourceKeyFieldName')}
invalid={errors.keyFieldName && touched.keyFieldName}
errorMessage={errors.keyFieldName}
extra={
@ -355,7 +355,7 @@ function FormTabDatabaseDataSource(props: FormEditProps) {
isLoadingColumns
? translate('::Loading')
: translate(
'::App.Listform.ListformField.KeyFieldName',
'::ListForms.ListFormEdit.DatabaseDataSourceKeyFieldName',
)
}
options={selectCommandColumns.map((c) => ({

View file

@ -154,7 +154,7 @@ function FormTabDatabaseDelete({
<Th>{translate('::ListForms.ListFormFieldEdit.FieldName')}</Th>
<Th>{translate('::ListForms.ListFormEdit.FieldDbType')}</Th>
<Th>{translate('::ListForms.ListFormEdit.CustomValueType')}</Th>
<Th>{translate('::App.Listform.ListformField.Value')}</Th>
<Th>{translate('::ListForms.ListFormEdit.Value')}</Th>
<Th>{translate('::ListForms.ListFormEdit.SqlQuery')}</Th>
</Tr>
</THead>

View file

@ -152,7 +152,7 @@ function FormTabDatabaseInsert({
<Th>{translate('::ListForms.ListFormFieldEdit.FieldName')}</Th>
<Th>{translate('::ListForms.ListFormEdit.FieldDbType')}</Th>
<Th>{translate('::ListForms.ListFormEdit.CustomValueType')}</Th>
<Th>{translate('::App.Listform.ListformField.Value')}</Th>
<Th>{translate('::ListForms.ListFormEdit.Value')}</Th>
<Th>{translate('::ListForms.ListFormEdit.SqlQuery')}</Th>
</Tr>
</THead>
@ -251,7 +251,7 @@ function FormTabDatabaseInsert({
<Th>{translate('::ListForms.ListFormFieldEdit.FieldName')}</Th>
<Th>{translate('::ListForms.ListFormEdit.FieldDbType')}</Th>
<Th>{translate('::ListForms.ListFormEdit.CustomValueType')}</Th>
<Th>{translate('::App.Listform.ListformField.Value')}</Th>
<Th>{translate('::ListForms.ListFormEdit.Value')}</Th>
<Th>{translate('::ListForms.ListFormEdit.SqlQuery')}</Th>
</Tr>
</THead>

View file

@ -107,7 +107,7 @@ function FormTabDatabaseSelect({
<Th>{translate('::ListForms.ListFormFieldEdit.FieldName')}</Th>
<Th>{translate('::ListForms.ListFormEdit.FieldDbType')}</Th>
<Th>{translate('::ListForms.ListFormEdit.CustomValueType')}</Th>
<Th>{translate('::App.Listform.ListformField.Value')}</Th>
<Th>{translate('::ListForms.ListFormEdit.Value')}</Th>
<Th>{translate('::ListForms.ListFormEdit.SqlQuery')}</Th>
</Tr>
</THead>

View file

@ -154,7 +154,7 @@ function FormTabDatabaseUpdate({
<Th>{translate('::ListForms.ListFormFieldEdit.FieldName')}</Th>
<Th>{translate('::ListForms.ListFormEdit.FieldDbType')}</Th>
<Th>{translate('::ListForms.ListFormEdit.CustomValueType')}</Th>
<Th>{translate('::App.Listform.ListformField.Value')}</Th>
<Th>{translate('::ListForms.ListFormEdit.Value')}</Th>
<Th>{translate('::ListForms.ListFormEdit.SqlQuery')}</Th>
</Tr>
</THead>

View file

@ -76,7 +76,7 @@ function FormTabDetails(
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
<Card className="my-2" header="General">
<FormItem
label={translate('::App.Listform.ListformField.CultureName')}
label={translate('::ListForms.ListFormEdit.DetailsCultureName')}
invalid={errors.cultureName && touched.cultureName}
errorMessage={errors.cultureName}
>
@ -84,7 +84,7 @@ function FormTabDetails(
type="text"
autoComplete="off"
name="cultureName"
placeholder={translate('::App.Listform.ListformField.CultureName')}
placeholder={translate('::ListForms.ListFormEdit.DetailsCultureName')}
>
{({ field, form }: FieldProps<LanguageInfo>) => (
<Select

View file

@ -73,7 +73,7 @@ function FormTabEdit(props: FormEditProps & { listFormCode: string }) {
/>
</FormItem>
<FormItem
label={translate('::ListForms.ListFormEdit.AllowEditing')}
label={translate('::ListForms.ListFormEdit.EditingAllowEditing')}
invalid={
errors.editingOptionDto?.allowEditing &&
touched.editingOptionDto?.allowEditing
@ -82,7 +82,7 @@ function FormTabEdit(props: FormEditProps & { listFormCode: string }) {
>
<Field
name="editingOptionDto.allowEditing"
placeholder={translate('::ListForms.ListFormEdit.AllowEditing')}
placeholder={translate('::ListForms.ListFormEdit.EditingAllowEditing')}
component={Checkbox}
/>
</FormItem>
@ -118,20 +118,6 @@ function FormTabEdit(props: FormEditProps & { listFormCode: string }) {
/>
</FormItem>
<FormItem
label={translate('::ListForms.ListFormEdit.EditingAllowDuplicate')}
invalid={
errors.editingOptionDto?.allowDuplicate && touched.editingOptionDto?.allowDuplicate
}
errorMessage={errors.editingOptionDto?.allowDuplicate}
>
<Field
name="editingOptionDto.allowDuplicate"
placeholder={translate('::ListForms.ListFormEdit.EditingAllowDuplicate')}
component={Checkbox}
/>
</FormItem>
<FormItem
label={translate('::ListForms.ListFormEdit.EditingAllowDeleting')}
invalid={
@ -206,7 +192,7 @@ function FormTabEdit(props: FormEditProps & { listFormCode: string }) {
</FormItem>
<FormItem
label={translate('::ListForms.ListFormEdit.EditingMode')}
label={translate('::SidePanel.Mode')}
invalid={errors.editingOptionDto?.mode && touched.editingOptionDto?.mode}
errorMessage={errors.editingOptionDto?.mode}
>

View file

@ -54,7 +54,7 @@ function FormTabEditForm(props: { listFormCode: string }) {
}}
/>
</Th>
<Th>{translate('::App.Listform.ListformField.Order')}</Th>
<Th>{translate('::ListForms.ListFormEdit.EditingFormOrder')}</Th>
<Th>{translate('::ListForms.ListFormEdit.EditingFormItemType')}</Th>
<Th>{translate('::ListForms.ListFormEdit.DetailsTitle')}</Th>
<Th>{translate('::ListForms.ListFormEdit.EditingFormColumnCount')}</Th>

View file

@ -87,7 +87,7 @@ function FormTabGantt(props: FormEditProps) {
<Card className="mt-4">
<h5 className="mb-4">{translate('::ListForms.SchedulerOptions.BasicSettings')}</h5>
<FormItem
label={translate('::App.Listform.ListformField.KeyFieldName')}
label={translate('::ListForms.ListFormEdit.DatabaseDataSourceKeyFieldName')}
invalid={errors.ganttOptionDto?.keyExpr && touched.ganttOptionDto?.keyExpr}
errorMessage={errors.ganttOptionDto?.keyExpr}
>
@ -95,7 +95,7 @@ function FormTabGantt(props: FormEditProps) {
type="text"
name="ganttOptionDto.keyExpr"
placeholder={translate(
'::App.Listform.ListformField.KeyFieldName',
'::ListForms.ListFormEdit.DatabaseDataSourceKeyFieldName',
)}
>
{({ field, form }: FieldProps<SelectBoxOption>) => (

View file

@ -354,7 +354,7 @@ function FormTabScheduler(props: FormEditProps) {
<Field name="schedulerOptionDto.allowAdding" component={Checkbox} />
</FormItem>
<FormItem label={translate('::ListForms.ListFormEdit.AllowEditing')}>
<FormItem label={translate('::ListForms.SchedulerOptions.AllowEditing')}>
<Field name="schedulerOptionDto.allowEditing" component={Checkbox} />
</FormItem>

View file

@ -57,7 +57,7 @@ function FormTabSubForm() {
</Th>
<Th>{translate('::ListForms.ListFormEdit.SubFormsTabTitle')}</Th>
<Th>{translate('::ListForms.ListFormEdit.SubFormsTabType')}</Th>
<Th>{translate('::App.Listform.ListformField.ListFormCode')}</Th>
<Th>{translate('::ListForms.ListFormEdit.SubFormsCode')}</Th>
<Th>{translate('::ListForms.ListFormEdit.SubFormsIsRefresh')}</Th>
<Th>{translate('::ListForms.ListFormEdit.SubFormsRelation')}</Th>
</Tr>

View file

@ -82,14 +82,14 @@ function FormTabTree(props: FormEditProps) {
<Form>
<FormContainer size="sm">
<FormItem
label={translate('::App.Listform.ListformField.KeyFieldName')}
label={translate('::ListForms.ListFormEdit.DatabaseDataSourceKeyFieldName')}
invalid={errors.treeOptionDto?.keyExpr && touched.treeOptionDto?.keyExpr}
errorMessage={errors.treeOptionDto?.keyExpr}
>
<Field
type="text"
name="treeOptionDto.keyExpr"
placeholder={translate('::App.Listform.ListformField.KeyFieldName')}
placeholder={translate('::ListForms.ListFormEdit.DatabaseDataSourceKeyFieldName')}
>
{({ field, form }: FieldProps<SelectBoxOption>) => (
<Select

View file

@ -12,7 +12,6 @@ import { tooltipFormatListOptions } from '@/proxy/admin/list-form/options'
const schema = object().shape({
fieldName: string().required().max(100),
captionName: string(),
placeHolder: string(),
bandName: string(),
sourceDbType: number().required(),
})
@ -54,13 +53,6 @@ function FormFieldTabDetails({
>
<Field type="text" name="captionName" component={Input} />
</FormItem>
<FormItem
label={translate('::ListForms.ListFormFieldEdit.PlaceHolder')}
invalid={errors.placeHolder && touched.placeHolder}
errorMessage={errors.placeHolder}
>
<Field type="text" name="placeHolder" component={Input} />
</FormItem>
<FormItem
label={translate('::ListForms.ListFormFieldEdit.DetailsBandName')}
invalid={errors.bandName && touched.bandName}

View file

@ -78,7 +78,7 @@ function FormFieldTabJoinOptions({
</FormItem>
<FormItem
label={translate('::App.Listform.ListformField.TableName')}
label={translate('::ListForms.ListFormEdit.DatabaseDataSourceTableName')}
invalid={
errors.columnJoinTableDto?.tableName && touched.columnJoinTableDto?.tableName
}
@ -88,7 +88,7 @@ function FormFieldTabJoinOptions({
type="text"
autoComplete="off"
name="columnJoinTableDto.tableName"
placeholder={translate('::App.Listform.ListformField.TableName')}
placeholder={translate('::ListForms.ListFormEdit.DatabaseDataSourceTableName')}
component={Input}
/>
</FormItem>

View file

@ -101,7 +101,7 @@ function FormFieldTabOptions({
<FormItem label={translate('::ListForms.ListFormFieldEdit.HeaderingAllowSearch')}>
<Field name="allowSearch" component={Checkbox} />
</FormItem>
<FormItem label={translate('::ListForms.ListFormEdit.AllowEditing')}>
<FormItem label={translate('::ListForms.ListFormEdit.EditingAllowEditing')}>
<Field name="allowEditing" component={Checkbox} />
</FormItem>

View file

@ -340,7 +340,7 @@ function FormFields({
<Field type="text" name="cultureName">
{({ field, form }: FieldProps<LanguageInfo>) => (
<Select
placeholder={translate('::App.Listform.ListformField.CultureName')}
placeholder={translate('::ListForms.ListFormField.FilterCultureName')}
field={field}
form={form}
options={langOptions}
@ -593,7 +593,7 @@ function FormFields({
</FormItem>
<FormItem
label={translate('::App.Listform.ListformField.CultureName')}
label={translate('::ListForms.ListFormEdit.DetailsCultureName')}
invalid={errors.cultureName && touched.cultureName}
errorMessage={errors.cultureName}
asterisk={true}
@ -602,7 +602,7 @@ function FormFields({
type="text"
autoComplete="off"
name="cultureName"
placeholder={translate('::App.Listform.ListformField.CultureName')}
placeholder={translate('::ListForms.ListFormEdit.DetailsCultureName')}
>
{({ field, form }: FieldProps<LanguageInfo>) => (
<Select

View file

@ -204,7 +204,7 @@ function JsonRowOpDialogDatabase({
</Field>
</FormItem>
<FormItem
label={translate('::App.Listform.ListformField.Value')}
label={translate('::ListForms.ListFormEdit.Value')}
invalid={errors.value && touched.value}
errorMessage={errors.value}
>
@ -212,7 +212,7 @@ function JsonRowOpDialogDatabase({
type="text"
autoComplete="off"
name="value"
placeholder={translate('::App.Listform.ListformField.Value')}
placeholder={translate('::ListForms.ListFormEdit.Value')}
component={Input}
/>
</FormItem>

View file

@ -430,24 +430,24 @@ const OrganizationUnits = () => {
<div className="file-actions">
<div className="flex gap-1 folderFileActions">
<Button
<button
onClick={() => setIsMoveAllUsersOpen(true)}
title={translate('::Abp.Identity.OrganizationUnit.MoveAllUsers')}
>
<FaUserPlus size="20" color="#2d6da3" />
</Button>
<Button
</button>
<button
onClick={() => node.edit()}
title={translate('::Abp.Identity.OrganizationUnit.Rename')}
>
<FaEdit size="20" className="text-teal-900" />
</Button>
<Button
</button>
<button
onClick={() => setDeleteRow({ id: node.data.id, name: 'Organization Unit' })}
title={translate('::Delete')}
>
<FaTrashAlt size="20" className="text-red-500" />
</Button>
</button>
</div>
</div>
</div>

View file

@ -1,4 +1,3 @@
import { FaChevronRight, FaChevronDown } from 'react-icons/fa'
import Container from '@/components/shared/Container'
import { Button, Checkbox, Dialog, Input, Menu, toast } from '@/components/ui'
import { useConfig } from '@/components/ui/ConfigProvider'
@ -9,14 +8,13 @@ import {
PermissionGroupDto,
PermissionWithGroupName,
PermissionWithStyle,
UpdatePermissionDto,
} from '@/proxy/admin/models'
import { getPermissions, getRoles, updatePermissions } from '@/services/identity.service'
import { getPermissions, updatePermissions } from '@/services/identity.service'
import { useStoreActions, useStoreState } from '@/store'
import { useLocalization } from '@/utils/hooks/useLocalization'
import { ChangeEvent, useEffect, useMemo, useState } from 'react'
type OpenState = Record<string, boolean>
function RolesPermission({
open,
onDialogClose,
@ -26,24 +24,6 @@ function RolesPermission({
onDialogClose: () => void
name: string
}) {
// Collapse/expand state
const [openPermissions, setOpenPermissions] = useState<OpenState>({})
// Parent izinleri aç/kapat
const togglePermission = (permName: string) => {
setOpenPermissions((prev) => ({ ...prev, [permName]: !prev[permName] }))
}
// Parent izin mi?
function isParent(permission: PermissionWithStyle) {
return selectedGroupPermissions.some((p) => p.parentName === permission.name)
}
// Belirli parent'ın alt izinleri
function getChildren(parentName: string) {
if (!parentName) return []
return selectedGroupPermissions.filter((p) => p.parentName === parentName)
}
const providerName = 'R'
const { translate } = useLocalization()
const { getConfig } = useStoreActions((a) => a.abpConfig)
@ -155,28 +135,6 @@ function RolesPermission({
}
}
// --- Expand/Collapse All fonksiyonları (tüm state ve yardımcı fonksiyonlardan sonra) ---
const handleExpandAll = () => {
const expanded: OpenState = {}
function expandRecursively(perms: PermissionWithStyle[]) {
perms.forEach((perm) => {
if (isParent(perm)) {
expanded[perm.name || ''] = true
// Alt parent'ları da recursive aç
const children = getChildren(perm.name || '')
expandRecursively(children)
}
})
}
// Tüm parent ve alt parent'ları recursive olarak aç
expandRecursively(selectedGroupPermissions)
setOpenPermissions(expanded)
}
const handleCollapseAll = () => {
setOpenPermissions({})
}
function getPermissionsWithGroupName(groups: PermissionGroupDto[]): PermissionWithGroupName[] {
return groups.reduce(
(acc, val) => [
@ -216,43 +174,34 @@ function RolesPermission({
[selectedGroupPermissions],
)
// Collapse/expand için recursive düzende izinleri döndür
const filteredPermissions = useMemo(() => {
if (!searchTerm) {
return selectedGroupPermissions
}
const lowerTerm = searchTerm.toLowerCase()
// Filtreli veya tüm parent izinler
const parents = selectedGroupPermissions.filter(
// 1⃣ Sadece 1. kırınımdakileri (parent olmayanları değil) ara
const topLevelMatches = selectedGroupPermissions.filter(
(p) =>
!p.parentName &&
(searchTerm === '' ||
!p.parentName && // parentName yoksa bu 1. kırınım demektir
translate('::' + p.displayName)
.toLowerCase()
.includes(lowerTerm)),
.includes(lowerTerm),
)
// Recursive children builder (her seviye için doğru level)
function buildTree(
parent: PermissionWithStyle,
level: number,
): (PermissionWithStyle & { level: number })[] {
const arr = [{ ...parent, level }]
const parentKey = parent.name || ''
if (openPermissions[parentKey] || searchTerm) {
const children = getChildren(parentKey)
children.forEach((child) => {
arr.push(...buildTree(child, level + 1))
})
}
return arr
}
// 2⃣ Her bulunan parentın altındaki child izinleri ekle
const result: PermissionWithStyle[] = []
topLevelMatches.forEach((parent) => {
result.push(parent)
// Tüm parentlar ve altlarını sırayla ekle
let result: (PermissionWithStyle & { level: number })[] = []
parents.forEach((parent) => {
result = result.concat(buildTree(parent, 0))
// Alt kırılımları bul
const children = selectedGroupPermissions.filter((child) => child.parentName === parent.name)
result.push(...children)
})
return result
}, [selectedGroupPermissions, searchTerm, translate, openPermissions])
}, [selectedGroupPermissions, searchTerm, translate])
const onSelectAll = (value: boolean, e: ChangeEvent<HTMLInputElement>) => {
if (!permissionList) {
@ -311,55 +260,6 @@ function RolesPermission({
fetchDataPermissions()
}, [name])
// --- Copy Permissions State and Logic ---
const [roleList, setRoleList] = useState<string[]>([])
// --- Copy Permissions Dialog State ---
const [copyDialogOpen, setCopyDialogOpen] = useState(false)
const [copyDialogRole, setCopyDialogRole] = useState('')
// Fetch all roles for select (except current)
useEffect(() => {
async function fetchRoles() {
try {
const res = await getRoles()
setRoleList(res.data.items?.map((r: any) => r.name) || [])
} catch (e) {
setRoleList([])
}
}
fetchRoles()
}, [copyDialogOpen])
// Dialog üzerinden kopyalama işlemi
const handleCopyDialogConfirm = async () => {
if (!copyDialogRole || !permissionList) return
setIsLoading(true)
try {
const res = await getPermissions(providerName, copyDialogRole)
const sourcePerms = getPermissionsWithGroupName(res.data?.groups)
const grantedNames = new Set(sourcePerms.filter((p) => p.isGranted).map((p) => p.name))
permissionList.groups.forEach((group) => {
group.permissions.forEach((perm) => {
perm.isGranted = grantedNames.has(perm.name)
})
})
setPermissionList({ ...permissionList })
changeGroup(selectedGroup?.name)
toast.push(<Notification title={translate('::PermissionsCopied')} type="success" />, {
placement: 'top-end',
})
setCopyDialogOpen(false)
setCopyDialogRole('')
} catch (e) {
toast.push(<Notification title={translate('::CopyFailed')} type="danger" />, {
placement: 'top-end',
})
} finally {
setIsLoading(false)
}
}
return permissionList ? (
<Container>
<Dialog
@ -384,13 +284,6 @@ function RolesPermission({
<Checkbox name="group" checked={isAllSelectedForGroup} onChange={onSelectAll}>
{translate('AbpPermissionManagement::SelectAllInThisTab')}
</Checkbox>
<Button size="sm" variant="plain" onClick={handleExpandAll} icon={<FaChevronRight />}>
{translate('::ListForms.ListFormEdit.ExpandAll')}
</Button>
<Button size="sm" variant="plain" onClick={handleCollapseAll} icon={<FaChevronDown />}>
{translate('::ListForms.ListFormEdit.CollapseAll')}
</Button>
</div>
</div>
@ -417,66 +310,30 @@ function RolesPermission({
</div>
<div className="w-full md:w-2/3 max-h-[450px] overflow-y-auto">
<hr className="mb-2"></hr>
<div className="flex items-center gap-2 mb-2">
<Input
size="sm"
className=""
className="mb-2"
placeholder={translate('::Search')}
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
/>
</div>
<div className="my-1">
{filteredPermissions.map((permission) => {
const isParentPerm = isParent(permission)
const permKey = permission.name || ''
const children = getChildren(permKey)
return (
<div key={permission.name} className={`ml-${permission.level * 4} group`}>
<div className="flex items-center gap-2 px-2 py-0.5 rounded-md hover:bg-gray-50 transition-all">
{/* Expand Icon */}
{isParentPerm ? (
<button
onClick={(e) => {
e.stopPropagation()
togglePermission(permKey)
}}
className="w-5 h-5 flex items-center justify-center rounded hover:bg-gray-200 transition"
>
{openPermissions[permKey] || searchTerm ? (
<FaChevronDown className="text-gray-500 text-xs" />
) : (
<FaChevronRight className="text-gray-500 text-xs" />
)}
</button>
) : (
<div className="w-5" />
)}
{/* Checkbox */}
{filteredPermissions.map((permission) => (
<div key={permission.name}>
<Checkbox
name={permission.name}
className={permission.class}
checked={permission.isGranted}
onChange={() => onClickCheckbox(permission)}
>
<span className="text-sm text-gray-700 group-hover:text-gray-900 transition">
{translate('::' + permission.displayName)}
</span>
</Checkbox>
</div>
</div>
)
})}
))}
</div>
</div>
</div>
<div className="flex justify-between items-center mt-6">
<div className="flex items-center">
<Button variant="solid" color="sky-500" onClick={() => setCopyDialogOpen(true)}>
{translate('::AbpIdentity.Roles.CopyPermissions')}
</Button>
</div>
<div className="text-right">
<div className="text-right mt-6">
<Button className="ltr:mr-2 rtl:ml-2" variant="plain" onClick={onDialogClose}>
{translate('::Cancel')}
</Button>
@ -484,45 +341,6 @@ function RolesPermission({
{isLoading ? translate('::SavingWithThreeDot') : translate('::Save')}
</Button>
</div>
</div>
</Dialog>
{/* Copy Permissions Dialog */}
<Dialog
isOpen={copyDialogOpen}
width="min(400px, 95vw)"
onClose={() => setCopyDialogOpen(false)}
>
<h5 className="mb-2">{translate('::AbpIdentity.Roles.CopyPermissions')}</h5>
<div className="mb-4">
<select
className="border rounded px-2 py-1 w-full"
value={copyDialogRole}
onChange={(e) => setCopyDialogRole(e.target.value)}
>
<option value="">{translate('::App.Select')}</option>
{roleList
.filter((role) => role !== name)
.map((role) => (
<option key={role} value={role}>
{role}
</option>
))}
</select>
</div>
<div className="flex justify-end gap-2">
<Button variant="plain" onClick={() => setCopyDialogOpen(false)}>
{translate('::Cancel')}
</Button>
<Button
variant="solid"
onClick={handleCopyDialogConfirm}
disabled={!copyDialogRole || isLoading}
loading={isLoading}
>
{translate('::Copy')}
</Button>
</div>
</Dialog>
</Container>
) : (

View file

@ -318,7 +318,7 @@ function TenantConnectionString({
</FormItem>
<FormItem
label={translate('::App.Listform.ListformField.ConnectionString')}
label="Connection String"
invalid={errors.value && touched.value}
errorMessage={errors.value}
>

View file

@ -271,7 +271,7 @@ function UserDetails() {
<Field
type="text"
name="phoneNumber"
placeholder={translate('::Abp.Identity.User.UserInformation.PhoneNumber')}
placeholder="Phone Number"
component={Input}
/>
</FormItem>

View file

@ -1,6 +1,6 @@
import { FaChevronRight, FaChevronDown } from 'react-icons/fa'
import { Badge, Button, Checkbox, Dialog, Input, Menu, toast } from '@/components/ui'
type OpenState = Record<string, boolean>
import AdaptableCard from '@/components/shared/AdaptableCard'
import Container from '@/components/shared/Container'
import { Badge, Button, Checkbox, Dialog, Menu, toast } from '@/components/ui'
import { useConfig } from '@/components/ui/ConfigProvider'
import Notification from '@/components/ui/Notification'
import {
@ -10,6 +10,7 @@ import {
PermissionWithGroupName,
PermissionWithStyle,
ProviderInfoDto,
UpdatePermissionDto,
} from '@/proxy/admin/models'
import { getPermissions, updatePermissions } from '@/services/identity.service'
import { useStoreActions, useStoreState } from '@/store'
@ -38,44 +39,6 @@ function UsersPermission({
[],
)
// Collapse/expand state
const [openPermissions, setOpenPermissions] = useState<OpenState>({})
// Parent izinleri aç/kapat
const togglePermission = (permName: string) => {
setOpenPermissions((prev) => ({ ...prev, [permName]: !prev[permName] }))
}
// Parent izin mi?
function isParent(permission: PermissionWithStyle) {
return selectedGroupPermissions.some((p) => p.parentName === permission.name)
}
// Belirli parent'ın alt izinleri
function getChildren(parentName: string) {
if (!parentName) return []
return selectedGroupPermissions.filter((p) => p.parentName === parentName)
}
// --- Expand/Collapse All fonksiyonları ---
const handleExpandAll = () => {
const expanded: OpenState = {}
function expandRecursively(perms: PermissionWithStyle[]) {
perms.forEach((perm) => {
if (isParent(perm)) {
expanded[perm.name || ''] = true
const children = getChildren(perm.name || '')
expandRecursively(children)
}
})
}
expandRecursively(selectedGroupPermissions)
setOpenPermissions(expanded)
}
const handleCollapseAll = () => {
setOpenPermissions({})
}
const mode = useStoreState((state) => state.theme.mode)
const { direction } = useConfig()
@ -253,42 +216,6 @@ function UsersPermission({
return false
}
// --- Search ve recursive treeview ---
// Collapse/expand için recursive düzende izinleri döndür
const [searchTerm, setSearchTerm] = useState('')
const filteredPermissions = useMemo(() => {
const lowerTerm = searchTerm.toLowerCase()
// Filtreli veya tüm parent izinler
const parents = selectedGroupPermissions.filter(
(p) =>
!p.parentName &&
(searchTerm === '' ||
translate('::' + p.displayName)
.toLowerCase()
.includes(lowerTerm)),
)
// Recursive children builder (her seviye için doğru level)
function buildTree(
parent: PermissionWithStyle,
level: number,
): (PermissionWithStyle & { level: number })[] {
const arr = [{ ...parent, level }]
const parentKey = parent.name || ''
if (openPermissions[parentKey] || searchTerm) {
const children = getChildren(parentKey)
children.forEach((child) => {
arr.push(...buildTree(child, level + 1))
})
}
return arr
}
let result: (PermissionWithStyle & { level: number })[] = []
parents.forEach((parent) => {
result = result.concat(buildTree(parent, 0))
})
return result
}, [selectedGroupPermissions, searchTerm, translate, openPermissions])
return permissionList ? (
<Dialog
width="min(900px, 95vw)"
@ -297,7 +224,7 @@ function UsersPermission({
onClose={onDialogClose}
onRequestClose={onDialogClose}
>
<h5 className="mb-1">
<h5 className="mb-4">
{translate('::Permission')} - {name}
</h5>
<hr className="mt-1 mb-2"></hr>
@ -312,18 +239,12 @@ function UsersPermission({
<Checkbox name="group" checked={isAllSelectedForGroup} onChange={onSelectAll}>
{translate('AbpPermissionManagement::SelectAllInThisTab')}
</Checkbox>
<Button size="sm" variant="plain" onClick={handleExpandAll} icon={<FaChevronRight />}>
{translate('::ListForms.ListFormEdit.ExpandAll')}
</Button>
<Button size="sm" variant="plain" onClick={handleCollapseAll} icon={<FaChevronDown />}>
{translate('::ListForms.ListFormEdit.CollapseAll')}
</Button>
</div>
</div>
<div className="flex flex-col md:flex-row gap-4">
<div className="w-full md:w-1/3 max-h-[450px] overflow-y-auto">
<hr className="mb-2"></hr>
<hr className="mt-2 mb-2"></hr>
<Menu variant={mode} defaultActiveKeys={[selectedGroup?.displayName ?? '']}>
{permissionList?.groups.map((group) => (
<Menu.MenuItem
@ -341,70 +262,31 @@ function UsersPermission({
</Menu>
</div>
<div className="w-full md:w-2/3 max-h-[450px] overflow-y-auto">
<hr className="mb-2"></hr>
<div className="flex items-center gap-2 mb-2">
<Input
size="sm"
className=""
placeholder={translate('::Search')}
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
/>
</div>
<div className="my-1">
{filteredPermissions.map((permission) => {
const isParentPerm = isParent(permission)
const permKey = permission.name || ''
const children = getChildren(permKey)
return (
<div key={permission.name} className={`ml-${permission.level * 4} group`}>
<div className="flex items-center gap-2 px-2 py-0.5 rounded-md hover:bg-gray-50 transition-all">
{/* Expand Icon */}
{isParentPerm ? (
<button
onClick={(e) => {
e.stopPropagation()
togglePermission(permKey)
}}
className="w-5 h-5 flex items-center justify-center rounded hover:bg-gray-200 transition"
>
{openPermissions[permKey] || searchTerm ? (
<FaChevronDown className="text-gray-500 text-xs" />
) : (
<FaChevronRight className="text-gray-500 text-xs" />
)}
</button>
) : (
<div className="w-5" />
)}
{/* Checkbox */}
<hr className="mt-2 mb-2"></hr>
<div className="card-body">
{selectedGroupPermissions.map((permission) => (
<div key={permission.name}>
<Checkbox
name={permission.name}
className={permission.class}
disabled={isGrantedByOtherProviderName(permission.grantedProviders)}
//disabled={permission.grantedProviders.length > 0 ? true : false}
checked={permission.isGranted}
onChange={() => onClickCheckbox(permission)}
disabled={permission.grantedProviders.some((provider) => provider.providerName === 'R')}
>
<span className="text-sm text-gray-700 group-hover:text-gray-900 transition">
{translate('::' + permission.displayName)}
{permission.grantedProviders.map((provider) => {
const badgeContent = provider.providerName !== providerName
? `${provider.providerName}: ${provider.providerKey}`
: provider.providerName;
return (
{permission.grantedProviders.map((provider) => (
<Badge
key={provider.providerKey}
className="m-2"
content={badgeContent}
content={`${provider.providerName}${
provider.providerName !== providerName ? `: ${provider.providerKey}` : ''
}`}
/>
);
})}
</span>
))}
</Checkbox>
</div>
</div>
)
})}
))}
</div>
</div>
</div>

View file

@ -10,7 +10,6 @@ import { Container } from '@/components/shared'
import { Helmet } from 'react-helmet'
import { APP_NAME } from '@/constants/app.constant'
import dayjs from 'dayjs'
import { AI_ASSISTANT } from '@/constants/permission.constant'
// Types
type ChatType = 'chat' | 'query' | 'analyze'
@ -294,7 +293,7 @@ const Assistant = () => {
<Container>
<Helmet
titleTemplate={`%s | ${APP_NAME}`}
title={translate('::' + AI_ASSISTANT)}
title={translate('::' + 'App.AiBot.Asistant')}
defaultTitle={APP_NAME}
></Helmet>

View file

@ -314,7 +314,7 @@ export default ${pascalCaseName}Component;`
</FormItem>
<FormItem
label={translate('::App.Platform.Code')}
label={translate('::App.DeveloperKit.ComponentEditor.Code')}
invalid={!!(errors.code && touched.code)}
errorMessage={errors.code as string}
>

View file

@ -63,7 +63,7 @@ const DynamicServiceManager: React.FC = () => {
const stats = [
{
name: translate('::App.Listform.ListformField.Total'),
name: translate('::App.DeveloperKit.DynamicServices.Total'),
value: totalServices,
icon: FaCode,
color: 'text-purple-600',

View file

@ -1500,7 +1500,7 @@ const SqlTableDesignerDialog = ({
if (result.data.success) {
const deployedTable = settings.tableName || initialTableData?.tableName || ''
toast.push(
<Notification type="success" title={translate('::App.Platform.Success')}>
<Notification type="success" title={translate('::App.SqlQueryManager.Success')}>
{`${translate(isEditMode ? '::App.SqlQueryManager.TableUpdated' : '::App.SqlQueryManager.TableCreated')}: [dbo].[${deployedTable}]`}
</Notification>,
{ placement: 'top-center' },
@ -1509,7 +1509,7 @@ const SqlTableDesignerDialog = ({
handleClose()
} else {
toast.push(
<Notification type="danger" title={translate('::App.Platform.Error')}>
<Notification type="danger" title={translate('::App.SqlQueryManager.Error')}>
{result.data.message || translate('::App.SqlQueryManager.TableCreationFailed')}
</Notification>,
{ placement: 'top-center' },
@ -1517,7 +1517,7 @@ const SqlTableDesignerDialog = ({
}
} catch (error: any) {
toast.push(
<Notification type="danger" title={translate('::App.Platform.Error')}>
<Notification type="danger" title={translate('::App.SqlQueryManager.Error')}>
{error.response?.data?.error?.message ||
translate('::App.SqlQueryManager.TableDeployFailed')}
</Notification>,
@ -1873,7 +1873,7 @@ const SqlTableDesignerDialog = ({
{/* Table Name (readonly, auto-generated) */}
<div>
<label className="block text-sm font-medium mb-1">
{translate('::App.Listform.ListformField.TableName')}
{translate('::App.SqlQueryManager.TableName')}
</label>
<input
type="text"
@ -2057,7 +2057,7 @@ const SqlTableDesignerDialog = ({
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg text-sm dark:bg-gray-700 dark:text-white focus:ring-2 focus:ring-indigo-500"
>
<option value="">
{translate('::App.Select')}
{translate('::App.SqlQueryManager.SelectPlaceholder')}
</option>
{columns
.filter((c) => c.columnName.trim())
@ -2082,7 +2082,7 @@ const SqlTableDesignerDialog = ({
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg text-sm dark:bg-gray-700 dark:text-white focus:ring-2 focus:ring-indigo-500"
>
<option value="">
{translate('::App.Select')}
{translate('::App.SqlQueryManager.SelectPlaceholder')}
</option>
{dbTables.map((t) => (
<option key={`${t.schemaName}.${t.tableName}`} value={t.tableName}>
@ -2187,6 +2187,7 @@ const SqlTableDesignerDialog = ({
</label>
</div>
{/* Description */}
<div>
<label className="block text-xs font-semibold text-gray-700 dark:text-gray-300 mb-1.5">
{translate('::App.SqlQueryManager.Description')}
@ -2207,7 +2208,7 @@ const SqlTableDesignerDialog = ({
onClick={() => setFkModalOpen(false)}
className="px-4 py-2 text-sm text-gray-600 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors"
>
{translate('::Cancel')}
{translate('::App.SqlQueryManager.Cancel')}
</button>
<button
onClick={saveFk}
@ -2431,7 +2432,7 @@ const SqlTableDesignerDialog = ({
<div className="border border-gray-200 dark:border-gray-600 rounded-lg overflow-hidden">
<div className="grid grid-cols-12 gap-2 px-3 py-1.5 bg-gray-50 dark:bg-gray-700 text-xs font-semibold text-gray-500">
<div className="col-span-1" />
<div className="col-span-7">{translate('::App.Listform.ListformField.Column')}</div>
<div className="col-span-7">{translate('::App.SqlQueryManager.Column')}</div>
<div className="col-span-4">{translate('::App.SqlQueryManager.SortOrder')}</div>
</div>
<div className="max-h-44 overflow-y-auto divide-y divide-gray-100 dark:divide-gray-700">
@ -2516,6 +2517,7 @@ const SqlTableDesignerDialog = ({
)}
</div>
{/* Description */}
<div>
<label className="block text-xs font-semibold text-gray-700 dark:text-gray-300 mb-1.5">
{translate('::App.SqlQueryManager.Description')}
@ -2538,7 +2540,7 @@ const SqlTableDesignerDialog = ({
onClick={() => setIndexModalOpen(false)}
className="px-4 py-2 text-sm text-gray-600 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors"
>
{translate('::Cancel')}
{translate('::App.SqlQueryManager.Cancel')}
</button>
<button
onClick={saveIndex}
@ -2613,12 +2615,12 @@ const SqlTableDesignerDialog = ({
{/* Footer */}
<div className="flex justify-between items-center border-t pt-3 mt-1">
<Button variant="plain" onClick={handleClose}>
{translate('::Cancel')}
{translate('::App.SqlQueryManager.Cancel')}
</Button>
<div className="flex items-center gap-2">
{step > 0 && (
<Button variant="default" onClick={handleBack}>
{translate('::Back')}
{translate('::App.SqlQueryManager.Back')}
</Button>
)}
{step < 4 ? (
@ -2638,7 +2640,7 @@ const SqlTableDesignerDialog = ({
(isEditMode && generatedSql.includes('Henüz değişiklik yapılmadı'))
}
>
{translate('::App.Platform.Deploy')}
{translate('::App.SqlQueryManager.Deploy')}
</Button>
)}
</div>

View file

@ -177,10 +177,7 @@ const FormDevExpress = (props: {
}}
></TagBoxEditorComponent>
)}
label={{
text: translate('::' + formItem.colData?.captionName),
className: 'font-semibold',
}}
label={{ text: translate('::' + formItem.colData?.captionName), className: 'font-semibold' }}
></SimpleItemDx>
) : formItem.editorType2 === PlatformEditorTypes.dxGridBox ? (
<SimpleItemDx
@ -202,10 +199,7 @@ const FormDevExpress = (props: {
}}
></GridBoxEditorComponent>
)}
label={{
text: translate('::' + formItem.colData?.captionName),
className: 'font-semibold',
}}
label={{ text: translate('::' + formItem.colData?.captionName), className: 'font-semibold' }}
></SimpleItemDx>
) : (
<SimpleItemDx
@ -222,9 +216,6 @@ const FormDevExpress = (props: {
showClearButton: true,
}
: {}),
...(formItem.colData?.placeHolder
? { placeholder: translate('::' + formItem.colData.placeHolder) }
: {}),
buttons: (formItem.editorOptions?.buttons || []).map((btn: any) => {
if (btn?.options?.onClick && typeof btn.options.onClick === 'string') {
const origClick = eval(`(${btn.options.onClick})`)

View file

@ -2,7 +2,12 @@ import React, { useState, useRef, useEffect } from 'react'
import { NoteModal } from './NoteModal'
import { NoteList } from './NoteList'
import { Button, Badge } from '@/components/ui'
import { FaChevronLeft, FaChevronRight, FaTimes, FaGripVertical } from 'react-icons/fa'
import {
FaChevronLeft,
FaChevronRight,
FaTimes,
FaGripVertical,
} from 'react-icons/fa'
import { noteService } from '@/services/note.service'
import { NoteDto } from '@/proxy/note/models'
import { useLocalization } from '@/utils/hooks/useLocalization'
@ -46,7 +51,11 @@ export const NotePanel: React.FC<NotePanelProps> = ({
const handleDownloadFile = async (fileData: any) => {
if (!fileData?.SavedFileName) return
try {
await noteService.downloadFile(fileData.SavedFileName, fileData.FileName, fileData.FileType)
await noteService.downloadFile(
fileData.SavedFileName,
fileData.FileName,
fileData.FileType,
)
} catch (err) {
console.error(translate('::ListForms.ListForm.NotesPanel.DownloadFailed'), err)
}
@ -126,11 +135,7 @@ export const NotePanel: React.FC<NotePanelProps> = ({
e.stopPropagation()
onToggle()
}}
title={
isVisible
? translate('::ListForms.ListForm.NotesPanel.ClosePanel')
: translate('::ListForms.ListForm.NotesPanel.OpenPanel')
}
title={isVisible ? translate('::ListForms.ListForm.NotesPanel.ClosePanel') : translate('::ListForms.ListForm.NotesPanel.OpenPanel')}
>
<div className="flex items-center gap-2">
{isVisible ? <FaChevronRight /> : <FaChevronLeft />}
@ -152,13 +157,12 @@ export const NotePanel: React.FC<NotePanelProps> = ({
onClick={(e) => e.stopPropagation()}
>
<div className="flex flex-col h-full">
<div className="p-2 border-b border-gray-200 bg-gray-50">
<div className="p-4 border-b border-gray-200 bg-gray-50">
{/* Üst Satır: Başlık, Kayıt Bilgisi Toggle ve Kapat Butonu */}
<div className="flex items-center justify-between mx-1 my-1">
<div className="flex flex-col gap-1 text-sm text-gray-700">
<div className="flex items-center justify-between mb-3">
<div className="flex items-center gap-1 text-sm text-gray-700">
<span className="font-medium">{entityName}</span>
<code className="bg-gray-100 py-1 rounded text-gray-800 text-xs font-mono w-fit">
<code className="bg-gray-100 px-2 rounded text-gray-800 text-xs font-mono">
<Badge className="bg-blue-100 text-blue-600" content={entityId} />
</code>
</div>
@ -175,10 +179,13 @@ export const NotePanel: React.FC<NotePanelProps> = ({
className={`transition-all duration-300 overflow-hidden ${
showEntityInfo ? 'max-h-20 mt-2 opacity-100' : 'max-h-0 opacity-0'
}`}
></div>
>
</div>
<div className="flex-1 overflow-y-auto p-2">
</div>
<div className="flex-1 overflow-y-auto p-4">
<NoteList
notes={activities}
entityName={entityName}

View file

@ -7,7 +7,6 @@ import { Container } from '@/components/shared'
import { Helmet } from 'react-helmet'
import { useLocalization } from '@/utils/hooks/useLocalization'
import { APP_NAME } from '@/constants/app.constant'
import { Button } from '@/components/ui'
export function Forum() {
const { translate } = useLocalization()
@ -64,9 +63,9 @@ export function Forum() {
<div className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded relative">
<strong className="font-bold">Error: </strong>
<span className="block sm:inline">{error}</span>
<Button onClick={clearError} className="absolute top-0 bottom-0 right-0 px-4 py-3">
<button onClick={clearError} className="absolute top-0 bottom-0 right-0 px-4 py-3">
<span className="sr-only">Dismiss</span>×
</Button>
</button>
</div>
</div>
)}

View file

@ -5,7 +5,6 @@ import { Container } from '@/components/shared'
import { Helmet } from 'react-helmet'
import { useLocalization } from '@/utils/hooks/useLocalization'
import { APP_NAME } from '@/constants/app.constant'
import { Button } from '@/components/ui'
export function Management() {
const {
@ -70,9 +69,9 @@ export function Management() {
<div className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded relative">
<strong className="font-bold">Error: </strong>
<span className="block sm:inline">{error}</span>
<Button onClick={clearError} className="absolute top-0 bottom-0 right-0 px-4 py-3">
<button onClick={clearError} className="absolute top-0 bottom-0 right-0 px-4 py-3">
<span className="sr-only">Dismiss</span>×
</Button>
</button>
</div>
</div>
)}

View file

@ -6,7 +6,6 @@ import { PostManagement } from './PostManagement'
import { ForumCategory, ForumPost, ForumTopic } from '@/proxy/forum/forum'
import { AdminStats } from './Dashboard'
import { useLocalization } from '@/utils/hooks/useLocalization'
import { Button } from '@/components/ui'
interface AdminViewProps {
categories: ForumCategory[]
@ -109,7 +108,7 @@ export function AdminView({
{navigationItems.map((item) => {
const Icon = item.icon
return (
<Button
<button
key={item.id}
onClick={() => setActiveSection(item.id)}
className={`w-full flex items-center space-x-3 px-4 py-3 rounded-lg text-left transition-colors ${
@ -120,7 +119,7 @@ export function AdminView({
>
<Icon className="w-5 h-5" />
<span className="font-medium">{item.label}</span>
</Button>
</button>
)
})}
</nav>

View file

@ -165,15 +165,14 @@ export function CategoryManagement({
<h2 className="text-2xl font-bold text-gray-900">
{translate('::App.Forum.CategoryManagement.Title')}
</h2>
<Button
variant="solid"
<button
onClick={() => setShowCreateForm(true)}
disabled={loading}
className="flex items-center space-x-2 bg-blue-600 px-4 py-2 rounded-lg hover:bg-blue-700 transition-colors disabled:opacity-50"
className="flex items-center space-x-2 bg-blue-600 text-white px-4 py-2 rounded-lg hover:bg-blue-700 transition-colors disabled:opacity-50"
>
<FaPlus className="w-4 h-4" />
<span>{translate('::App.Forum.CategoryManagement.AddCategory')}</span>
</Button>
</button>
</div>
{/* Create/Edit Form */}
@ -368,8 +367,7 @@ export function CategoryManagement({
</div>
<div className="flex items-center space-x-2">
<Button
size='xs'
<button
onClick={() => handleToggleActive(category)}
className={`p-2 rounded-lg transition-colors ${
category.isActive
@ -383,10 +381,9 @@ export function CategoryManagement({
) : (
<FaEyeSlash className="w-4 h-4" />
)}
</Button>
</button>
<Button
size='xs'
<button
onClick={() => handleToggleLocked(category)}
className={`p-2 rounded-lg transition-colors ${
category.isLocked
@ -400,25 +397,23 @@ export function CategoryManagement({
) : (
<FaUnlock className="w-4 h-4" />
)}
</Button>
</button>
<Button
size='xs'
<button
onClick={() => handleEdit(category)}
className="p-2 text-blue-600 hover:bg-blue-100 rounded-lg transition-colors"
title={translate('::App.Forum.CategoryManagement.EditCategory')}
>
<FaEdit className="w-4 h-4" />
</Button>
</button>
<Button
size='xs'
<button
onClick={() => confirmDeleteCategory(category)}
className="p-2 text-red-600 hover:bg-red-100 rounded-lg transition-colors"
title={translate('::App.Forum.CategoryManagement.DeleteCategory')}
>
<FaTrash className="w-4 h-4" />
</Button>
</button>
</div>
</div>
</div>

View file

@ -164,15 +164,14 @@ export function PostManagement({
<h2 className="text-2xl font-bold text-gray-900">
{translate('::App.Forum.PostManagement.Title')}
</h2>
<Button
variant="solid"
<button
onClick={() => setShowCreateForm(true)}
disabled={loading}
className="flex items-center space-x-2 bg-blue-600 px-4 py-2 rounded-lg hover:bg-blue-700 transition-colors disabled:opacity-50"
className="flex items-center space-x-2 bg-blue-600 text-white px-4 py-2 rounded-lg hover:bg-blue-700 transition-colors disabled:opacity-50"
>
<FaPlus className="w-4 h-4" />
<span>{translate('::App.Forum.PostManagement.AddPost')}</span>
</Button>
</button>
</div>
{/* Create/Edit Form */}
@ -398,7 +397,7 @@ export function PostManagement({
</div>
<div className="flex items-center space-x-2 ml-4">
<Button
<button
onClick={() => handleToggleAcceptedAnswer(post)}
className={`p-2 rounded-lg transition-colors ${
post.isAcceptedAnswer
@ -416,23 +415,23 @@ export function PostManagement({
) : (
<FaCircle className="w-4 h-4" />
)}
</Button>
</button>
<Button
<button
onClick={() => handleEdit(post)}
className="p-2 text-blue-600 hover:bg-blue-100 rounded-lg transition-colors"
title={translate('::App.Forum.PostManagement.EditPost')}
>
<FaEdit className="w-4 h-4" />
</Button>
</button>
<Button
<button
onClick={() => confirmDeletePost(post)}
className="p-2 text-red-600 hover:bg-red-100 rounded-lg transition-colors"
title={translate('::App.Forum.PostManagement.DeletePost')}
>
<FaTrashAlt className="w-4 h-4" />
</Button>
</button>
</div>
</div>
</div>

View file

@ -206,15 +206,14 @@ export function TopicManagement({
<h2 className="text-2xl font-bold text-gray-900">
{translate('::App.Forum.TopicManagement.Title')}
</h2>
<Button
variant="solid"
<button
onClick={() => setShowCreateForm(true)}
disabled={loading}
className="flex items-center space-x-2 bg-blue-600 px-4 py-2 rounded-lg hover:bg-blue-700 transition-colors disabled:opacity-50"
className="flex items-center space-x-2 bg-blue-600 text-white px-4 py-2 rounded-lg hover:bg-blue-700 transition-colors disabled:opacity-50"
>
<FaPlus className="w-4 h-4" />
<span>{translate('::App.Forum.TopicManagement.AddTopic')}</span>
</Button>
</button>
</div>
{/* Create/Edit Form */}
@ -399,7 +398,7 @@ export function TopicManagement({
</div>
<div className="flex items-center space-x-2 ml-4">
<Button
<button
onClick={() => handlePin(topic)}
className={`p-2 rounded-lg transition-colors ${
topic.isPinned
@ -413,9 +412,9 @@ export function TopicManagement({
) : (
<FaTree className="w-4 h-4" />
)}
</Button>
</button>
<Button
<button
onClick={() => handleLock(topic)}
className={`p-2 rounded-lg transition-colors ${
topic.isLocked
@ -429,9 +428,9 @@ export function TopicManagement({
) : (
<FaUnlock className="w-4 h-4" />
)}
</Button>
</button>
<Button
<button
onClick={() => handleSolved(topic)}
className={`p-2 rounded-lg transition-colors ${
topic.isSolved
@ -445,23 +444,23 @@ export function TopicManagement({
) : (
<FaCircle className="w-4 h-4" />
)}
</Button>
</button>
<Button
<button
onClick={() => handleEdit(topic)}
className="p-2 text-blue-600 hover:bg-blue-100 rounded-lg transition-colors"
title={translate('::App.Forum.TopicManagement.EditTopic')}
>
<FaEdit className="w-4 h-4" />
</Button>
</button>
<Button
<button
onClick={() => confirmDeleteTopic(topic)}
className="p-2 text-red-600 hover:bg-red-100 rounded-lg transition-colors"
title={translate('::App.Forum.TopicManagement.DeleteTopic')}
>
<FaTrashAlt className="w-4 h-4" />
</Button>
</button>
</div>
</div>
</div>

View file

@ -77,7 +77,7 @@ export function CreatePostModal({ onClose, onSubmit, parentPostId }: CreatePostM
label={
parentPostId
? translate('::App.Forum.PostManagement.BaslikEdit')
: translate('::App.Listform.ListformField.Content')
: translate('::App.Forum.PostManagement.BaslikNew')
}
invalid={!!errors.content && !!touched.content}
errorMessage={errors.content}

View file

@ -74,7 +74,7 @@ export function CreateTopicModal({ onClose, onSubmit }: CreateTopicModalProps) {
</FormItem>
<FormItem
label={translate('::App.Listform.ListformField.Content')}
label={translate('::App.Forum.TopicManagement.Content')}
invalid={errors.content && touched.content}
errorMessage={errors.content}
asterisk

View file

@ -44,7 +44,7 @@ export function ForumTopicCard({ topic, onClick }: TopicCardProps) {
<p className="text-gray-600 text-sm mb-4 line-clamp-2">{topic.content}</p>
<div className="flex items-center space-x-4 text-sm text-gray-500">
<div className="flex items-center space-x-1" title={translate('::App.Platform.Views')}>
<div className="flex items-center space-x-1" title="Views">
<FaEye className="w-4 h-4" />
<span>{topic.viewCount}</span>
</div>

View file

@ -11,7 +11,6 @@ import { ForumTopicCard } from './ForumTopicCard'
import { useStoreState } from '@/store/store'
import { useLocalization } from '@/utils/hooks/useLocalization'
import { forumService } from '@/services/forum.service'
import { Button } from '@/components/ui'
interface ForumViewProps {
categories: ForumCategory[]
@ -353,45 +352,41 @@ export function ForumView({
{/* Right Side: Actions + Search */}
<div className="flex items-center space-x-2 ml-auto">
{viewState === 'topics' && selectedCategory && !selectedCategory.isLocked && (
<Button
variant='solid'
<button
onClick={() => setShowCreateTopic(true)}
className="flex items-center space-x-2 bg-blue-600 text-white px-4 py-2 rounded-lg hover:bg-blue-700 transition-colors"
>
<FaPlus className="w-4 h-4" />
<span>{translate('::App.Forum.TopicManagement.NewTopic')}</span>
</Button>
</button>
)}
{viewState === 'posts' && selectedTopic && !selectedTopic.isLocked && (
<Button
variant='solid'
<button
onClick={() => setShowCreatePost(true)}
className="flex items-center space-x-2 bg-emerald-600 text-white px-4 py-2 rounded-lg hover:bg-emerald-700 transition-colors"
>
<FaPlus className="w-4 h-4" />
<span>{translate('::App.Forum.PostManagement.NewPost')}</span>
</Button>
</button>
)}
{/* Search */}
<Button
<button
onClick={() => setIsSearchModalOpen(true)}
variant='default'
className="hidden md:flex items-center space-x-2 px-4 py-2 border border-gray-300 rounded-lg hover:bg-gray-50 transition-colors"
>
<FaSearch className="w-4 h-4 text-gray-400" />
<span className="text-gray-500">
{translate('::App.Forum.TopicManagement.Searchtopics')}
</span>
</Button>
</button>
<Button
<button
onClick={() => setIsSearchModalOpen(true)}
variant='default'
className="md:hidden p-2 text-gray-400 hover:text-gray-600 transition-colors"
>
<FaSearch className="w-5 h-5" />
</Button>
</button>
</div>
</div>

View file

@ -114,7 +114,7 @@ const ChartDrawer = ({
} catch (error: any) {
toast.push(
<Notification type="danger">
{translate('::App.Platform.Error')}
{translate('::App.Platform.ChartDrawer.Error')}
<code>{error}</code>
</Notification>,
{ placement: 'top-end' },
@ -143,13 +143,13 @@ const ChartDrawer = ({
<span>📊</span>
{translate('::App.Platform.ChartDrawer.ChartSeries')}
</h2>
<Button
<button
type="button"
onClick={onClose}
className="p-2 hover:bg-gray-200 rounded-full transition-colors"
>
<FaTimes />
</Button>
</button>
</div>
<FormContainer size="sm" className="flex flex-col flex-1 overflow-hidden">
@ -205,7 +205,7 @@ const ChartDrawer = ({
<Field name={`series[${index}].type`}>
{({ field, form }: FieldProps) => (
<div className="relative">
<Button
<button
type="button"
onClick={() =>
setSelectedSeriesIndex(
@ -222,12 +222,12 @@ const ChartDrawer = ({
{chartSeriesTypeOptions.find((t) => t.label === field.value)
?.label || field.value}
</span>
</Button>
</button>
{selectedSeriesIndex === index && (
<div className="absolute z-50 mt-1 w-full bg-white border rounded-lg shadow-xl p-2">
<div className="grid grid-cols-2 gap-2">
{chartSeriesTypeOptions.map((chartType) => (
<Button
<button
key={chartType.label}
type="button"
onClick={() => {
@ -244,7 +244,7 @@ const ChartDrawer = ({
<span className="text-xs font-medium">
{chartType.label}
</span>
</Button>
</button>
))}
</div>
</div>
@ -257,7 +257,7 @@ const ChartDrawer = ({
{/* Name */}
<div className="mb-3">
<label className="text-xs font-medium mb-1 block">
{translate('::App.Listform.ListformField.Name')}
{translate('::App.Platform.ChartDrawer.Name')}
</label>
<Field
size="sm"

View file

@ -73,7 +73,6 @@ import { useListFormCustomDataSource } from './useListFormCustomDataSource'
import { useListFormColumns } from './useListFormColumns'
import { Loading } from '@/components/shared'
import { useStoreState } from '@/store'
import { placeholder } from '@babel/types'
interface GridProps {
listFormCode: string
@ -925,14 +924,6 @@ const Grid = (props: GridProps) => {
}
}
// Her item'a placeholder olarak captionName ekle
if (listFormField?.placeHolder) {
editorOptions = {
...editorOptions,
placeholder: translate('::' + listFormField.placeHolder),
}
}
// Set defaultValue for @AUTONUMBER fields
if (
typeof listFormField?.defaultValue === 'string' &&

View file

@ -119,6 +119,7 @@ const GridFilterDialogs = (props: {
<h5 className="mb-4">Delete Filter</h5>
<Select
placeholder="Please Select"
options={filtersForSelectBox}
onChange={(option) => {
setNewFilterId(option?.value)

View file

@ -621,7 +621,7 @@ const SchedulerView = (props: SchedulerViewProps) => {
allowDragging={gridDto.gridOptions.schedulerOptionDto?.allowDragging ?? false}
/>
<View type="day" name={translate('::App.Listform.ListformField.Day')} />
<View type="day" name={translate('::ListForms.SchedulerOptions.Day')} />
<View type="week" name={translate('::ListForms.SchedulerOptions.Week')} />
<View type="workWeek" name={translate('::ListForms.SchedulerOptions.WorkWeek')} />
<View type="month" name={translate('::ListForms.SchedulerOptions.Month')} />

Some files were not shown because too many files have changed in this diff Show more