From 9de6b1d05c06c421a81226bc59c9e48aa7c9f7cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sedat=20=C3=96ZT=C3=9CRK?= <76204082+iamsedatozturk@users.noreply.github.com> Date: Wed, 26 Aug 2026 09:40:18 +0300 Subject: [PATCH] =?UTF-8?q?Wizard=20Permission=20Group=20Name=20d=C3=BCzen?= =?UTF-8?q?lemesi?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Wizard/IListFormWizardAppService.cs | 3 + .../ListForms/Wizard/ListFormWizardDto.cs | 11 + .../Wizard/WizardPermissionGroupDto.cs | 12 + .../ListForms/ListFormWizardAppService.cs | 101 +++++++- .../Migrations/LanguagesData.json | 42 +++ .../Migrations/WizardDataSeeder.cs | 11 +- .../host/wizard/20260819131500_RoleList.json | 2 + ui/src/proxy/admin/wizard/models.ts | 11 + ui/src/services/wizard.service.ts | 8 + ui/src/views/admin/listForm/wizard/Wizard.tsx | 176 +++++++++++-- .../listForm/wizard/WizardStepDeploy.tsx | 12 +- .../admin/listForm/wizard/WizardStepMenu.tsx | 241 ++++++++++++++++-- 12 files changed, 571 insertions(+), 59 deletions(-) create mode 100644 api/src/Sozsoft.Platform.Application.Contracts/ListForms/Wizard/WizardPermissionGroupDto.cs diff --git a/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Wizard/IListFormWizardAppService.cs b/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Wizard/IListFormWizardAppService.cs index 368133f1..df26f12a 100644 --- a/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Wizard/IListFormWizardAppService.cs +++ b/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Wizard/IListFormWizardAppService.cs @@ -11,6 +11,9 @@ public interface IListFormWizardAppService Task GetFile(string fileName); Task DeleteFile(string fileName); + /// Izin gruplarini iki dilli gorunen adlariyla dondurur. + Task> GetPermissionGroups(); + /// Wizard dosyasini ve bagli seed dosyalarini zip olarak dondurur. Task ExportFile(string fileName); diff --git a/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Wizard/ListFormWizardDto.cs b/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Wizard/ListFormWizardDto.cs index 4b1479c7..d3bb5fc5 100644 --- a/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Wizard/ListFormWizardDto.cs +++ b/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Wizard/ListFormWizardDto.cs @@ -75,6 +75,17 @@ public class ListFormWizardDto public string LanguageTextMenuParentTr { get; set; } public string PermissionGroupName { get; set; } + + /// + /// Izin grubunun Ingilizce gorunen adi. Grup yeni tanimlaniyorsa grup adiyla ayni olan dil + /// anahtarina bu metin yazilir; mevcut bir grup secildiginde bos gelirse sunucu veritabanindaki + /// degeri geri yazar, boylece seed dosyasi her durumda iki dilli gorunen adi tasir. + /// + public string PermissionGroupDisplayNameEn { get; set; } + + /// Izin grubunun Turkce gorunen adi; ile ayni kurallara tabidir. + public string PermissionGroupDisplayNameTr { get; set; } + public string MenuParentCode { get; set; } public string MenuParentShortName { get; set; } public string MenuParentIcon { get; set; } diff --git a/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Wizard/WizardPermissionGroupDto.cs b/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Wizard/WizardPermissionGroupDto.cs new file mode 100644 index 00000000..c5827b36 --- /dev/null +++ b/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Wizard/WizardPermissionGroupDto.cs @@ -0,0 +1,12 @@ +namespace Sozsoft.Platform.ListForms; + +/// +/// Wizard izin grubu secim listesi ogesi. Gorunen adlar, grup adiyla ayni olan dil +/// anahtarinin metinlerinden okunur; secim yapildiginda forma bu degerler doldurulur. +/// +public class WizardPermissionGroupDto +{ + public string Name { get; set; } + public string DisplayNameEn { get; set; } + public string DisplayNameTr { get; set; } +} diff --git a/api/src/Sozsoft.Platform.Application/ListForms/ListFormWizardAppService.cs b/api/src/Sozsoft.Platform.Application/ListForms/ListFormWizardAppService.cs index 12cd38f7..1b391756 100644 --- a/api/src/Sozsoft.Platform.Application/ListForms/ListFormWizardAppService.cs +++ b/api/src/Sozsoft.Platform.Application/ListForms/ListFormWizardAppService.cs @@ -138,15 +138,7 @@ public class ListFormWizardAppService( ? PlatformConsts.AppName : input.PermissionGroupName.Trim(); input.PermissionGroupName = groupName; - if (!await repoPermGroup.AnyAsync(a => a.Name == groupName)) - { - await repoPermGroup.InsertAsync(new PermissionGroupDefinitionRecord(GuidGenerator.Create(), groupName, groupName), autoSave: false); - if (string.Equals(groupName, input.MenuParentCode, StringComparison.OrdinalIgnoreCase)) - await EnsureLangKey(groupName, inserted); - else - await CreateLangKey(groupName, groupName, groupName, inserted); - inserted.PermissionGroupNames.Add(groupName); - } + await ApplyPermissionGroupAsync(input, groupName, inserted); // Permission'ları tek seferde kontrol et ve oluştur var existingPerms = await repoPerm.GetListAsync(a => a.GroupName == groupName); @@ -676,6 +668,33 @@ public class ListFormWizardAppService( return routePath; } + /// + /// Izin gruplarini iki dilli gorunen adlariyla dondurur. Gorunen ad, grup adiyla ayni olan + /// dil anahtarinin metnidir; wizard formu secim yapildiginda bu degerleri doldurur. + /// + public async Task> GetPermissionGroups() + { + var res = PlatformConsts.AppName; + var groups = await repoPermGroup.GetListAsync(); + var names = groups.Where(g => g.Name != null).Select(g => g.Name!).ToList(); + + var texts = await repoLangText.GetListAsync(a => a.ResourceName == res && names.Contains(a.Key)); + var textsByKey = texts.Where(t => t.Key != null).GroupBy(t => t.Key!).ToDictionary(g => g.Key, g => g.ToList()); + + return [.. groups + .OrderBy(g => g.Name) + .Select(g => + { + textsByKey.TryGetValue(g.Name, out var groupTexts); + return new WizardPermissionGroupDto + { + Name = g.Name, + DisplayNameEn = groupTexts?.FirstOrDefault(t => t.CultureName == cultureNameDefault)?.Value ?? g.DisplayName, + DisplayNameTr = groupTexts?.FirstOrDefault(t => t.CultureName == LanguageCodes.Tr)?.Value ?? g.DisplayName + }; + })]; + } + public async Task> GetFiles() { var outputPath = ResolveWizardSeedOutputPath(); @@ -1575,6 +1594,70 @@ public class ListFormWizardAppService( } } + /// + /// Izin grubunu ve iki dilli gorunen adini hazirlar. + /// + /// Grup yeniyse kaydi ve grup adiyla ayni olan dil anahtarini uretir; gorunen ad + /// girilmediyse grup adina duser. Grup zaten varsa veritabani metinlerine dokunulmaz, + /// yalnizca kullanici acikca farkli bir metin girdiyse guncellenir. Her durumda + /// uzerindeki gorunen adlar doldurulur; seed dosyasi mevcut bir + /// grup secildiginde de iki dilli gorunen adi tasimalidir. + /// + /// + private async Task ApplyPermissionGroupAsync(ListFormWizardDto input, string groupName, WizardInsertedRecordsDto inserted) + { + var displayEn = input.PermissionGroupDisplayNameEn?.Trim(); + var displayTr = input.PermissionGroupDisplayNameTr?.Trim(); + + if (!await repoPermGroup.AnyAsync(a => a.Name == groupName)) + { + await repoPermGroup.InsertAsync(new PermissionGroupDefinitionRecord(GuidGenerator.Create(), groupName, groupName), autoSave: false); + + // Grup adi ust menu koduyla ayniysa dil anahtari menu metinleriyle zaten uretilir; + // burada uzerine yazmak menu adini bozar. + if (string.Equals(groupName, input.MenuParentCode, StringComparison.OrdinalIgnoreCase)) + { + await EnsureLangKey(groupName, inserted); + displayEn = string.IsNullOrWhiteSpace(displayEn) ? input.LanguageTextMenuParentEn?.Trim() : displayEn; + displayTr = string.IsNullOrWhiteSpace(displayTr) ? input.LanguageTextMenuParentTr?.Trim() : displayTr; + } + else + { + displayEn = string.IsNullOrWhiteSpace(displayEn) ? groupName : displayEn; + displayTr = string.IsNullOrWhiteSpace(displayTr) ? groupName : displayTr; + await CreateLangKey(groupName, displayEn, displayTr, inserted); + } + + inserted.PermissionGroupNames.Add(groupName); + } + else + { + var (existingEn, existingTr) = await GetLangTextsAsync(groupName); + + // Mevcut grubun metinleri yalnizca kullanici bilerek degistirdiginde guncellenir. + var changed = (!string.IsNullOrWhiteSpace(displayEn) && displayEn != existingEn) + || (!string.IsNullOrWhiteSpace(displayTr) && displayTr != existingTr); + + displayEn = string.IsNullOrWhiteSpace(displayEn) ? (existingEn ?? groupName) : displayEn; + displayTr = string.IsNullOrWhiteSpace(displayTr) ? (existingTr ?? groupName) : displayTr; + + if (changed) + await CreateLangKey(groupName, displayEn, displayTr, inserted); + } + + input.PermissionGroupDisplayNameEn = string.IsNullOrWhiteSpace(displayEn) ? groupName : displayEn; + input.PermissionGroupDisplayNameTr = string.IsNullOrWhiteSpace(displayTr) ? groupName : displayTr; + } + + /// Bir dil anahtarinin Ingilizce/Turkce metinlerini dondurur; yoksa null. + private async Task<(string? En, string? Tr)> GetLangTextsAsync(string key) + { + var res = PlatformConsts.AppName; + var texts = await repoLangText.GetListAsync(a => a.ResourceName == res && a.Key == key); + return (texts.FirstOrDefault(a => a.CultureName == cultureNameDefault)?.Value, + texts.FirstOrDefault(a => a.CultureName == LanguageCodes.Tr)?.Value); + } + private async Task CreateLangKey(string key, string textEn, string textTr, WizardInsertedRecordsDto inserted = null) { var res = PlatformConsts.AppName; diff --git a/api/src/Sozsoft.Platform.DbMigrator/Migrations/LanguagesData.json b/api/src/Sozsoft.Platform.DbMigrator/Migrations/LanguagesData.json index 1a484d4f..6060cfe3 100644 --- a/api/src/Sozsoft.Platform.DbMigrator/Migrations/LanguagesData.json +++ b/api/src/Sozsoft.Platform.DbMigrator/Migrations/LanguagesData.json @@ -16290,6 +16290,48 @@ "en": "Permission Group Name", "tr": "İzin Grubu Adı" }, + { + "resourceName": "Platform", + "key": "App.WizardStep1.PermissionGroupDefine", + "en": "Define Permission Group", + "tr": "İzin Grubu Tanımla" + }, + { + "resourceName": "Platform", + "key": "App.WizardStep1.PermissionGroupDisplayName", + "en": "Permission Group Display Name", + "tr": "İzin Grubu Görünen Adı" + }, + { + "resourceName": "Platform", + "key": "App.WizardStep1.PermissionGroupDisplayNameRequired", + "en": "Define the display names of the permission group", + "tr": "İzin grubunun görünen adlarını tanımlayın" + }, + { + "resourceName": "Platform", + "key": "App.WizardStep1.PermissionGroupNewHint", + "en": "The group will be created on deploy with these display names.", + "tr": "Grup, deploy sırasında bu görünen adlarla oluşturulur." + }, + { + "resourceName": "Platform", + "key": "App.WizardStep1.PermissionGroupExistsHint", + "en": "This group already exists; changing the display names updates its texts on deploy.", + "tr": "Bu grup zaten var; görünen adları değiştirmek deploy sırasında metinlerini günceller." + }, + { + "resourceName": "Platform", + "key": "App.WizardStep1.PermissionGroupDisplayNameEn", + "en": "Permission Group Display Name (English)", + "tr": "İzin Grubu Görünen Adı (İngilizce)" + }, + { + "resourceName": "Platform", + "key": "App.WizardStep1.PermissionGroupDisplayNameTr", + "en": "Permission Group Display Name (Turkish)", + "tr": "İzin Grubu Görünen Adı (Türkçe)" + }, { "resourceName": "Platform", "key": "App.WizardStep1.NoMenusAvailable", diff --git a/api/src/Sozsoft.Platform.DbMigrator/Migrations/WizardDataSeeder.cs b/api/src/Sozsoft.Platform.DbMigrator/Migrations/WizardDataSeeder.cs index 3481eaae..c24610a3 100644 --- a/api/src/Sozsoft.Platform.DbMigrator/Migrations/WizardDataSeeder.cs +++ b/api/src/Sozsoft.Platform.DbMigrator/Migrations/WizardDataSeeder.cs @@ -192,12 +192,15 @@ public class WizardDataSeeder : IDataSeedContributor, ITransientDependency { await _repoPermGroup.InsertAsync( new PermissionGroupDefinitionRecord(Guid.NewGuid(), groupName, groupName), autoSave: true); - if (string.Equals(groupName, input.MenuParentCode, StringComparison.OrdinalIgnoreCase)) - await EnsureLangKeyAsync(groupName); - else - await CreateLangKeyAsync(groupName, groupName, groupName); } + // Grup adi ust menu koduyla ayniysa dil anahtari menu metinleriyle uretilir; aksi halde + // gorunen adlar yazilir. CreateLangKeyAsync mevcut metinlerin uzerine yazmaz. + if (string.Equals(groupName, input.MenuParentCode, StringComparison.OrdinalIgnoreCase)) + await EnsureLangKeyAsync(groupName); + else + await CreateLangKeyAsync(groupName, input.PermissionGroupDisplayNameEn?.Trim(), input.PermissionGroupDisplayNameTr?.Trim()); + // Permissions - tek seferde mevcut permission'ları çek, sonra her birini kontrol et var permQueryable = await _repoPerm.GetQueryableAsync(); var existingPerms = permQueryable.Where(a => a.GroupName == groupName).ToList(); diff --git a/configs/seeds/host/wizard/20260819131500_RoleList.json b/configs/seeds/host/wizard/20260819131500_RoleList.json index 3db37a3b..8622706a 100644 --- a/configs/seeds/host/wizard/20260819131500_RoleList.json +++ b/configs/seeds/host/wizard/20260819131500_RoleList.json @@ -33,6 +33,8 @@ "LanguageTextMenuParentEn": "Sample", "LanguageTextMenuParentTr": "Sample", "PermissionGroupName": "App.Wizard.Sample", + "PermissionGroupDisplayNameEn": "Sample", + "PermissionGroupDisplayNameTr": "Sample", "MenuParentCode": "App.Wizard.Sample", "MenuParentShortName": "Sam", "MenuParentIcon": "FcBookmark", diff --git a/ui/src/proxy/admin/wizard/models.ts b/ui/src/proxy/admin/wizard/models.ts index 10bf73e0..c14052f6 100644 --- a/ui/src/proxy/admin/wizard/models.ts +++ b/ui/src/proxy/admin/wizard/models.ts @@ -84,6 +84,10 @@ export interface ListFormWizardDto { languageTextMenuParentEn: string languageTextMenuParentTr: string permissionGroupName: string + /** Izin grubunun Ingilizce gorunen adi; grup adiyla ayni olan dil anahtarina yazilir. */ + permissionGroupDisplayNameEn: string + /** Izin grubunun Turkce gorunen adi. */ + permissionGroupDisplayNameTr: string menuParentCode: string menuParentShortName?: string menuParentIcon?: string @@ -106,6 +110,13 @@ export interface ListFormWizardDto { widgets?: WidgetEditDto[] } +/** Izin grubu secim listesi ogesi; gorunen adlar grup adiyla ayni olan dil anahtarindan gelir. */ +export interface WizardPermissionGroupDto { + name: string + displayNameEn: string + displayNameTr: string +} + export interface WizardFileInfoDto { fileName: string wizardName: string diff --git a/ui/src/services/wizard.service.ts b/ui/src/services/wizard.service.ts index 5a28157e..8fe13c57 100644 --- a/ui/src/services/wizard.service.ts +++ b/ui/src/services/wizard.service.ts @@ -3,6 +3,7 @@ import { WizardFileInfoDto, WizardImportAnalysisDto, WizardImportApplyResultDto, + WizardPermissionGroupDto, WizardSeedFileDto, } from '@/proxy/admin/wizard/models' import apiService from './api.service' @@ -14,6 +15,13 @@ export const postListFormWizard = (input: ListFormWizardDto) => data: input, }) +/** Izin gruplarini iki dilli gorunen adlariyla getirir. */ +export const getWizardPermissionGroups = () => + apiService.fetchData({ + method: 'GET', + url: `/api/app/list-form-wizard/permission-groups`, + }) + export const getWizardFiles = () => apiService.fetchData({ method: 'GET', diff --git a/ui/src/views/admin/listForm/wizard/Wizard.tsx b/ui/src/views/admin/listForm/wizard/Wizard.tsx index e5dd21c5..ab2274bf 100644 --- a/ui/src/views/admin/listForm/wizard/Wizard.tsx +++ b/ui/src/views/admin/listForm/wizard/Wizard.tsx @@ -10,7 +10,6 @@ import { useLocation, useNavigate } from 'react-router-dom' import * as Yup from 'yup' import { FaFolderOpen } from 'react-icons/fa' import { getMenus } from '@/services/menu.service' -import { getPermissions } from '@/services/identity.service' import { getDataSources } from '@/services/data-source.service' import { sqlObjectManagerService } from '@/services/sql-query-manager.service' import type { SqlObjectExplorerDto, DatabaseColumnDto } from '@/proxy/sql-query-manager/models' @@ -35,7 +34,13 @@ import WizardStepDeploy from './WizardStepDeploy' import { Container } from '@/components/shared' import { sqlDataTypeToDbType } from '../edit/options' import { useStoreActions } from '@/store/store' -import { getWizardFile, getWizardFiles, postListFormWizard } from '@/services/wizard.service' +import { useStoreState } from '@/store' +import { + getWizardFile, + getWizardFiles, + getWizardPermissionGroups, + postListFormWizard, +} from '@/services/wizard.service' import { ListFormWizardDto, WizardComponentKindEnum } from '@/proxy/admin/wizard/models' import WizardStepComponent from './WizardStepComponent' import WizardStepCustomComponent from './WizardStepCustomComponent' @@ -158,6 +163,8 @@ const initialValues: ListFormWizardDto = { languageTextMenuParentEn: '', languageTextMenuParentTr: '', permissionGroupName: '', + permissionGroupDisplayNameEn: '', + permissionGroupDisplayNameTr: '', menuParentCode: '', menuParentShortName: '', menuParentIcon: '', @@ -186,6 +193,9 @@ const step1ValidationSchema = Yup.object().shape({ createMenu: Yup.boolean(), // İzin grubu menüden bağımsızdır: menüsüz wizard'da da izinler üretilir ve bir gruba yazılır. permissionGroupName: Yup.string().required(), + // Grup adi ile birlikte iki dilli gorunen ad da seed dosyasina yazilir; bos birakilamaz. + permissionGroupDisplayNameEn: Yup.string().required(), + permissionGroupDisplayNameTr: Yup.string().required(), languageTextMenuEn: Yup.string().required(), languageTextMenuTr: Yup.string().required(), }) @@ -542,7 +552,17 @@ const Wizard = () => { const [isLoadingMenu, setIsLoadingMenu] = useState(false) const [menuTree, setMenuTree] = useState([]) const [rawMenuItems, setRawMenuItems] = useState<(MenuItem & { id?: string })[]>([]) - const { translate } = useLocalization() + const { translate, cultureName } = useLocalization() + // Dil secimi store'da tutulur; abpConfig'teki kultur, secim sonrasi config yenilenene + // kadar eski degeri tasiyabildigi icin once store okunur. + const currentLang = useStoreState((state) => state.locale.currentLang) + /** Izin grubu etiketi, uygulamanin acik olan dilinde gosterilir. */ + const isTurkishUi = (currentLang || cultureName || '').toLowerCase().startsWith('tr') + const permissionGroupLabel = (group: { + name: string + displayNameEn: string + displayNameTr: string + }) => (isTurkishUi ? group.displayNameTr : group.displayNameEn) || group.name const getMenuList = async () => { setIsLoadingMenu(true) const response = await getMenus() @@ -558,17 +578,83 @@ const Wizard = () => { // ── Permission Groups ── const [isLoadingPermissionGroup, setIsLoadingPermissionGroup] = useState(false) const [permissionGroupList, setPermissionGroupList] = useState([]) + // Secilen grubun iki dilli gorunen adini forma doldurmak icin ad -> metin esleme. + const permissionGroupTextsRef = useRef>({}) const getPermissionGroupList = async () => { setIsLoadingPermissionGroup(true) - const response = await getPermissions('R', '') - if (response.data?.groups) { - setPermissionGroupList( - response.data.groups.map((item: any) => ({ value: item.name, label: item.displayName })), - ) - } + const response = await getWizardPermissionGroups() + const groups = response.data ?? [] + permissionGroupTextsRef.current = groups.reduce( + (acc, item) => ({ + ...acc, + [item.name]: { en: item.displayNameEn ?? '', tr: item.displayNameTr ?? '' }, + }), + {} as Record, + ) + setPermissionGroupList( + groups.map((item) => ({ value: item.name, label: permissionGroupLabel(item) })), + ) setIsLoadingPermissionGroup(false) } + /** + * Izin grubu secimi/olusturulmasi. Mevcut bir grup secildiginde gorunen adlar veritabanindaki + * degerlerle doldurulur; yeni bir ad yazildiginda grup adi ilk deger olarak onerilir. Her iki + * durumda da alanlar dolu kalir, cunku seed dosyasi iki dilli gorunen adi tasimak zorundadir. + */ + const handlePermissionGroupChange = ( + groupName: string, + /** Grup mevcut degilse kullanilacak metinler; yeni olusturulan menu henuz listede yoktur. */ + fallbackTexts?: { en: string; tr: string }, + ) => { + const formik = formikRef.current + formik?.setFieldValue('permissionGroupName', groupName) + if (!groupName) { + formik?.setFieldValue('permissionGroupDisplayNameEn', '') + formik?.setFieldValue('permissionGroupDisplayNameTr', '') + return + } + // Sirasiyla: mevcut grubun metinleri, cagiranin verdigi metinler, ayni koda sahip menunun + // metinleri. Grup adi kod oldugu icin en son care olarak kullanilir. + const texts = permissionGroupTextsRef.current[groupName] + const menu = rawMenuItems.find((item) => item.code === groupName) + formik?.setFieldValue( + 'permissionGroupDisplayNameEn', + texts?.en || fallbackTexts?.en || menu?.menuTextEn || groupName, + ) + formik?.setFieldValue( + 'permissionGroupDisplayNameTr', + texts?.tr || fallbackTexts?.tr || menu?.menuTextTr || groupName, + ) + } + + /** + * Dialogda tanimlanan grup. Kayit veritabanina yazilmaz; deploy adiminda grup yoksa uretilir, + * varsa metinler yalnizca burada degistirildiginde guncellenir. Yeni ad, secim listesine de + * eklenir ki kullanici adimlar arasinda gidip gelince secim kaybolmasin. + */ + const handlePermissionGroupDefine = (group: { + name: string + displayNameEn: string + displayNameTr: string + }) => { + const formik = formikRef.current + permissionGroupTextsRef.current[group.name] = { + en: group.displayNameEn, + tr: group.displayNameTr, + } + setPermissionGroupList((prev) => + prev.some((o) => o.value === group.name) + ? prev.map((o) => + o.value === group.name ? { ...o, label: permissionGroupLabel(group) } : o, + ) + : [...prev, { value: group.name, label: permissionGroupLabel(group) }], + ) + formik?.setFieldValue('permissionGroupName', group.name) + formik?.setFieldValue('permissionGroupDisplayNameEn', group.displayNameEn) + formik?.setFieldValue('permissionGroupDisplayNameTr', group.displayNameTr) + } + useEffect(() => { getDataSourceList() getMenuList() @@ -646,6 +732,8 @@ const Wizard = () => { languageTextMenuParentEn: w.languageTextMenuParentEn ?? '', languageTextMenuParentTr: w.languageTextMenuParentTr ?? '', permissionGroupName: w.permissionGroupName ?? '', + permissionGroupDisplayNameEn: w.permissionGroupDisplayNameEn ?? '', + permissionGroupDisplayNameTr: w.permissionGroupDisplayNameTr ?? '', menuParentCode: w.menuParentCode ?? '', menuParentShortName: w.menuParentShortName ?? '', menuParentIcon: w.menuParentIcon ?? '', @@ -810,19 +898,23 @@ const Wizard = () => { return null } - const applyPermissionGroupFromRoot = (rootCode: string) => { + const applyPermissionGroupFromRoot = ( + rootCode: string, + /** Kok menu heniz listede degilse (yeni eklendi) gorunen ad olarak kullanilacak metinler. */ + fallbackTexts?: { en: string; tr: string }, + ) => { const rootItem = rawMenuItems.find((i) => i.code === rootCode) // 1. Use group field if set if (rootItem?.group) { - formikRef.current?.setFieldValue('permissionGroupName', rootItem.group) + handlePermissionGroupChange(rootItem.group) return } // 2. Match root code exactly against permission group names (e.g. "App.Saas" -> "App.Saas") const exactMatch = permissionGroupList.find((g) => g.value === rootCode) if (exactMatch) { - formikRef.current?.setFieldValue('permissionGroupName', exactMatch.value) + handlePermissionGroupChange(exactMatch.value ?? '') return } @@ -833,13 +925,53 @@ const Wizard = () => { g.value?.toLowerCase().includes(lastSegment), ) if (partialMatch) { - formikRef.current?.setFieldValue('permissionGroupName', partialMatch.value) + handlePermissionGroupChange(partialMatch.value ?? '') return } } // 4. Fallback: set rootCode as a new creatable value (SelectBox supports free input) - formikRef.current?.setFieldValue('permissionGroupName', rootCode) + handlePermissionGroupChange(rootCode, fallbackTexts) + } + + /** + * Ust menu secimi kaldirilir. Izin grubu yalnizca kaldirilan menuden turetilmisse + * (grup adi menunun kendisi ya da kok kodu ile ayni) temizlenir; kullanici baska bir grup + * sectiyse secimi korunur. + */ + const handleClearMenuParent = () => { + const formik = formikRef.current + const parentCode = formik?.values.menuParentCode ?? '' + const group = (formik?.values.permissionGroupName ?? '').toLowerCase() + const derivedFromParent = + !!group && + (group === parentCode.toLowerCase() || + group === findRootCode(rawMenuItems, parentCode).toLowerCase()) + + formik?.setFieldValue('menuParentCode', '') + formik?.setFieldValue('menuParentShortName', '') + formik?.setFieldValue('menuParentIcon', '') + formik?.setFieldValue('languageTextMenuParentEn', '') + formik?.setFieldValue('languageTextMenuParentTr', '') + if (derivedFromParent) handlePermissionGroupChange('') + } + + /** + * Menu agacindan bir menu silindiginde, o koda bagli secimler formda kalmamalidir: + * secili ust menu ve ayni koda sahip izin grubu temizlenir. + */ + const handleMenuDeleted = (code: string) => { + const formik = formikRef.current + const deleted = code.toLowerCase() + if ((formik?.values.permissionGroupName ?? '').toLowerCase() === deleted) + handlePermissionGroupChange('') + if ((formik?.values.menuParentCode ?? '').toLowerCase() === deleted) { + formik?.setFieldValue('menuParentCode', '') + formik?.setFieldValue('menuParentShortName', '') + formik?.setFieldValue('menuParentIcon', '') + formik?.setFieldValue('languageTextMenuParentEn', '') + formik?.setFieldValue('languageTextMenuParentTr', '') + } } /** @@ -908,7 +1040,12 @@ const Wizard = () => { 'menuParentShortName', menu.parentCode ? (existingRoot?.shortName ?? '') : (menu.shortName ?? ''), ) - applyPermissionGroupFromRoot(rootCode) + // Yeni menu kok seviyedeyse izin grubu ondan turetilir; gorunen adlar da menunun + // dialogda girilen TR/EN metinleridir (menu heniz listede olmadigi icin buradan gecilir). + applyPermissionGroupFromRoot( + rootCode, + menu.parentCode ? undefined : { en: menu.menuTextEn, tr: menu.menuTextTr }, + ) await getMenuList() } @@ -1210,15 +1347,14 @@ const Wizard = () => { isLoadingMenu={isLoadingMenu} onMenuParentChange={handleMenuParentChange} onCreateMenuChange={handleCreateMenuChange} - onClearMenuParent={() => { - formikRef.current?.setFieldValue('menuParentCode', '') - formikRef.current?.setFieldValue('menuParentShortName', '') - formikRef.current?.setFieldValue('menuParentIcon', '') - }} + onClearMenuParent={handleClearMenuParent} + onMenuDeleted={handleMenuDeleted} onMenuCreated={handleMenuCreated} onReloadMenu={getMenuList} permissionGroupList={permissionGroupList} isLoadingPermissionGroup={isLoadingPermissionGroup} + onPermissionGroupChange={handlePermissionGroupChange} + onPermissionGroupDefine={handlePermissionGroupDefine} onBack={() => setCurrentStep( values.componentKind === WizardComponentKindEnum.Custom diff --git a/ui/src/views/admin/listForm/wizard/WizardStepDeploy.tsx b/ui/src/views/admin/listForm/wizard/WizardStepDeploy.tsx index 7a524352..410a1b4c 100644 --- a/ui/src/views/admin/listForm/wizard/WizardStepDeploy.tsx +++ b/ui/src/views/admin/listForm/wizard/WizardStepDeploy.tsx @@ -1,4 +1,4 @@ -import { Button } from '@/components/ui' +import { Button } from '@/components/ui' import type { DatabaseColumnDto } from '@/proxy/sql-query-manager/models' import React, { useMemo, useState } from 'react' import { @@ -92,6 +92,7 @@ function buildLogSteps( label: translate('::App.Step4Log.ConfiguringPermission', { 0: values.permissionGroupName, }), + detail: `EN: ${values.permissionGroupDisplayNameEn} / TR: ${values.permissionGroupDisplayNameTr}`, }, { id: 5, @@ -122,6 +123,7 @@ function buildLogSteps( label: translate('::App.Step4Log.ConfiguringPermission', { 0: values.permissionGroupName, }), + detail: `EN: ${values.permissionGroupDisplayNameEn} / TR: ${values.permissionGroupDisplayNameTr}`, }, { id: 5, @@ -320,6 +322,14 @@ const WizardStepDeploy = ({ label={translate('::App.WizardStep4.PermissionGroup')} value={values.permissionGroupName} /> + + diff --git a/ui/src/views/admin/listForm/wizard/WizardStepMenu.tsx b/ui/src/views/admin/listForm/wizard/WizardStepMenu.tsx index de2400eb..e3eabbfc 100644 --- a/ui/src/views/admin/listForm/wizard/WizardStepMenu.tsx +++ b/ui/src/views/admin/listForm/wizard/WizardStepMenu.tsx @@ -1,4 +1,13 @@ -import { Button, FormItem, Input, Notification, Select, Switcher, toast } from '@/components/ui' +import { + Button, + Dialog, + FormItem, + Input, + Notification, + Select, + Switcher, + toast, +} from '@/components/ui' import classNames from 'classnames' import { SelectBoxOption } from '@/types/shared' import { useNavigationIcons } from '@/proxy/menus/navigation-icon.config' @@ -246,6 +255,8 @@ interface MenuTreeInlineProps { isLoading: boolean invalid?: boolean onReload: () => void + /** Silinen menünün kodu; forma yazılmış bağlı seçimleri temizlemek için. */ + onDeleted: (code: string) => void /** Düzenleme dialogunu ilgili menü ile açar. */ onEditMenu: (node: MenuTreeNode & { id?: string }) => void initialExpanded?: Set @@ -259,6 +270,7 @@ function MenuTreeInline({ isLoading, invalid, onReload, + onDeleted, onEditMenu, initialExpanded, }: MenuTreeInlineProps) { @@ -295,6 +307,7 @@ function MenuTreeInline({ setSaving(true) try { await menuService.delete(node.id) + onDeleted(node.code) onReload() } catch (e: any) { toast.push(, { @@ -353,6 +366,146 @@ function MenuTreeInline({ ) } +// ─── PermissionGroupDialog ─────────────────────────────────────────────────── + +export interface PermissionGroupDefinition { + name: string + displayNameEn: string + displayNameTr: string +} + +interface PermissionGroupDialogProps { + isOpen: boolean + onClose: () => void + /** Dialog acilirken forma yazilmis degerler; yeni tanimda bos gelir. */ + initialValue: PermissionGroupDefinition + /** Mevcut izin gruplarinin adlari; girilen ad bunlardan biriyse uyari gosterilir. */ + existingNames: string[] + onSave: (group: PermissionGroupDefinition) => void +} + +/** + * Izin grubu tanimlama dialogu. AbpPermissionGroups'ta Name ve DisplayName ayni yazilir, + * gorunen ad dil anahtarindan cozulur; bu yuzden burada Name ile birlikte iki dilli gorunen + * ad alinir. Kayit yalnizca wizard formuna yazilir, veritabani kaydi deploy adiminda uretilir. + */ +function PermissionGroupDialog({ + isOpen, + onClose, + initialValue, + existingNames, + onSave, +}: PermissionGroupDialogProps) { + const { translate } = useLocalization() + const [form, setForm] = useState(initialValue) + + useEffect(() => { + if (isOpen) setForm(initialValue) + }, [isOpen, initialValue]) + + const name = form.name.trim() + const isExisting = existingNames.some((n) => n.toLowerCase() === name.toLowerCase()) + const canSave = !!name && !!form.displayNameEn.trim() && !!form.displayNameTr.trim() + + const fieldCls = + 'h-9 px-3 py-2 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 text-sm text-gray-700 dark:text-gray-200 outline-none focus:border-indigo-400 w-full' + const labelCls = 'text-xs font-medium text-gray-500 dark:text-gray-400 mb-1' + + return ( + +
+ {/* Header */} +
+ {isExisting ? ( + + ) : ( + + )} +
+ {translate('::App.WizardStep1.PermissionGroupDefine')} +
+
+ + {/* Name */} +
+ + setForm((p) => ({ ...p, name: e.target.value }))} + /> +
+ + {/* Display names */} +
+
+ + setForm((p) => ({ ...p, displayNameTr: e.target.value }))} + /> +
+
+ + setForm((p) => ({ ...p, displayNameEn: e.target.value }))} + /> +
+
+ + {/* Ad mevcut bir gruba denk geliyorsa deploy o grubun metinlerini gunceller. */} +

+ {translate( + isExisting + ? '::App.WizardStep1.PermissionGroupExistsHint' + : '::App.WizardStep1.PermissionGroupNewHint', + )} +

+ + {/* Footer */} +
+ + +
+
+
+ ) +} + // ─── WizardStepMenu ────────────────────────────────────────────────────────────── export interface WizardStepMenuProps { @@ -369,7 +522,10 @@ export interface WizardStepMenuProps { onMenuParentChange: (code: string) => void /** Menuye eklenip eklenmeyecegi; false iken menuye ozel alanlar hic sorulmaz. */ onCreateMenuChange: (createMenu: boolean) => void + /** Ust menu secimini ve ona bagli olarak turetilen izin grubunu temizler. */ onClearMenuParent: () => void + /** Menu agacindan bir menu silindiginde cagrilir; koda bagli secimler temizlenir. */ + onMenuDeleted: (code: string) => void onMenuCreated: (menu: { code: string parentCode?: string @@ -381,6 +537,10 @@ export interface WizardStepMenuProps { onReloadMenu: () => void permissionGroupList: SelectBoxOption[] isLoadingPermissionGroup: boolean + /** Listeden grup secildiginde iki dilli gorunen ad alanlarini da doldurur. */ + onPermissionGroupChange: (groupName: string) => void + /** Dialogda tanimlanan grup adi ve iki dilli gorunen adi forma yazar. */ + onPermissionGroupDefine: (group: PermissionGroupDefinition) => void onBack: () => void onNext: () => void translate: (key: string) => string @@ -404,15 +564,19 @@ const WizardStepMenu = ({ onMenuParentChange, onCreateMenuChange, onClearMenuParent, + onMenuDeleted, onMenuCreated, onReloadMenu, permissionGroupList, isLoadingPermissionGroup, + onPermissionGroupChange, + onPermissionGroupDefine, onBack, onNext, translate, }: WizardStepMenuProps) => { const [menuDialogOpen, setMenuDialogOpen] = useState(false) + const [permissionGroupDialogOpen, setPermissionGroupDialogOpen] = useState(false) const [menuDialogParentCode, setMenuDialogParentCode] = useState('') // Dolu ise dialog düzenleme modunda açılır. const [menuBeingEdited, setMenuBeingEdited] = useState<(MenuItem & { id?: string }) | null>(null) @@ -463,6 +627,10 @@ const WizardStepMenu = ({ // burada kontrol edilmezse "Next" aktif görünüp sessizce çalışmıyordu. createMenu && !(Number(values.menuOrder) >= 1) && translate('::App.WizardStep1.MenuOrder'), !values.permissionGroupName && translate('::App.WizardStep1.PermissionGroupName'), + !values.permissionGroupDisplayNameEn && + translate('::App.WizardStep1.PermissionGroupDisplayNameEn'), + !values.permissionGroupDisplayNameTr && + translate('::App.WizardStep1.PermissionGroupDisplayNameTr'), !values.languageTextMenuEn && translate('::App.WizardStep4.MenuEn'), !values.languageTextMenuTr && translate('::App.WizardStep4.MenuTr'), createMenu && !values.menuIcon && translate('::App.WizardStep4.MenuIcon'), @@ -590,6 +758,7 @@ const WizardStepMenu = ({ isLoading={isLoadingMenu} invalid={false} onReload={onReloadMenu} + onDeleted={onMenuDeleted} onEditMenu={handleEditMenu} initialExpanded={ values.menuParentCode @@ -661,32 +830,54 @@ const WizardStepMenu = ({ errorMessage={errors.permissionGroupName} asterisk={true} > - - {({ field, form }: FieldProps) => ( - o.value === values.permissionGroupName, + ) ?? { + label: values.permissionGroupName, + value: values.permissionGroupName, + }) + : null + } + onChange={(option) => onPermissionGroupChange(option?.value ?? '')} + /> + )} + + + {/* Gorunen adlar dialogda tanimlanir; listeden secilen grupta da duzenlenebilir. */} +