Intranet üzerinde Widgets yapısı dinamik hale getirildi
This commit is contained in:
parent
0941b9143b
commit
7a0839a3f6
34 changed files with 1181 additions and 208 deletions
|
|
@ -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ü. |
|
||||
|
|
|
|||
|
|
@ -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; } = [];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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; }
|
||||
}
|
||||
|
|
@ -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\", " +
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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": "Prop’lar"
|
||||
},
|
||||
{
|
||||
"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ğ"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6480,5 +6480,5 @@ public class ListFormSeeder_Administration : IDataSeedContributor, ITransientDep
|
|||
#endregion
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
|
|
@ -88,6 +88,7 @@ public enum TableNameEnum
|
|||
Event,
|
||||
EventPhoto,
|
||||
EventComment,
|
||||
IntranetWidget,
|
||||
MessengerConversation,
|
||||
MessengerConversationMessage,
|
||||
Videoroom,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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) },
|
||||
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
@ -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");
|
||||
|
||||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 />,
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 || []}
|
||||
|
|
|
|||
|
|
@ -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} />
|
||||
),
|
||||
|
|
|
|||
|
|
@ -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 || []} />,
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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} />
|
||||
),
|
||||
|
|
|
|||
|
|
@ -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 || []} />,
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue