Intranet üzerinde Widgets yapısı dinamik hale getirildi

This commit is contained in:
Sedat ÖZTÜRK 2026-09-03 11:26:35 +03:00
parent 0941b9143b
commit 7a0839a3f6
34 changed files with 1181 additions and 208 deletions

View file

@ -798,7 +798,7 @@ içindedir; oradaki kontroller yalnızca butonları gizler, asıl kontrol AppSer
| Aktivite ve denetim kaydı | `/admin/activityLog`, `/admin/changeLog` | Kullanıcı hareketleri ve ABP audit log detayı. |
| Bildirimler | `/admin/profile/notification-settings`, bildirim ekranları | Kanal tercihleri ve bildirim oluşturma. |
| Forum | `/admin/forum`, `/admin/forumManagement` | Forum kullanımı ve yönetimi. |
| Intranet | Intranet dashboard ve widget'ları | Duyuru, anket, sosyal duvar, etkinlik. |
| Intranet | Intranet dashboard ve widget'ları | Duyuru, anket, sosyal duvar, etkinlik. Hangi widget'ın hangi kolonda, hangi sırada ve hangi yetkiyle yayınlandığı `Adm_T_IntranetWidget` kaydından gelir (`TenantData.json` → `Widgets`). |
| Video oda | `/admin/videoroom/*` | Oda listesi, detay, planlama, katılımcı ve ekran paylaşımı. |
| Raporlar | `/admin/reports/:report/view|design/...` | DevExpress rapor görüntüleyici ve tasarımcı. |
| AI asistanı | `/admin/ai` | n8n webhook üzerinden sohbet arayüzü. |

View file

@ -11,4 +11,7 @@ public class IntranetDashboardDto
public List<FileItemDto> Documents { get; set; } = [];
public List<AnnouncementDto> Announcements { get; set; } = [];
public List<SurveyDto> Surveys { get; set; } = [];
/// <summary>Dashboard'da yayinlanan widget'lar; sira ve kolon bilgisi kayittan gelir.</summary>
public List<IntranetWidgetDto> Widgets { get; set; } = [];
}

View file

@ -0,0 +1,34 @@
using System;
using Sozsoft.Platform.Enums;
namespace Sozsoft.Platform.Intranet;
/// <summary>
/// Dashboard'da yayinlanacak widget tanimi. Hangi widget'in nerede, hangi sirada ve hangi
/// yetkiyle gorunecegi bu kayittan gelir; koddaki kayit yalnizca render fonksiyonunu tasir.
/// </summary>
public class IntranetWidgetDto
{
public Guid Id { get; set; }
/// <summary>Builtin widget'ta koddaki kayit anahtari, Custom widget'ta tekil kod.</summary>
public string Code { get; set; }
public string Name { get; set; }
public string LabelKey { get; set; }
public IntranetWidgetSourceType SourceType { get; set; }
/// <summary>Custom widget'in CustomComponent adi.</summary>
public string ComponentName { get; set; }
public string PermissionName { get; set; }
public IntranetWidgetColumn Column { get; set; }
public int Order { get; set; }
/// <summary>Custom widget'a gecilecek prop'lar; JSON nesnesi.</summary>
public string Props { get; set; }
}

View file

@ -85,6 +85,16 @@ public static class LookupQueryValues
$"\"{FullNameTable(TableNameEnum.LanguageText)}\".\"CultureName\" = 'tr' " +
$"ORDER BY \"{FullNameTable(TableNameEnum.LanguageKey)}\".\"Key\";";
public static string CustomComponentValues =
$"SELECT " +
$"\"Name\" AS \"Key\", " +
$"\"Name\" AS \"Name\" " +
$"FROM \"{FullNameTable(TableNameEnum.CustomComponent)}\" " +
$"WHERE " +
$"\"IsActive\" = 'true' " +
$"AND \"IsDeleted\" = 'false' " +
$"ORDER BY \"Name\";";
public static string CountryValues =
$"SELECT " +
$"\"Id\" AS \"Key\", " +

View file

@ -36,6 +36,7 @@ public class IntranetAppService : PlatformAppService, IIntranetAppService
private readonly IRepository<Department, Guid> _departmentRepository;
private readonly IRepository<JobPosition, Guid> _jobPositionRepository;
private readonly IRepository<Announcement, Guid> _announcementRepository;
private readonly IRepository<IntranetWidget, Guid> _intranetWidgetRepository;
private readonly IRepository<Survey, Guid> _surveyRepository;
private readonly IRepository<SurveyResponse, Guid> _surveyResponseRepository;
private readonly IRepository<SurveyAnswer, Guid> _surveyAnswerRepository;
@ -60,6 +61,7 @@ public class IntranetAppService : PlatformAppService, IIntranetAppService
IRepository<Department, Guid> departmentRepository,
IRepository<JobPosition, Guid> jobPositionRepository,
IRepository<Announcement, Guid> announcementRepository,
IRepository<IntranetWidget, Guid> intranetWidgetRepository,
IRepository<Survey, Guid> surveyRepository,
IRepository<SurveyResponse, Guid> surveyResponseRepository,
IRepository<SurveyAnswer, Guid> surveyAnswerRepository,
@ -83,6 +85,7 @@ public class IntranetAppService : PlatformAppService, IIntranetAppService
_departmentRepository = departmentRepository;
_jobPositionRepository = jobPositionRepository;
_announcementRepository = announcementRepository;
_intranetWidgetRepository = intranetWidgetRepository;
_surveyRepository = surveyRepository;
_surveyResponseRepository = surveyResponseRepository;
_surveyAnswerRepository = surveyAnswerRepository;
@ -107,9 +110,45 @@ public class IntranetAppService : PlatformAppService, IIntranetAppService
Announcements = await GetAnnouncementsAsync(), //3
Surveys = await GetSurveysAsync(), //4
Events = await GetUpcomingEventsAsync(), //5
Widgets = await GetWidgetsAsync(), //6
};
}
/// <summary>
/// Aktif widget'lari sirali dondurur. Yetkisi olmayan widget sunucuda elenir; boylece
/// istemciye yalnizca gorulebilecek widget'lar gider.
/// </summary>
private async Task<List<IntranetWidgetDto>> GetWidgetsAsync()
{
var widgets = await _intranetWidgetRepository.GetListAsync(x => x.IsActive);
var result = new List<IntranetWidgetDto>();
foreach (var widget in widgets.OrderBy(x => x.Order).ThenBy(x => x.Code))
{
if (!widget.PermissionName.IsNullOrWhiteSpace() &&
!await AuthorizationService.IsGrantedAsync(widget.PermissionName))
{
continue;
}
result.Add(new IntranetWidgetDto
{
Id = widget.Id,
Code = widget.Code,
Name = widget.Name,
LabelKey = widget.LabelKey,
SourceType = widget.SourceType,
ComponentName = widget.ComponentName,
PermissionName = widget.PermissionName,
Column = widget.Column,
Order = widget.Order,
Props = widget.Props
});
}
return result;
}
private Task<(Dictionary<Guid, string> DepartmentDict, Dictionary<Guid, JobPosition> JobPositionDict)> GetUserLookupDictionariesAsync()
{
return _userLookupDictionariesTask ??= LoadUserLookupDictionariesAsync();

View file

@ -17196,18 +17196,6 @@
"en": "Select at least one operation or turn generation off",
"tr": "En az bir operasyon seçin ya da üretimi kapatın"
},
{
"resourceName": "Platform",
"key": "App.SqlQueryManager.ExistingCrudEndpoints",
"en": "Existing CRUD endpoints",
"tr": "Kayıtlı CRUD endpoint'leri"
},
{
"resourceName": "Platform",
"key": "App.SqlQueryManager.ExistingCrudEndpointsHint",
"en": "This table already has generated endpoints; deploying again regenerates them with the operations selected below.",
"tr": "Bu tablo için endpoint'ler daha önce üretilmiş; tekrar deploy edildiğinde aşağıda seçilen operasyonlarla yeniden üretilir."
},
{
"resourceName": "Platform",
"key": "App.SqlQueryManager.CrudEndpointsInactiveNote",
@ -27737,6 +27725,78 @@
"key": "App.Agenda.Description",
"en": "Agenda — the calendar screen. Table: Adm_T_Agenda · tenant scope · scheduler (calendar) layout.\nUsage: this screen is the reference for the scheduler layout. A recurring appointment request needs no code — the recurrence is expressed in the RecurrenceRule field.\nFields:\n• Text, Description → the title and the detail of the appointment\n• StartDate, EndDate → the start and end moments; the calendar places the record over them\n• AllDay → an all day record; the hour information is ignored\n• RecurrenceRule → the recurrence rule in iCalendar format, e.g. FREQ=WEEKLY;BYDAY=MO,WE. Written by the calendar's own repeat dialog\n• RecurrenceException → the excluded occurrences of the series; when a single occurrence is cancelled its date is added here rather than breaking the series\n• UserName → the owner of the record\nCaution: the calendar view is opened through the LayoutJson and SchedulerOptionJson of the list screen — there is no separate calendar route. When a recurring record is edited, decide whether the change applies to the whole series or to one occurrence; changing the rule shifts the past occurrences as well.",
"tr": "Ajanda — takvim ekranı. Tablo: Adm_T_Agenda · tenant kapsamlı · scheduler (takvim) düzeni.\nKullanım: bu ekran scheduler düzeninin referansıdır. Tekrarlayan randevu talebi kod gerektirmez — tekrar bilgisi RecurrenceRule alanında ifade edilir.\nAlanlar:\n• Text, Description → randevunun başlığı ve ayrıntısı\n• StartDate, EndDate → başlangıç ve bitiş anları; takvim kaydı bunlara göre yerleştirir\n• AllDay → tüm gün süren kayıt; saat bilgisi dikkate alınmaz\n• RecurrenceRule → iCalendar biçiminde tekrar kuralı, örn. FREQ=WEEKLY;BYDAY=MO,WE. Takvimin kendi tekrar diyaloğu tarafından yazılır\n• RecurrenceException → serinin hariç tutulan tekrarları; tek bir tekrar iptal edildiğinde seri bozulmaz, tarihi buraya eklenir\n• UserName → kaydın sahibi\nDikkat: takvim görünümü liste ekranının LayoutJson ve SchedulerOptionJson tanımlarıyla açılır — ayrı bir takvim rotası yoktur. Tekrarlayan kayıt düzenlenirken değişikliğin tüm seriye mi tek tekrara mı uygulanacağına karar ver; kuralı değiştirmek geçmiş tekrarları da kaydırır."
},
{
"resourceName": "Platform",
"key": "App.DeveloperKit.IntranetWidgets",
"en": "Intranet Widgets",
"tr": "Intranet Widget'ları"
},
{
"resourceName": "Platform",
"key": "App.DeveloperKit.IntranetWidgets.Description",
"en": "Intranet Widgets — the widgets published on the intranet dashboard. Table: Adm_T_IntranetWidget · tenant scope (IsTenant = true) · list screen.\nUsage: which widget appears on the dashboard, in which column, in which order and with which permission is decided here. A widget coded into the application is published by adding a record whose Code matches the code registration; a component produced in the Component Manager is published by choosing it in ComponentName.\nFields:\n• Code → the unique key of the widget; for a builtin widget it is the registration key in the code\n• Name → the name shown on this screen\n• SourceType → Builtin (coded into the application) or Custom (a custom component)\n• ComponentName → the custom component to render; only for SourceType = Custom\n• Column, Order → where on the dashboard and in which order it is placed\n• LabelKey → the language key of the title shown on the dashboard\n• PermissionName → the permission required to see the widget; a widget left empty is open to every logged in user\n• Props → the props passed to the custom component, as a JSON object\n• IsActive → an inactive widget is not sent to the dashboard\nCaution: the Code of a builtin widget must match the registration in the code; a record with no counterpart renders nothing. The user's own layout (design mode) is kept per user — Reset returns to the order defined here.",
"tr": "Intranet Widget'ları — intranet dashboard'ında yayınlanan widget'lar. Tablo: Adm_T_IntranetWidget · tenant kapsamlı (IsTenant = true) · liste ekranı.\nKullanım: dashboard'da hangi widget'ın, hangi kolonda, hangi sırada ve hangi yetkiyle görüneceği buradan belirlenir. Uygulamaya kodlanmış bir widget, Code alanı koddaki kayıtla eşleşen bir satır eklenerek yayınlanır; Component Manager'da üretilmiş bir bileşen ise ComponentName alanından seçilerek yayınlanır.\nAlanlar:\n• Code → widget'ın tekil anahtarı; builtin widget'ta koddaki kayıt anahtarıdır\n• Name → bu ekranda görünen ad\n• SourceType → Builtin (uygulamaya kodlanmış) veya Custom (özel bileşen)\n• ComponentName → basılacak özel bileşen; yalnızca SourceType = Custom için\n• Column, Order → dashboard'da hangi kolona ve hangi sıraya yerleşeceği\n• LabelKey → dashboard'da gösterilen başlığın dil anahtarı\n• PermissionName → widget'ı görmek için gereken yetki; boş bırakılan widget oturum açmış herkese açıktır\n• Props → özel bileşene geçilecek prop'lar; JSON nesnesi\n• IsActive → pasif widget dashboard'a gönderilmez\nDikkat: builtin widget'ın Code değeri koddaki kayıtla birebir aynı olmalıdır; karşılığı olmayan kayıt hiçbir şey basmaz. Kullanıcının kendi yerleşimi (tasarım modu) kullanıcı bazında tutulur — Reset buradaki sıraya döner."
},
{
"resourceName": "Platform",
"key": "App.Listform.ListformField.Code",
"en": "Code",
"tr": "Kod"
},
{
"resourceName": "Platform",
"key": "App.Listform.ListformField.LabelKey",
"en": "Label Key",
"tr": "Başlık Anahtarı"
},
{
"resourceName": "Platform",
"key": "App.Listform.ListformField.SourceType",
"en": "Source Type",
"tr": "Kaynak Türü"
},
{
"resourceName": "Platform",
"key": "App.Listform.ListformField.ComponentName",
"en": "Component",
"tr": "Bileşen"
},
{
"resourceName": "Platform",
"key": "App.Listform.ListformField.PermissionName",
"en": "Permission",
"tr": "Yetki"
},
{
"resourceName": "Platform",
"key": "App.Listform.ListformField.Props",
"en": "Props",
"tr": "Proplar"
},
{
"resourceName": "Platform",
"key": "App.StaticLookup.Builtin",
"en": "Builtin",
"tr": "Yerleşik"
},
{
"resourceName": "Platform",
"key": "App.StaticLookup.Left",
"en": "Left",
"tr": "Sol"
},
{
"resourceName": "Platform",
"key": "App.StaticLookup.Center",
"en": "Center",
"tr": "Orta"
},
{
"resourceName": "Platform",
"key": "App.StaticLookup.Right",
"en": "Right",
"tr": "Sağ"
}
]
}
}

View file

@ -6480,5 +6480,5 @@ public class ListFormSeeder_Administration : IDataSeedContributor, ITransientDep
#endregion
}
#endregion
}
}
}

View file

@ -10096,5 +10096,312 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
#endregion
}
#endregion
#region IntranetWidget
listFormName = AppCodes.DeveloperKits.IntranetWidgets;
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 = false,
LayoutJson = DefaultLayoutJson(),
CultureName = LanguageCodes.En,
ListFormCode = listFormName,
Name = listFormName,
Title = listFormName,
DataSourceCode = SeedConsts.DataSources.DefaultCode,
IsTenant = true,
IsBranch = false,
IsOrganizationUnit = false,
Description = $"{listFormName}.Description",
SelectCommandType = SelectCommandTypeEnum.Table,
SelectCommand = TableNameResolver.GetFullTableName(nameof(TableNameEnum.IntranetWidget)),
KeyFieldName = "Id",
KeyFieldDbSourceType = DbType.Guid,
DefaultFilter = DefaultFilterJson,
SortMode = GridOptions.SortModeMultiple,
FilterRowJson = DefaultFilterRowJson(),
HeaderFilterJson = DefaultHeaderFilterJson(),
SearchPanelJson = DefaultSearchPanelJson(),
GroupPanelJson = DefaultGroupPanelJson(),
SelectionJson = DefaultSelectionSingleJson,
ColumnOptionJson = DefaultColumnOptionJson(),
PermissionJson = DefaultPermissionJson(listFormName),
DeleteCommand = DefaultDeleteCommand(nameof(TableNameEnum.IntranetWidget)),
DeleteFieldsDefaultValueJson = DefaultDeleteFieldsDefaultValueJson(),
PagerOptionJson = DefaultPagerOptionJson,
InsertFieldsDefaultValueJson = DefaultInsertFieldsDefaultValueJson(),
EditingOptionJson = DefaultEditingOptionJson(listFormName, 850, 560, true, true, true, true, false),
FormFieldsDefaultValueJson = JsonSerializer.Serialize(new FieldsDefaultValue[] {
new() { FieldName = "IsActive", FieldDbType = DbType.Boolean, Value = "true", CustomValueType = FieldCustomValueTypeEnum.Value }
}),
EditingFormJson = JsonSerializer.Serialize(new List<EditingFormDto>
{
new() { Order = 1, ColCount = 2, ColSpan = 2, ItemType = "group" },
}),
});
#region IntranetWidget Fields
await _listFormFieldRepository.InsertManyAsync([
new() {
ListFormCode = listForm.ListFormCode,
CultureName = LanguageCodes.En,
SourceDbType = DbType.Guid,
FieldName = "Id",
CaptionName = "App.Listform.ListformField.Id",
Width = 0,
ListOrderNo = 1,
Visible = false,
IsActive = true,
ColumnCustomizationJson = DefaultColumnCustomizationJson,
PermissionJson = DefaultFieldPermissionJson(listForm.Name),
PivotSettingsJson = DefaultPivotSettingsJson
},
new() {
ListFormCode = listForm.ListFormCode,
CultureName = LanguageCodes.En,
SourceDbType = DbType.String,
FieldName = "Code",
EditGroupOrderNo = 1,
EditOrderNo = 1,
EditorType2 = EditorTypes.dxTextBox,
ColSpan = 1,
CaptionName = "App.Listform.ListformField.Code",
Width = 0,
ListOrderNo = 2,
Visible = true,
IsActive = true,
AllowSearch = true,
ValidationRuleJson = DefaultValidationRuleRequiredJson,
ColumnCustomizationJson = DefaultColumnCustomizationJson,
PermissionJson = DefaultFieldPermissionJson(listForm.Name),
PivotSettingsJson = DefaultPivotSettingsJson
},
new() {
ListFormCode = listForm.ListFormCode,
CultureName = LanguageCodes.En,
SourceDbType = DbType.String,
FieldName = "Name",
EditGroupOrderNo = 1,
EditOrderNo = 2,
EditorType2 = EditorTypes.dxTextBox,
ColSpan = 1,
CaptionName = "App.Listform.ListformField.Name",
Width = 0,
ListOrderNo = 3,
Visible = true,
IsActive = true,
AllowSearch = true,
ValidationRuleJson = DefaultValidationRuleRequiredJson,
ColumnCustomizationJson = DefaultColumnCustomizationJson,
PermissionJson = DefaultFieldPermissionJson(listForm.Name),
PivotSettingsJson = DefaultPivotSettingsJson
},
new() {
ListFormCode = listForm.ListFormCode,
CultureName = LanguageCodes.En,
SourceDbType = DbType.Int32,
FieldName = "SourceType",
EditGroupOrderNo = 1,
EditOrderNo = 3,
EditorType2 = EditorTypes.dxSelectBox,
ColSpan = 1,
CaptionName = "App.Listform.ListformField.SourceType",
Width = 0,
ListOrderNo = 4,
Visible = true,
IsActive = true,
AllowSearch = true,
LookupJson = JsonSerializer.Serialize(new LookupDto
{
DataSourceType = UiLookupDataSourceTypeEnum.StaticData,
DisplayExpr = "name",
ValueExpr = "key",
LookupQuery = JsonSerializer.Serialize(new LookupDataDto[] {
new () { Key = (int)IntranetWidgetSourceType.Builtin, Name = "App.StaticLookup.Builtin" },
new () { Key = (int)IntranetWidgetSourceType.Custom, Name = "App.StaticLookup.Custom" },
}),
}),
ValidationRuleJson = DefaultValidationRuleRequiredJson,
ColumnCustomizationJson = DefaultColumnCustomizationJson,
PermissionJson = DefaultFieldPermissionJson(listForm.Name),
PivotSettingsJson = DefaultPivotSettingsJson
},
new() {
ListFormCode = listForm.ListFormCode,
CultureName = LanguageCodes.En,
SourceDbType = DbType.String,
FieldName = "ComponentName",
EditGroupOrderNo = 1,
EditOrderNo = 4,
EditorType2 = EditorTypes.dxSelectBox,
ColSpan = 1,
EditorOptions = EditorOptions.ShowClearButton(),
CaptionName = "App.Listform.ListformField.ComponentName",
Width = 0,
ListOrderNo = 5,
Visible = true,
IsActive = true,
AllowSearch = true,
LookupJson = JsonSerializer.Serialize(new LookupDto
{
DataSourceType = UiLookupDataSourceTypeEnum.Query,
DisplayExpr = "Name",
ValueExpr = "Key",
LookupQuery = LookupQueryValues.CustomComponentValues,
}),
ColumnCustomizationJson = DefaultColumnCustomizationJson,
PermissionJson = DefaultFieldPermissionJson(listForm.Name),
PivotSettingsJson = DefaultPivotSettingsJson
},
new() {
ListFormCode = listForm.ListFormCode,
CultureName = LanguageCodes.En,
SourceDbType = DbType.Int32,
FieldName = "Column",
EditGroupOrderNo = 1,
EditOrderNo = 5,
EditorType2 = EditorTypes.dxSelectBox,
ColSpan = 1,
CaptionName = "App.Listform.ListformField.Column",
Width = 0,
ListOrderNo = 6,
Visible = true,
IsActive = true,
AllowSearch = true,
SortIndex = 1,
SortDirection = GridColumnOptions.SortOrderAsc,
LookupJson = JsonSerializer.Serialize(new LookupDto
{
DataSourceType = UiLookupDataSourceTypeEnum.StaticData,
DisplayExpr = "name",
ValueExpr = "key",
LookupQuery = JsonSerializer.Serialize(new LookupDataDto[] {
new () { Key = (int)IntranetWidgetColumn.Left, Name = "App.StaticLookup.Left" },
new () { Key = (int)IntranetWidgetColumn.Center, Name = "App.StaticLookup.Center" },
new () { Key = (int)IntranetWidgetColumn.Right, Name = "App.StaticLookup.Right" },
}),
}),
ValidationRuleJson = DefaultValidationRuleRequiredJson,
ColumnCustomizationJson = DefaultColumnCustomizationJson,
PermissionJson = DefaultFieldPermissionJson(listForm.Name),
PivotSettingsJson = DefaultPivotSettingsJson
},
new() {
ListFormCode = listForm.ListFormCode,
CultureName = LanguageCodes.En,
SourceDbType = DbType.Int32,
FieldName = "Order",
EditGroupOrderNo = 1,
EditOrderNo = 6,
EditorType2 = EditorTypes.dxNumberBox,
ColSpan = 1,
EditorOptions = EditorOptions.Number(0),
CaptionName = "App.Listform.ListformField.Order",
Width = 0,
ListOrderNo = 7,
Visible = true,
IsActive = true,
AllowSearch = true,
SortIndex = 2,
SortDirection = GridColumnOptions.SortOrderAsc,
ValidationRuleJson = DefaultValidationRuleRequiredJson,
ColumnCustomizationJson = DefaultColumnCustomizationJson,
PermissionJson = DefaultFieldPermissionJson(listForm.Name),
PivotSettingsJson = DefaultPivotSettingsJson
},
new() {
ListFormCode = listForm.ListFormCode,
CultureName = LanguageCodes.En,
SourceDbType = DbType.String,
FieldName = "LabelKey",
EditGroupOrderNo = 1,
EditOrderNo = 7,
EditorType2 = EditorTypes.dxTextBox,
ColSpan = 1,
CaptionName = "App.Listform.ListformField.LabelKey",
Width = 0,
ListOrderNo = 8,
Visible = true,
IsActive = true,
AllowSearch = true,
ColumnCustomizationJson = DefaultColumnCustomizationJson,
PermissionJson = DefaultFieldPermissionJson(listForm.Name),
PivotSettingsJson = DefaultPivotSettingsJson
},
new() {
ListFormCode = listForm.ListFormCode,
CultureName = LanguageCodes.En,
SourceDbType = DbType.String,
FieldName = "PermissionName",
EditGroupOrderNo = 1,
EditOrderNo = 8,
EditorType2 = EditorTypes.dxSelectBox,
ColSpan = 1,
EditorOptions = EditorOptions.ShowClearButton(),
CaptionName = "App.Listform.ListformField.PermissionName",
Width = 0,
ListOrderNo = 9,
Visible = true,
IsActive = true,
AllowSearch = true,
LookupJson = JsonSerializer.Serialize(new LookupDto
{
DataSourceType = UiLookupDataSourceTypeEnum.Query,
DisplayExpr = "Name",
ValueExpr = "Key",
LookupQuery = LookupQueryValues.PermissionNameValues,
}),
ColumnCustomizationJson = DefaultColumnCustomizationJson,
PermissionJson = DefaultFieldPermissionJson(listForm.Name),
PivotSettingsJson = DefaultPivotSettingsJson
},
new() {
ListFormCode = listForm.ListFormCode,
CultureName = LanguageCodes.En,
SourceDbType = DbType.Boolean,
FieldName = "IsActive",
EditGroupOrderNo = 1,
EditOrderNo = 9,
EditorType2 = EditorTypes.dxCheckBox,
ColSpan = 1,
CaptionName = "App.Listform.ListformField.IsActive",
Width = 0,
ListOrderNo = 10,
Visible = true,
IsActive = true,
AllowSearch = true,
ColumnCustomizationJson = DefaultColumnCustomizationJson,
PermissionJson = DefaultFieldPermissionJson(listForm.Name),
PivotSettingsJson = DefaultPivotSettingsJson
},
new() {
ListFormCode = listForm.ListFormCode,
CultureName = LanguageCodes.En,
SourceDbType = DbType.String,
FieldName = "Props",
EditGroupOrderNo = 1,
EditOrderNo = 10,
EditorType2 = EditorTypes.dxTextArea,
ColSpan = 2,
EditorOptions = EditorOptions.Multiline(120),
CaptionName = "App.Listform.ListformField.Props",
Width = 0,
ListOrderNo = 11,
Visible = false,
IsActive = true,
ColumnCustomizationJson = DefaultColumnCustomizationJson,
PermissionJson = DefaultFieldPermissionJson(listForm.Name),
PivotSettingsJson = DefaultPivotSettingsJson
}
]);
#endregion
}
#endregion
}
}

View file

@ -984,6 +984,17 @@
"IsDisabled": false,
"ModuleId": "Sas"
},
{
"ParentCode": "App.DeveloperKit",
"Code": "App.Files",
"DisplayName": "App.Files",
"Order": 2,
"Url": "/admin/files",
"Icon": "FcFolder",
"RequiredPermissionName": "App.Files",
"IsDisabled": false,
"ModuleId": "Sas"
},
{
"ParentCode": "App.DeveloperKit",
"Code": "App.DeveloperKit.CustomEndpoints",
@ -1028,11 +1039,22 @@
"IsDisabled": false,
"ModuleId": "Sas"
},
{
"ParentCode": "App.DeveloperKit",
"Code": "App.DeveloperKit.IntranetWidgets",
"DisplayName": "App.DeveloperKit.IntranetWidgets",
"Order": 7,
"Url": "/admin/list/App.DeveloperKit.IntranetWidgets",
"Icon": "FcAdvertising",
"RequiredPermissionName": "App.DeveloperKit.IntranetWidgets",
"IsDisabled": false,
"ModuleId": "Sas"
},
{
"ParentCode": "App.DeveloperKit",
"Code": "App.Listforms.WizardManager",
"DisplayName": "App.Listforms.WizardManager",
"Order": 6,
"Order": 8,
"Url": "/admin/listform/wizardManager",
"Icon": "FcFlashAuto",
"RequiredPermissionName": "App.Listforms.Wizard",
@ -1413,22 +1435,11 @@
"IsDisabled": false,
"ModuleId": "Adm"
},
{
"ParentCode": "App.Administration",
"Code": "App.Files",
"DisplayName": "App.Files",
"Order": 11,
"Url": "/admin/files",
"Icon": "FcFolder",
"RequiredPermissionName": "App.Files",
"IsDisabled": false,
"ModuleId": "Adm"
},
{
"ParentCode": "App.Administration",
"Code": "App.Forum",
"DisplayName": "App.Forum",
"Order": 12,
"Order": 11,
"Url": "/admin/forum",
"Icon": "FcLink",
"RequiredPermissionName": "App.ForumManagement.Publish",

View file

@ -2262,6 +2262,51 @@
"MultiTenancySide": 2,
"Modules": "Sas|Adm"
},
{
"GroupName": "App.Administration",
"Name": "App.Files",
"ParentName": null,
"DisplayName": "App.Files",
"IsEnabled": true,
"MultiTenancySide": 2,
"Modules": "Sas|Adm"
},
{
"GroupName": "App.Administration",
"Name": "App.Files.Create",
"ParentName": "App.Files",
"DisplayName": "App.Platform.Create",
"IsEnabled": true,
"MultiTenancySide": 2,
"Modules": "Sas|Adm"
},
{
"GroupName": "App.Administration",
"Name": "App.Files.Update",
"ParentName": "App.Files",
"DisplayName": "App.Platform.Update",
"IsEnabled": true,
"MultiTenancySide": 2,
"Modules": "Sas|Adm"
},
{
"GroupName": "App.Administration",
"Name": "App.Files.Delete",
"ParentName": "App.Files",
"DisplayName": "App.Platform.Delete",
"IsEnabled": true,
"MultiTenancySide": 2,
"Modules": "Sas|Adm"
},
{
"GroupName": "App.Administration",
"Name": "App.Files.Widget",
"ParentName": "App.Files",
"DisplayName": "App.Platform.Widget",
"IsEnabled": true,
"MultiTenancySide": 2,
"Modules": "Sas|Adm"
},
{
"GroupName": "App.Saas",
"Name": "App.DeveloperKit.CustomEndpoints",
@ -2460,6 +2505,60 @@
"MultiTenancySide": 2,
"Modules": "Sas|Adm"
},
{
"GroupName": "App.Saas",
"Name": "App.DeveloperKit.IntranetWidgets",
"ParentName": "App.DeveloperKit",
"DisplayName": "App.DeveloperKit.IntranetWidgets",
"IsEnabled": true,
"MultiTenancySide": 2,
"Modules": "Sas|Adm"
},
{
"GroupName": "App.Saas",
"Name": "App.DeveloperKit.IntranetWidgets.Create",
"ParentName": "App.DeveloperKit.IntranetWidgets",
"DisplayName": "App.Platform.Create",
"IsEnabled": true,
"MultiTenancySide": 2,
"Modules": "Sas|Adm"
},
{
"GroupName": "App.Saas",
"Name": "App.DeveloperKit.IntranetWidgets.Update",
"ParentName": "App.DeveloperKit.IntranetWidgets",
"DisplayName": "App.Platform.Update",
"IsEnabled": true,
"MultiTenancySide": 2,
"Modules": "Sas|Adm"
},
{
"GroupName": "App.Saas",
"Name": "App.DeveloperKit.IntranetWidgets.Delete",
"ParentName": "App.DeveloperKit.IntranetWidgets",
"DisplayName": "App.Platform.Delete",
"IsEnabled": true,
"MultiTenancySide": 2,
"Modules": "Sas|Adm"
},
{
"GroupName": "App.Saas",
"Name": "App.DeveloperKit.IntranetWidgets.Export",
"ParentName": "App.DeveloperKit.IntranetWidgets",
"DisplayName": "Export",
"IsEnabled": true,
"MultiTenancySide": 2,
"Modules": "Sas|Adm"
},
{
"GroupName": "App.Saas",
"Name": "App.DeveloperKit.IntranetWidgets.Import",
"ParentName": "App.DeveloperKit.IntranetWidgets",
"DisplayName": "Import",
"IsEnabled": true,
"MultiTenancySide": 2,
"Modules": "Sas|Adm"
},
{
"GroupName": "App.Saas",
"Name": "App.Listforms.Listform",
@ -4935,51 +5034,6 @@
"MultiTenancySide": 3,
"Modules": "Sas|Adm"
},
{
"GroupName": "App.Administration",
"Name": "App.Files",
"ParentName": null,
"DisplayName": "App.Files",
"IsEnabled": true,
"MultiTenancySide": 3,
"Modules": "Sas|Adm"
},
{
"GroupName": "App.Administration",
"Name": "App.Files.Create",
"ParentName": "App.Files",
"DisplayName": "App.Platform.Create",
"IsEnabled": true,
"MultiTenancySide": 3,
"Modules": "Sas|Adm"
},
{
"GroupName": "App.Administration",
"Name": "App.Files.Update",
"ParentName": "App.Files",
"DisplayName": "App.Platform.Update",
"IsEnabled": true,
"MultiTenancySide": 3,
"Modules": "Sas|Adm"
},
{
"GroupName": "App.Administration",
"Name": "App.Files.Delete",
"ParentName": "App.Files",
"DisplayName": "App.Platform.Delete",
"IsEnabled": true,
"MultiTenancySide": 3,
"Modules": "Sas|Adm"
},
{
"GroupName": "App.Administration",
"Name": "App.Files.Widget",
"ParentName": "App.Files",
"DisplayName": "App.Platform.Widget",
"IsEnabled": true,
"MultiTenancySide": 3,
"Modules": "Sas|Adm"
},
{
"GroupName": "App.Administration",
"Name": "App.ForumManagement.Publish",

View file

@ -0,0 +1,21 @@
namespace Sozsoft.Platform.Enums;
/// <summary>
/// Intranet dashboard widget'inin nereden render edildigini belirler.
/// </summary>
public enum IntranetWidgetSourceType
{
/// <summary>Uygulama icine kodlanmis widget; <c>Code</c> alani koddaki kayit anahtaridir.</summary>
Builtin = 0,
/// <summary>DeveloperKit'te uretilmis bir CustomComponent; <c>ComponentName</c> alani ile eslesir.</summary>
Custom = 1,
}
/// <summary>Dashboard'daki üç kolondan hangisinde yayinlanacagi.</summary>
public enum IntranetWidgetColumn
{
Left = 0,
Center = 1,
Right = 2,
}

View file

@ -88,6 +88,7 @@ public enum TableNameEnum
Event,
EventPhoto,
EventComment,
IntranetWidget,
MessengerConversation,
MessengerConversationMessage,
Videoroom,

View file

@ -476,6 +476,9 @@ public static class PlatformConsts
public const string CustomEndpoints = Default + ".CustomEndpoints";
/// <summary>Intranet dashboard'inda yayinlanan widget tanimlarinin ListForm ekrani.</summary>
public const string IntranetWidgets = Default + ".IntranetWidgets";
// Coarse gate for calling a dynamic endpoint through the dispatcher.
// The per endpoint User/Role/Global rules are checked on top of this.
// Named Put/Remove because Update/Delete already gate editing the

View file

@ -111,6 +111,7 @@ public static class TableNameResolver
{ nameof(TableNameEnum.Event), (TablePrefix.TenantByName, MenuPrefix.Administration) },
{ nameof(TableNameEnum.EventPhoto), (TablePrefix.TenantByName, MenuPrefix.Administration) },
{ nameof(TableNameEnum.EventComment), (TablePrefix.TenantByName, MenuPrefix.Administration) },
{ nameof(TableNameEnum.IntranetWidget), (TablePrefix.TenantByName, MenuPrefix.Administration) },
{ nameof(TableNameEnum.MessengerConversation), (TablePrefix.TenantByName, MenuPrefix.Administration) },
{ nameof(TableNameEnum.MessengerConversationMessage), (TablePrefix.TenantByName, MenuPrefix.Administration) },

View file

@ -471,6 +471,7 @@ public static class SeedConsts
public const string Default = Prefix.App + ".DeveloperKit";
public const string CustomEndpoints = Default + ".CustomEndpoints";
public const string IntranetWidgets = Default + ".IntranetWidgets";
}
public const string Forum = Prefix.App + ".Forum";

View file

@ -0,0 +1,44 @@
using System;
using Sozsoft.Platform.Enums;
using Volo.Abp.Domain.Entities.Auditing;
using Volo.Abp.MultiTenancy;
namespace Sozsoft.Platform.Entities;
/// <summary>
/// Intranet dashboard'inda yayinlanan widget tanimi. Widget'in kendisi ya uygulamaya kodlanmistir
/// (<see cref="IntranetWidgetSourceType.Builtin"/>, <see cref="Code"/> koddaki kayit anahtaridir)
/// ya da DeveloperKit'te uretilmis bir CustomComponent'tir
/// (<see cref="IntranetWidgetSourceType.Custom"/>, <see cref="ComponentName"/> ile eslesir).
/// Kolon, sira, yetki ve gorunurluk bu kayittan gelir; kodda tutulmaz.
/// </summary>
public class IntranetWidget : FullAuditedEntity<Guid>, IMultiTenant
{
public Guid? TenantId { get; set; }
/// <summary>Widget'in tenant icinde tekil anahtari; Builtin widget'larda koddaki kayit anahtaridir.</summary>
public string Code { get; set; }
/// <summary>Yonetim ekranlarinda gorunen ad.</summary>
public string Name { get; set; }
/// <summary>Dashboard'da gosterilen basligin dil anahtari.</summary>
public string LabelKey { get; set; }
public IntranetWidgetSourceType SourceType { get; set; }
/// <summary>Custom widget'in CustomComponent adi; Builtin widget'ta bostur.</summary>
public string ComponentName { get; set; }
/// <summary>Widget'i gormek icin gereken yetki kodu; bos ise herkese aciktir.</summary>
public string PermissionName { get; set; }
public IntranetWidgetColumn Column { get; set; }
public int Order { get; set; }
/// <summary>Custom widget'a gecilecek prop'lar; JSON nesnesi olarak tutulur.</summary>
public string Props { get; set; }
public bool IsActive { get; set; } = true;
}

View file

@ -126,6 +126,8 @@ public class PlatformDbContext :
public DbSet<MessengerConversation> MessengerConversations { get; set; }
public DbSet<MessengerConversationMessage> MessengerConversationMessages { get; set; }
public DbSet<IntranetWidget> IntranetWidgets { get; set; }
public DbSet<Announcement> Announcements { get; set; }
public DbSet<AnnouncementComment> AnnouncementComments { get; set; }
public DbSet<AnnouncementLike> AnnouncementLikes { get; set; }
@ -1201,6 +1203,22 @@ public class PlatformDbContext :
b.HasIndex(x => new { x.Name }).IsUnique().HasFilter("[IsDeleted] = 0");
});
builder.Entity<IntranetWidget>(b =>
{
b.ToTable(TableNameResolver.GetFullTableName(nameof(TableNameEnum.IntranetWidget)), Prefix.DbSchema);
b.ConfigureByConvention();
b.Property(x => x.Code).IsRequired().HasMaxLength(64);
b.Property(x => x.Name).IsRequired().HasMaxLength(128);
b.Property(x => x.LabelKey).HasMaxLength(256);
b.Property(x => x.ComponentName).HasMaxLength(256);
b.Property(x => x.PermissionName).HasMaxLength(256);
b.Property(x => x.Props).HasColumnType("nvarchar(max)");
b.Property(x => x.IsActive).HasDefaultValue(true);
b.HasIndex(x => new { x.TenantId, x.Code }).IsUnique().HasFilter("[IsDeleted] = 0");
});
builder.Entity<Announcement>(b =>
{
b.ToTable(TableNameResolver.GetFullTableName(nameof(TableNameEnum.Announcement)), Prefix.DbSchema);

View file

@ -13,7 +13,7 @@ using Volo.Abp.EntityFrameworkCore;
namespace Sozsoft.Platform.Migrations
{
[DbContext(typeof(PlatformDbContext))]
[Migration("20260901075908_Initial")]
[Migration("20260903062758_Initial")]
partial class Initial
{
/// <inheritdoc />
@ -3105,6 +3105,93 @@ namespace Sozsoft.Platform.Migrations
b.ToTable("Sas_H_InstallmentOption", (string)null);
});
modelBuilder.Entity("Sozsoft.Platform.Entities.IntranetWidget", b =>
{
b.Property<Guid>("Id")
.HasColumnType("uniqueidentifier");
b.Property<string>("Code")
.IsRequired()
.HasMaxLength(64)
.HasColumnType("nvarchar(64)");
b.Property<int>("Column")
.HasColumnType("int");
b.Property<string>("ComponentName")
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.Property<DateTime>("CreationTime")
.HasColumnType("datetime2")
.HasColumnName("CreationTime");
b.Property<Guid?>("CreatorId")
.HasColumnType("uniqueidentifier")
.HasColumnName("CreatorId");
b.Property<Guid?>("DeleterId")
.HasColumnType("uniqueidentifier")
.HasColumnName("DeleterId");
b.Property<DateTime?>("DeletionTime")
.HasColumnType("datetime2")
.HasColumnName("DeletionTime");
b.Property<bool>("IsActive")
.ValueGeneratedOnAdd()
.HasColumnType("bit")
.HasDefaultValue(true);
b.Property<bool>("IsDeleted")
.ValueGeneratedOnAdd()
.HasColumnType("bit")
.HasDefaultValue(false)
.HasColumnName("IsDeleted");
b.Property<string>("LabelKey")
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.Property<DateTime?>("LastModificationTime")
.HasColumnType("datetime2")
.HasColumnName("LastModificationTime");
b.Property<Guid?>("LastModifierId")
.HasColumnType("uniqueidentifier")
.HasColumnName("LastModifierId");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(128)
.HasColumnType("nvarchar(128)");
b.Property<int>("Order")
.HasColumnType("int");
b.Property<string>("PermissionName")
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.Property<string>("Props")
.HasColumnType("nvarchar(max)");
b.Property<int>("SourceType")
.HasColumnType("int");
b.Property<Guid?>("TenantId")
.HasColumnType("uniqueidentifier")
.HasColumnName("TenantId");
b.HasKey("Id");
b.HasIndex("TenantId", "Code")
.IsUnique()
.HasFilter("[IsDeleted] = 0");
b.ToTable("Adm_T_IntranetWidget", (string)null);
});
modelBuilder.Entity("Sozsoft.Platform.Entities.IpRestriction", b =>
{
b.Property<Guid>("Id")

View file

@ -601,6 +601,35 @@ namespace Sozsoft.Platform.Migrations
table.PrimaryKey("PK_Adm_T_EventType", x => x.Id);
});
migrationBuilder.CreateTable(
name: "Adm_T_IntranetWidget",
columns: table => new
{
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
TenantId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
Code = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: false),
Name = table.Column<string>(type: "nvarchar(128)", maxLength: 128, nullable: false),
LabelKey = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: true),
SourceType = table.Column<int>(type: "int", nullable: false),
ComponentName = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: true),
PermissionName = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: true),
Column = table.Column<int>(type: "int", nullable: false),
Order = table.Column<int>(type: "int", nullable: false),
Props = table.Column<string>(type: "nvarchar(max)", nullable: true),
IsActive = table.Column<bool>(type: "bit", nullable: false, defaultValue: true),
CreationTime = table.Column<DateTime>(type: "datetime2", nullable: false),
CreatorId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
LastModificationTime = table.Column<DateTime>(type: "datetime2", nullable: true),
LastModifierId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
IsDeleted = table.Column<bool>(type: "bit", nullable: false, defaultValue: false),
DeleterId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
DeletionTime = table.Column<DateTime>(type: "datetime2", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Adm_T_IntranetWidget", x => x.Id);
});
migrationBuilder.CreateTable(
name: "Adm_T_IpRestriction",
columns: table => new
@ -3838,6 +3867,13 @@ namespace Sozsoft.Platform.Migrations
unique: true,
filter: "[IsDeleted] = 0");
migrationBuilder.CreateIndex(
name: "IX_Adm_T_IntranetWidget_TenantId_Code",
table: "Adm_T_IntranetWidget",
columns: new[] { "TenantId", "Code" },
unique: true,
filter: "[IsDeleted] = 0");
migrationBuilder.CreateIndex(
name: "IX_Adm_T_IpRestriction_TenantId_ResourceType_ResourceId_IP",
table: "Adm_T_IpRestriction",
@ -4465,6 +4501,9 @@ namespace Sozsoft.Platform.Migrations
migrationBuilder.DropTable(
name: "Adm_T_EventLike");
migrationBuilder.DropTable(
name: "Adm_T_IntranetWidget");
migrationBuilder.DropTable(
name: "Adm_T_IpRestriction");

View file

@ -3102,6 +3102,93 @@ namespace Sozsoft.Platform.Migrations
b.ToTable("Sas_H_InstallmentOption", (string)null);
});
modelBuilder.Entity("Sozsoft.Platform.Entities.IntranetWidget", b =>
{
b.Property<Guid>("Id")
.HasColumnType("uniqueidentifier");
b.Property<string>("Code")
.IsRequired()
.HasMaxLength(64)
.HasColumnType("nvarchar(64)");
b.Property<int>("Column")
.HasColumnType("int");
b.Property<string>("ComponentName")
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.Property<DateTime>("CreationTime")
.HasColumnType("datetime2")
.HasColumnName("CreationTime");
b.Property<Guid?>("CreatorId")
.HasColumnType("uniqueidentifier")
.HasColumnName("CreatorId");
b.Property<Guid?>("DeleterId")
.HasColumnType("uniqueidentifier")
.HasColumnName("DeleterId");
b.Property<DateTime?>("DeletionTime")
.HasColumnType("datetime2")
.HasColumnName("DeletionTime");
b.Property<bool>("IsActive")
.ValueGeneratedOnAdd()
.HasColumnType("bit")
.HasDefaultValue(true);
b.Property<bool>("IsDeleted")
.ValueGeneratedOnAdd()
.HasColumnType("bit")
.HasDefaultValue(false)
.HasColumnName("IsDeleted");
b.Property<string>("LabelKey")
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.Property<DateTime?>("LastModificationTime")
.HasColumnType("datetime2")
.HasColumnName("LastModificationTime");
b.Property<Guid?>("LastModifierId")
.HasColumnType("uniqueidentifier")
.HasColumnName("LastModifierId");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(128)
.HasColumnType("nvarchar(128)");
b.Property<int>("Order")
.HasColumnType("int");
b.Property<string>("PermissionName")
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.Property<string>("Props")
.HasColumnType("nvarchar(max)");
b.Property<int>("SourceType")
.HasColumnType("int");
b.Property<Guid?>("TenantId")
.HasColumnType("uniqueidentifier")
.HasColumnName("TenantId");
b.HasKey("Id");
b.HasIndex("TenantId", "Code")
.IsUnique()
.HasFilter("[IsDeleted] = 0");
b.ToTable("Adm_T_IntranetWidget", (string)null);
});
modelBuilder.Entity("Sozsoft.Platform.Entities.IpRestriction", b =>
{
b.Property<Guid>("Id")

View file

@ -2239,5 +2239,91 @@
"FormatTemplate": "{Prefix}-{yyyy}{MM}{dd}-{Number}",
"ResetPeriod": "Daily"
}
],
"Widgets": [
{
"Code": "today-birthdays",
"Name": "Today Birthdays",
"LabelKey": "::App.DashboardWidget.Birthdays",
"SourceType": "Builtin",
"ComponentName": "",
"PermissionName": "AbpIdentity.Users.Widget",
"Column": "Left",
"Order": 10,
"Props": "",
"IsActive": true
},
{
"Code": "documents",
"Name": "Recent Documents",
"LabelKey": "::App.DashboardWidget.Documents",
"SourceType": "Builtin",
"ComponentName": "",
"PermissionName": "App.Files.Widget",
"Column": "Left",
"Order": 20,
"Props": "",
"IsActive": true
},
{
"Code": "active-surveys",
"Name": "Active Surveys",
"LabelKey": "::App.DashboardWidget.Surveys",
"SourceType": "Builtin",
"ComponentName": "",
"PermissionName": "App.Intranet.Survey.Widget",
"Column": "Left",
"Order": 30,
"Props": "",
"IsActive": true
},
{
"Code": "social-wall",
"Name": "Social Wall",
"LabelKey": "::App.DashboardWidget.SocialWall",
"SourceType": "Builtin",
"ComponentName": "",
"PermissionName": "App.Intranet.SocialPost.Widget",
"Column": "Center",
"Order": 40,
"Props": "",
"IsActive": true
},
{
"Code": "announcements",
"Name": "Announcements",
"LabelKey": "::App.Intranet.Announcement",
"SourceType": "Builtin",
"ComponentName": "",
"PermissionName": "App.Intranet.Announcement.Widget",
"Column": "Right",
"Order": 50,
"Props": "",
"IsActive": true
},
{
"Code": "upcoming-events",
"Name": "Upcoming Events",
"LabelKey": "::App.DashboardWidget.Events",
"SourceType": "Builtin",
"ComponentName": "",
"PermissionName": "App.Intranet.Events.Event.Widget",
"Column": "Right",
"Order": 60,
"Props": "",
"IsActive": true
},
{
"Code": "role-list",
"Name": "Role List",
"LabelKey": "::AbpIdentity.Roles",
"SourceType": "Custom",
"ComponentName": "RoleComponent",
"PermissionName": "AbpIdentity.Roles",
"Column": "Center",
"Order": 20,
"Props": "",
"IsActive": true
}
]
}

View file

@ -69,6 +69,7 @@ public class TenantSeederDto
public List<EventSeedDto> Events { get; set; }
public List<EventPhotoSeedDto> EventPhotos { get; set; }
public List<EventCommentSeedDto> EventComments { get; set; }
public List<IntranetWidgetSeedDto> Widgets { get; set; }
}
public class SequenceSeedDto
@ -195,6 +196,20 @@ public class SurveyQuestionOptionSeedDto
public int Order { get; set; }
}
public class IntranetWidgetSeedDto
{
public string Code { get; set; }
public string Name { get; set; }
public string LabelKey { get; set; }
public IntranetWidgetSourceType SourceType { get; set; }
public string ComponentName { get; set; }
public string PermissionName { get; set; }
public IntranetWidgetColumn Column { get; set; }
public int Order { get; set; }
public string Props { get; set; }
public bool IsActive { get; set; } = true;
}
public class AnnouncementSeedDto
{
public string Title { get; set; }
@ -487,6 +502,7 @@ public class TenantDataSeeder : IDataSeedContributor, ITransientDependency
private readonly IRepository<Department, Guid> _departmentRepository;
private readonly IRepository<JobPosition, Guid> _jobPositionRepository;
private readonly IRepository<Announcement, Guid> _announcementRepository;
private readonly IRepository<IntranetWidget, Guid> _intranetWidgetRepository;
private readonly IRepository<Survey, Guid> _surveyRepository;
private readonly IRepository<SurveyQuestion, Guid> _surveyQuestionRepository;
private readonly IRepository<SurveyQuestionOption, Guid> _surveyQuestionOptionRepository;
@ -531,6 +547,7 @@ public class TenantDataSeeder : IDataSeedContributor, ITransientDependency
IRepository<JobPosition, Guid> jobPositionRepository,
IRepository<Announcement, Guid> announcementRepository,
IRepository<IntranetWidget, Guid> intranetWidgetRepository,
IRepository<Survey, Guid> surveyRepository,
IRepository<SurveyQuestion, Guid> surveyQuestionRepository,
IRepository<SurveyQuestionOption, Guid> surveyQuestionOptionRepository,
@ -572,6 +589,7 @@ public class TenantDataSeeder : IDataSeedContributor, ITransientDependency
_departmentRepository = departmentRepository;
_jobPositionRepository = jobPositionRepository;
_announcementRepository = announcementRepository;
_intranetWidgetRepository = intranetWidgetRepository;
_surveyRepository = surveyRepository;
_surveyQuestionRepository = surveyQuestionRepository;
_surveyQuestionOptionRepository = surveyQuestionOptionRepository;
@ -1006,6 +1024,26 @@ public class TenantDataSeeder : IDataSeedContributor, ITransientDependency
}, autoSave: true);
}
foreach (var item in items.Widgets)
{
if (await _intranetWidgetRepository.AnyAsync(x => x.Code == item.Code))
continue;
await _intranetWidgetRepository.InsertAsync(new IntranetWidget
{
Code = item.Code,
Name = item.Name,
LabelKey = item.LabelKey,
SourceType = item.SourceType,
ComponentName = item.ComponentName,
PermissionName = item.PermissionName,
Column = item.Column,
Order = item.Order,
Props = item.Props,
IsActive = item.IsActive
});
}
foreach (var item in items.Announcements)
{
var exists = await _announcementRepository.AnyAsync(x => x.Title == item.Title);

File diff suppressed because one or more lines are too long

View file

@ -7,6 +7,25 @@ export interface IntranetDashboardDto {
announcements: AnnouncementDto[]
surveys: SurveyDto[]
socialPosts: SocialPostDto[]
widgets: IntranetWidgetDto[]
}
// Dashboard'da yayınlanan widget tanımı; kolon, sıra ve yetki veritabanından gelir.
export type IntranetWidgetSourceType = 0 | 1 // 0: Builtin (koda gömülü), 1: Custom (CustomComponent)
export type IntranetWidgetColumn = 0 | 1 | 2 // 0: left, 1: center, 2: right
export interface IntranetWidgetDto {
id: string
code: string
name: string
labelKey: string
sourceType: IntranetWidgetSourceType
componentName: string
permissionName: string
column: IntranetWidgetColumn
order: number
props: string
}
// Etkinlik

View file

@ -781,7 +781,7 @@ const SqlObjectExplorer = ({
closeCtx()
}}
>
{ctxNode!.folder === 'views' && <FaEye className="text-purple-500" />}
{ctxNode!.folder === 'views' && <FaEye className="text-blue-500" />}
{ctxNode!.folder === 'procedures' && <FaCog className="text-green-600" />}
{ctxNode!.folder === 'functions' && <FaCode className="text-orange-500" />}
View Definition

View file

@ -24,14 +24,11 @@ import {
FaTasks,
FaChartBar,
FaRegCalendarAlt,
FaToggleOn,
FaToggleOff,
} from 'react-icons/fa'
import { sqlObjectManagerService } from '@/services/sql-query-manager.service'
import { getMenus } from '@/services/menu.service'
import { useLocalization } from '@/utils/hooks/useLocalization'
import { CascadeBehavior, SqlTableRelation, RelationshipType } from '@/proxy/developerKit/models'
import type { CrudEndpoint } from '@/proxy/developerKit/models'
import { useNavigationIcons } from '@/proxy/menus/navigation-icon.config'
import { MenuItem } from '@/proxy/menus/menu'
import {
@ -48,7 +45,6 @@ import {
CRUD_ENDPOINT_PERMISSION,
CRUD_OPERATION_TYPES,
type CrudOperationType,
METHOD_COLOR,
getErrorMessage,
getOperationMeta,
toEntityName,
@ -1338,7 +1334,6 @@ const SqlTableDesignerDialog = ({
...CRUD_OPERATION_TYPES,
])
/** Duzenlenen tablonun kayitli endpoint'leri; adim mevcut durumu gosterir. */
const [existingCrudEndpoints, setExistingCrudEndpoints] = useState<CrudEndpoint[]>([])
const [crudLoading, setCrudLoading] = useState(false)
const [columns, setColumns] = useState<ColumnDefinition[]>([createEmptyColumn()])
const [originalColumns, setOriginalColumns] = useState<ColumnDefinition[]>([])
@ -1431,7 +1426,6 @@ const SqlTableDesignerDialog = ({
developerKitService
.getEndpointsByEntity(toEntityName(initialTableData.tableName))
.then((endpoints) => {
setExistingCrudEndpoints(endpoints)
if (endpoints.length === 0) return
setGenerateCrud(true)
const active = endpoints
@ -1439,7 +1433,7 @@ const SqlTableDesignerDialog = ({
.map((e) => e.operationType as CrudOperationType)
setCrudOperations(CRUD_OPERATION_TYPES.filter((op) => active.includes(op)))
})
.catch(() => setExistingCrudEndpoints([]))
.catch(() => {})
.finally(() => setCrudLoading(false))
}
@ -2151,7 +2145,6 @@ const SqlTableDesignerDialog = ({
setTemplatesOpen(false)
setGenerateCrud(false)
setCrudOperations([...CRUD_OPERATION_TYPES])
setExistingCrudEndpoints([])
onClose()
}
@ -3373,39 +3366,6 @@ const SqlTableDesignerDialog = ({
<p className="mb-0 text-xs text-gray-400">{translate('::App.Platform.Loading')}</p>
)}
{existingCrudEndpoints.length > 0 && (
<div className="rounded-lg border border-blue-200 bg-blue-50 p-3 dark:border-blue-900 dark:bg-blue-900/20">
<span className="block text-sm font-medium">
{translate('::App.SqlQueryManager.ExistingCrudEndpoints')}
</span>
<span className="mb-2 block text-xs text-gray-500 dark:text-gray-400">
{translate('::App.SqlQueryManager.ExistingCrudEndpointsHint')}
</span>
<div className="flex flex-col gap-1">
{existingCrudEndpoints.map((endpoint) => (
<div key={endpoint.id} className="flex items-center gap-2 text-xs">
<span
className={`rounded border px-1.5 py-0.5 font-mono ${
METHOD_COLOR[endpoint.method] ||
'border-gray-200 bg-gray-100 text-gray-700 dark:border-gray-700 dark:bg-gray-700 dark:text-gray-200'
}`}
>
{endpoint.method}
</span>
<span className="font-mono text-gray-600 dark:text-gray-300">{endpoint.path}</span>
<span className="ml-auto">
{endpoint.isActive ? (
<FaToggleOn className="text-base text-green-500" />
) : (
<FaToggleOff className="text-base text-gray-300 dark:text-gray-600" />
)}
</span>
</div>
))}
</div>
</div>
)}
<label className="flex items-start gap-3 cursor-pointer rounded-lg border p-3 dark:border-gray-700">
<Checkbox checked={generateCrud} onChange={(checked) => setGenerateCrud(checked)} />
<span>

View file

@ -15,6 +15,7 @@ import {
AnnouncementDto,
EventDto,
IntranetDashboardDto,
IntranetWidgetDto,
SurveyAnswerDto,
SurveyDto,
} from '@/proxy/intranet/models'
@ -25,7 +26,12 @@ import { currentLocalDate } from '@/utils/dateUtils'
import { useStoreActions, useStoreState } from '@/store/store'
import Button from '@/components/ui/Button'
import { LuX } from 'react-icons/lu'
import type { DashboardWidgetColumn, DashboardWidgetDefinition } from './dashboardWidget'
import { useComponents } from '@/contexts/ComponentContext'
import type {
DashboardWidgetColumn,
DashboardWidgetContext,
DashboardWidgetDefinition,
} from './dashboardWidget'
dayjs.extend(relativeTime)
dayjs.extend(isBetween)
@ -33,16 +39,34 @@ dayjs.extend(localizedFormat)
const dashboardColumns: DashboardWidgetColumn[] = ['left', 'center', 'right']
// IntranetWidget.Column enum sırası ile aynı: 0 = Left, 1 = Center, 2 = Right
const widgetColumns: DashboardWidgetColumn[] = ['left', 'center', 'right']
type DashboardWidgetModule = { dashboardWidget?: DashboardWidgetDefinition }
const dashboardWidgets = Object.values(
import.meta.glob<DashboardWidgetModule>(['./widgets/*.tsx', './SocialWall/index.tsx'], {
eager: true,
}),
// Koda gömülü widget'ların render tablosu. Hangisinin nerede, hangi sırada ve hangi yetkiyle
// yayınlandığı IntranetWidget kaydından gelir; buradaki kayıt yalnızca `code` ile eşleşir.
const builtinWidgets = new Map(
Object.values(
import.meta.glob<DashboardWidgetModule>(['./widgets/*.tsx', './SocialWall/index.tsx'], {
eager: true,
}),
)
.map((module) => module.dashboardWidget)
.filter((widget): widget is DashboardWidgetDefinition => Boolean(widget))
.map((widget) => [widget.id, widget] as const),
)
.map((module) => module.dashboardWidget)
.filter((widget): widget is DashboardWidgetDefinition => Boolean(widget))
.sort((left, right) => left.order - right.order)
// Custom widget'ın prop'ları kayıtta JSON metni olarak durur; bozuk içerik widget'ı düşürmemeli.
const parseWidgetProps = (props: string): Record<string, unknown> => {
if (!props) return {}
try {
const parsed = JSON.parse(props)
return parsed && typeof parsed === 'object' ? (parsed as Record<string, unknown>) : {}
} catch {
return {}
}
}
const layoutPresets = [
{
@ -82,6 +106,7 @@ const columnSpanClasses: Record<number, string> = {
const IntranetDashboard: React.FC = () => {
const { checkPermission } = usePermission()
const { renderComponent, getComponentByName, compiling: componentsCompiling } = useComponents()
const [selectedAnnouncement, setSelectedAnnouncement] = useState<AnnouncementDto | null>(null)
const [selectedEvent, setSelectedEvent] = useState<EventDto | null>(null)
const [selectedSurvey, setSelectedSurvey] = useState<SurveyDto | null>(null)
@ -182,7 +207,38 @@ const IntranetDashboard: React.FC = () => {
)
}
const grantedPolicies = useStoreState((state) => state.abpConfig?.config?.auth.grantedPolicies)
// Sunucu yetkisiz widget'ı zaten elemiştir; istemcideki kontrol yetkiyi ikinci katmanda da uygular.
const widgets = (intranetDashboard?.widgets || []).filter(
(widget) => !widget.permissionName || checkPermission(widget.permissionName),
)
const findWidget = (code: string) => widgets.find((widget) => widget.code === code)
// Yeni tanımlanan widget kolonun sonuna değil, kayıttaki sırasının gerektirdiği yere girer.
const insertByOrder = (columnCodes: string[], widget: IntranetWidgetDto) => {
const index = columnCodes.findIndex((code) => {
const placed = findWidget(code)
return placed !== undefined && placed.order > widget.order
})
if (index === -1) columnCodes.push(widget.code)
else columnCodes.splice(index, 0, widget.code)
}
const assignedCodes = new Set(dashboardColumns.flatMap((column) => widgetOrder[column] || []))
// Gösterilecek liste kayıtlardan türetilir; kullanıcının sürükleyerek kaydettiği sıra korunur,
// kaydında yeri olmayan yeni widget kendi kolonunda sıra numarasının gerektirdiği yere girer.
// Böylece yeni tanımlanan bir widget, store güncellenmeden de ekranda görünür.
const columnWidgetCodes = (column: DashboardWidgetColumn) => {
const codes = [...new Set(widgetOrder[column] || [])].filter((code) => findWidget(code))
widgets
.filter((widget) => widgetColumns[widget.column] === column && !assignedCodes.has(widget.code))
.forEach((widget) => insertByOrder(codes, widget))
return codes
}
const initializeDefaultOrder = () => {
const defaultOrder: Record<DashboardWidgetColumn, string[]> = {
@ -190,37 +246,22 @@ const IntranetDashboard: React.FC = () => {
center: [],
right: [],
}
dashboardWidgets.forEach((widget) => {
if (checkPermission(widget.permission)) defaultOrder[widget.column].push(widget.id)
widgets.forEach((widget) => {
defaultOrder[widgetColumns[widget.column]].push(widget.code)
})
setWidgetOrder(defaultOrder)
}
// Türetilen liste sürükle-bırakın üzerine yazacağı sıradır; kayıtlar geldiğinde saklanır.
useEffect(() => {
if (!grantedPolicies) return
if (!intranetDashboard) return
const hasSavedOrder = dashboardColumns.some((column) => widgetOrder[column].length > 0)
if (!hasSavedOrder) {
initializeDefaultOrder()
return
}
// Kayıtlı sıralamayı tekilleştir ve henüz yerleştirilmemiş widget'ları ekle
const order: Record<DashboardWidgetColumn, string[]> = {
left: [...new Set(widgetOrder.left || [])],
center: [...new Set(widgetOrder.center || [])],
right: [...new Set(widgetOrder.right || [])],
}
const allAssigned = new Set([...order.left, ...order.center, ...order.right])
dashboardWidgets.forEach((widget) => {
if (!allAssigned.has(widget.id) && checkPermission(widget.permission)) {
order[widget.column].push(widget.id)
}
setWidgetOrder({
left: columnWidgetCodes('left'),
center: columnWidgetCodes('center'),
right: columnWidgetCodes('right'),
})
setWidgetOrder(order)
}, [grantedPolicies])
}, [intranetDashboard])
const setWidgetVisibility = (widgetId: string, visible: boolean) => {
const next = visible
@ -231,9 +272,7 @@ const IntranetDashboard: React.FC = () => {
const activeLayout =
layoutPresets.find((layout) => layout.id === selectedLayout) || layoutPresets[0]
const hiddenWidgets = dashboardWidgets.filter(
(widget) => hiddenWidgetIds.includes(widget.id) && checkPermission(widget.permission),
)
const hiddenWidgets = widgets.filter((widget) => hiddenWidgetIds.includes(widget.code))
const handleDragStart = (e: React.DragEvent, widgetId: string) => {
setDragState({ draggedId: widgetId, targetColumn: null, targetIndex: null })
@ -310,23 +349,62 @@ const IntranetDashboard: React.FC = () => {
const handleColumnDrop = (e: React.DragEvent<HTMLDivElement>, column: DashboardWidgetColumn) => {
if (!isDesignMode) return
handleDrop(e, column, widgetOrder[column].length)
handleDrop(e, column, columnWidgetCodes(column).length)
}
// Başlık anahtarı dil anahtarı listesinden seçildiğinde "::" öneki olmadan kaydedilir;
// anahtarı olmayan widget'ta kayıttaki ad gösterilir.
const widgetLabel = (widget: IntranetWidgetDto) =>
widget.labelKey
? translate(widget.labelKey.includes('::') ? widget.labelKey : `::${widget.labelKey}`)
: widget.name
const widgetContext: DashboardWidgetContext = {
dashboard: intranetDashboard,
openAnnouncement: setSelectedAnnouncement,
openEvent: setSelectedEvent,
takeSurvey: handleTakeSurvey,
}
// Widget'ın neden basılamadığı sessizce yutulmamalı; kayıt varken boş kalan bir kolon
// yapılandırma hatasını gizler.
const renderWidgetNotice = (message: string) => (
<div className="rounded-lg border border-dashed border-gray-300 bg-white p-4 text-sm text-gray-500 dark:border-gray-600 dark:bg-gray-800 dark:text-gray-400">
{message}
</div>
)
// Builtin widget koddaki kayıttan, custom widget DeveloperKit'te üretilmiş bileşenden basılır.
const renderWidgetContent = (widget: IntranetWidgetDto) => {
if (widget.sourceType === 1) {
if (!widget.componentName) {
return renderWidgetNotice(`${widget.name}: ComponentName tanımlı değil`)
}
if (!getComponentByName(widget.componentName)) {
return renderWidgetNotice(
componentsCompiling
? `${widget.componentName} yükleniyor…`
: `${widget.componentName} bulunamadı veya pasif`,
)
}
return renderComponent(widget.componentName, parseWidgetProps(widget.props))
}
const builtin = builtinWidgets.get(widget.code)
if (!builtin) {
return renderWidgetNotice(`${widget.code}: koda kayıtlı bir widget yok`)
}
return builtin.render(widgetContext)
}
const renderWidgets = (column: DashboardWidgetColumn) => {
const columnWidgets = widgetOrder[column] || []
const uniqueWidgets = [...new Set(columnWidgets)]
return uniqueWidgets
return columnWidgetCodes(column)
.map((widgetId, index) => {
const metadata = dashboardWidgets.find((w) => w.id === widgetId)
if (
!metadata ||
!checkPermission(metadata.permission) ||
hiddenWidgetIds.includes(widgetId)
)
return null
const widget = findWidget(widgetId)
if (!widget || hiddenWidgetIds.includes(widgetId)) return null
const isDragging = dragState.draggedId === widgetId
const isDropTarget = dragState.targetColumn === column && dragState.targetIndex === index
@ -417,7 +495,7 @@ const IntranetDashboard: React.FC = () => {
type="button"
className="absolute right-2 top-2 z-20 flex h-7 w-7 items-center justify-center rounded-full bg-white text-gray-500 shadow-sm dark:bg-gray-800 dark:text-gray-300"
title={translate('::App.Dashboard.HideWidget')}
aria-label={`${translate('::App.Dashboard.HideWidget')}: ${translate(metadata.labelKey)}`}
aria-label={`${translate('::App.Dashboard.HideWidget')}: ${widgetLabel(widget)}`}
draggable={false}
onMouseDown={(e) => e.stopPropagation()}
onDragStart={(e) => e.stopPropagation()}
@ -447,12 +525,7 @@ const IntranetDashboard: React.FC = () => {
<div
className={`${isDesignMode ? 'p-1.5' : ''} transition-all duration-500 ease-out`}
>
{metadata.render({
dashboard: intranetDashboard,
openAnnouncement: setSelectedAnnouncement,
openEvent: setSelectedEvent,
takeSurvey: handleTakeSurvey,
})}
{renderWidgetContent(widget)}
</div>
</div>
</div>
@ -555,15 +628,15 @@ const IntranetDashboard: React.FC = () => {
</span>
{hiddenWidgets.map((widget) => (
<Button
key={widget.id}
key={widget.code}
type="button"
variant="plain"
shape="none"
className="!h-8 !rounded-md !bg-emerald-50 !px-2 text-xs text-emerald-700 hover:!bg-emerald-100 dark:!bg-emerald-950 dark:text-emerald-300"
onClick={() => setWidgetVisibility(widget.id, true)}
onClick={() => setWidgetVisibility(widget.code, true)}
title={translate('::App.Dashboard.ShowWidget')}
>
+ {translate(widget.labelKey)}
+ {widgetLabel(widget)}
</Button>
))}
</div>
@ -572,7 +645,7 @@ const IntranetDashboard: React.FC = () => {
<div className="grid grid-cols-1 lg:grid-cols-12">
{dashboardColumns.map((column, index) => {
const isEmptyDropTarget =
isDesignMode && dragState.targetColumn === column && widgetOrder[column].length === 0
isDesignMode && dragState.targetColumn === column && columnWidgetCodes(column).length === 0
return (
<div

View file

@ -190,7 +190,7 @@ const SocialWall: React.FC = () => {
)
return (
<div className="mx-auto px-4">
<div className="mx-auto">
{/* Filter Tabs */}
<div className="flex gap-4 mb-6 border-b border-gray-200 dark:border-gray-700">
{renderTab('all', '::App.SocialWall.AllPosts')}
@ -242,10 +242,6 @@ const SocialWall: React.FC = () => {
export const dashboardWidget: DashboardWidgetDefinition = {
id: 'social-wall',
permission: 'App.Intranet.SocialPost.Widget',
column: 'center',
labelKey: '::App.DashboardWidget.SocialWall',
order: 40,
render: () => <SocialWall />,
}

View file

@ -15,11 +15,12 @@ export interface DashboardWidgetContext {
takeSurvey: (survey: SurveyDto) => void
}
/**
* Uygulamaya kodlanmış widget'ın kayıt girişi. Yalnızca render sorumluluğunu taşır:
* kolon, sıra, başlık, yetki ve yayında olup olmadığı IntranetWidget kaydından gelir.
* `id`, kaydın `Code` alanıyla eşleşmelidir.
*/
export interface DashboardWidgetDefinition {
id: string
permission: string
column: DashboardWidgetColumn
labelKey: string
order: number
render: (context: DashboardWidgetContext) => ReactNode
}

View file

@ -130,10 +130,6 @@ const Announcements: React.FC<AnnouncementsProps> = ({ announcements, onAnnounce
export const dashboardWidget: DashboardWidgetDefinition = {
id: 'announcements',
permission: 'App.Intranet.Announcement.Widget',
column: 'right',
labelKey: '::App.Intranet.Announcement',
order: 50,
render: ({ dashboard, openAnnouncement }) => (
<Announcements
announcements={dashboard?.announcements || []}

View file

@ -114,10 +114,6 @@ const Events: React.FC<EventsProps> = ({ events, onEventClick }) => {
export const dashboardWidget: DashboardWidgetDefinition = {
id: 'upcoming-events',
permission: 'App.Intranet.Events.Event.Widget',
column: 'right',
labelKey: '::App.DashboardWidget.Events',
order: 60,
render: ({ dashboard, openEvent }) => (
<Events events={dashboard?.events || []} onEventClick={openEvent} />
),

View file

@ -97,10 +97,6 @@ const RecentDocuments: React.FC<{ documents: DocumentDto[] }> = ({ documents })
export const dashboardWidget: DashboardWidgetDefinition = {
id: 'documents',
permission: 'App.Files.Widget',
column: 'left',
labelKey: '::App.DashboardWidget.Documents',
order: 20,
render: ({ dashboard }) => <RecentDocuments documents={dashboard?.documents || []} />,
}

View file

@ -195,10 +195,6 @@ const Surveys: React.FC<SurveysProps> = ({ surveys, onTakeSurvey }) => {
export const dashboardWidget: DashboardWidgetDefinition = {
id: 'active-surveys',
permission: 'App.Intranet.Survey.Widget',
column: 'left',
labelKey: '::App.DashboardWidget.Surveys',
order: 30,
render: ({ dashboard, takeSurvey }) => (
<Surveys surveys={dashboard?.surveys || []} onTakeSurvey={takeSurvey} />
),

View file

@ -57,10 +57,6 @@ const TodayBirthdays: React.FC<{ employees: UserInfoViewModel[] }> = ({ employee
export const dashboardWidget: DashboardWidgetDefinition = {
id: 'today-birthdays',
permission: 'AbpIdentity.Users.Widget',
column: 'left',
labelKey: '::App.DashboardWidget.Birthdays',
order: 10,
render: ({ dashboard }) => <TodayBirthdays employees={dashboard?.birthdays || []} />,
}