Wizard Permission Group Name düzenlemesi

This commit is contained in:
Sedat ÖZTÜRK 2026-08-26 09:40:18 +03:00
parent ec6b35d6d5
commit 9de6b1d05c
12 changed files with 571 additions and 59 deletions

View file

@ -11,6 +11,9 @@ public interface IListFormWizardAppService
Task<WizardSeedFileDto> GetFile(string fileName);
Task DeleteFile(string fileName);
/// <summary>Izin gruplarini iki dilli gorunen adlariyla dondurur.</summary>
Task<List<WizardPermissionGroupDto>> GetPermissionGroups();
/// <summary>Wizard dosyasini ve bagli seed dosyalarini zip olarak dondurur.</summary>
Task<IRemoteStreamContent> ExportFile(string fileName);

View file

@ -75,6 +75,17 @@ public class ListFormWizardDto
public string LanguageTextMenuParentTr { get; set; }
public string PermissionGroupName { get; set; }
/// <summary>
/// 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.
/// </summary>
public string PermissionGroupDisplayNameEn { get; set; }
/// <summary>Izin grubunun Turkce gorunen adi; <see cref="PermissionGroupDisplayNameEn"/> ile ayni kurallara tabidir.</summary>
public string PermissionGroupDisplayNameTr { get; set; }
public string MenuParentCode { get; set; }
public string MenuParentShortName { get; set; }
public string MenuParentIcon { get; set; }

View file

@ -0,0 +1,12 @@
namespace Sozsoft.Platform.ListForms;
/// <summary>
/// Wizard izin grubu secim listesi ogesi. Gorunen adlar, grup adiyla ayni olan dil
/// anahtarinin metinlerinden okunur; secim yapildiginda forma bu degerler doldurulur.
/// </summary>
public class WizardPermissionGroupDto
{
public string Name { get; set; }
public string DisplayNameEn { get; set; }
public string DisplayNameTr { get; set; }
}

View file

@ -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;
}
/// <summary>
/// Izin gruplarini iki dilli gorunen adlariyla dondurur. Gorunen ad, grup adiyla ayni olan
/// dil anahtarinin metnidir; wizard formu secim yapildiginda bu degerleri doldurur.
/// </summary>
public async Task<List<WizardPermissionGroupDto>> 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<List<WizardFileInfoDto>> GetFiles()
{
var outputPath = ResolveWizardSeedOutputPath();
@ -1575,6 +1594,70 @@ public class ListFormWizardAppService(
}
}
/// <summary>
/// Izin grubunu ve iki dilli gorunen adini hazirlar.
/// <para>
/// 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
/// <paramref name="input"/> uzerindeki gorunen adlar doldurulur; seed dosyasi mevcut bir
/// grup secildiginde de iki dilli gorunen adi tasimalidir.
/// </para>
/// </summary>
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;
}
/// <summary>Bir dil anahtarinin Ingilizce/Turkce metinlerini dondurur; yoksa null.</summary>
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<LanguageKey> CreateLangKey(string key, string textEn, string textTr, WizardInsertedRecordsDto inserted = null)
{
var res = PlatformConsts.AppName;

View file

@ -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",

View file

@ -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();

View file

@ -33,6 +33,8 @@
"LanguageTextMenuParentEn": "Sample",
"LanguageTextMenuParentTr": "Sample",
"PermissionGroupName": "App.Wizard.Sample",
"PermissionGroupDisplayNameEn": "Sample",
"PermissionGroupDisplayNameTr": "Sample",
"MenuParentCode": "App.Wizard.Sample",
"MenuParentShortName": "Sam",
"MenuParentIcon": "FcBookmark",

View file

@ -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

View file

@ -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<WizardPermissionGroupDto[]>({
method: 'GET',
url: `/api/app/list-form-wizard/permission-groups`,
})
export const getWizardFiles = () =>
apiService.fetchData<WizardFileInfoDto[]>({
method: 'GET',

View file

@ -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<MenuTreeNode[]>([])
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<SelectBoxOption[]>([])
// Secilen grubun iki dilli gorunen adini forma doldurmak icin ad -> metin esleme.
const permissionGroupTextsRef = useRef<Record<string, { en: string; tr: string }>>({})
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<string, { en: string; tr: string }>,
)
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

View file

@ -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}
/>
<Row
label={translate('::App.WizardStep1.PermissionGroupDisplayNameTr')}
value={values.permissionGroupDisplayNameTr}
/>
<Row
label={translate('::App.WizardStep1.PermissionGroupDisplayNameEn')}
value={values.permissionGroupDisplayNameEn}
/>
<Row label={translate('::App.Listform.ListformField.Icon')} value={values.menuIcon} />
<Row label={translate('::App.WizardStep4.MenuTr')} value={values.languageTextMenuTr} />
<Row label={translate('::App.WizardStep4.MenuEn')} value={values.languageTextMenuEn} />

View file

@ -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<string>
@ -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(<Notification title={e.message} type="danger" />, {
@ -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<PermissionGroupDefinition>(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 (
<Dialog isOpen={isOpen} width={620} onClose={onClose} onRequestClose={onClose}>
<div className="flex flex-col gap-5 p-1">
{/* Header */}
<div className="flex items-center gap-2 pb-1 border-b border-gray-100 dark:border-gray-700">
{isExisting ? (
<FaEdit className="text-indigo-500 text-sm" />
) : (
<FaPlus className="text-green-500 text-sm" />
)}
<h5 className="text-base font-semibold text-gray-800 dark:text-gray-100">
{translate('::App.WizardStep1.PermissionGroupDefine')}
</h5>
</div>
{/* Name */}
<div className="flex flex-col">
<label className={labelCls}>
{translate('::App.WizardStep1.PermissionGroupName')}{' '}
<span className="text-red-500">*</span>
</label>
<Input
unstyle
autoFocus
className={`${fieldCls} font-mono`}
placeholder="App.Wizard.MyGroup"
value={form.name}
onChange={(e) => setForm((p) => ({ ...p, name: e.target.value }))}
/>
</div>
{/* Display names */}
<div className="grid grid-cols-2 gap-4">
<div className="flex flex-col">
<label className={labelCls}>
{translate('::App.WizardStep1.PermissionGroupDisplayNameTr')}{' '}
<span className="text-red-500">*</span>
</label>
<Input
unstyle
className={fieldCls}
value={form.displayNameTr}
onChange={(e) => setForm((p) => ({ ...p, displayNameTr: e.target.value }))}
/>
</div>
<div className="flex flex-col">
<label className={labelCls}>
{translate('::App.WizardStep1.PermissionGroupDisplayNameEn')}{' '}
<span className="text-red-500">*</span>
</label>
<Input
unstyle
className={fieldCls}
value={form.displayNameEn}
onChange={(e) => setForm((p) => ({ ...p, displayNameEn: e.target.value }))}
/>
</div>
</div>
{/* Ad mevcut bir gruba denk geliyorsa deploy o grubun metinlerini gunceller. */}
<p className="text-xs text-gray-500 dark:text-gray-400">
{translate(
isExisting
? '::App.WizardStep1.PermissionGroupExistsHint'
: '::App.WizardStep1.PermissionGroupNewHint',
)}
</p>
{/* Footer */}
<div className="flex justify-end gap-2 pt-1 border-t border-gray-100 dark:border-gray-700">
<Button type="button" size="sm" variant="plain" onClick={onClose}>
{translate('::App.Platform.Cancel')}
</Button>
<Button
type="button"
size="sm"
variant="solid"
disabled={!canSave}
onClick={() => {
onSave({
name,
displayNameEn: form.displayNameEn.trim(),
displayNameTr: form.displayNameTr.trim(),
})
onClose()
}}
>
{translate('::App.Platform.Save')}
</Button>
</div>
</div>
</Dialog>
)
}
// ─── 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 type="text" autoComplete="off" name="permissionGroupName">
{({ field, form }: FieldProps<string>) => (
<Select
componentAs={CreatableSelect}
field={field}
form={form}
placeholder={translate('::App.WizardStep1.PermissionGroupName')}
isClearable={true}
isLoading={isLoadingPermissionGroup}
options={permissionGroupList}
value={
values.permissionGroupName
? (permissionGroupList?.find(
(o) => o.value === values.permissionGroupName,
) ?? {
label: values.permissionGroupName,
value: values.permissionGroupName,
})
: null
}
onChange={(option) => form.setFieldValue(field.name, option?.value)}
/>
)}
</Field>
<div className="flex items-center gap-2">
<div className="flex-1">
<Field type="text" autoComplete="off" name="permissionGroupName">
{({ field, form }: FieldProps<string>) => (
<Select
field={field}
form={form}
placeholder={translate('::App.WizardStep1.PermissionGroupName')}
isLoading={isLoadingPermissionGroup}
options={permissionGroupList}
value={
values.permissionGroupName
? (permissionGroupList?.find(
(o) => o.value === values.permissionGroupName,
) ?? {
label: values.permissionGroupName,
value: values.permissionGroupName,
})
: null
}
onChange={(option) => onPermissionGroupChange(option?.value ?? '')}
/>
)}
</Field>
</div>
{/* Gorunen adlar dialogda tanimlanir; listeden secilen grupta da duzenlenebilir. */}
<Button
type="button"
size="sm"
icon={values.permissionGroupName ? <FaEdit /> : <FaPlus />}
title={translate('::App.WizardStep1.PermissionGroupDefine')}
onClick={() => setPermissionGroupDialogOpen(true)}
/>
</div>
</FormItem>
<PermissionGroupDialog
isOpen={permissionGroupDialogOpen}
existingNames={permissionGroupList.map((o) => o.value ?? '')}
initialValue={{
name: values.permissionGroupName ?? '',
displayNameEn: values.permissionGroupDisplayNameEn ?? '',
displayNameTr: values.permissionGroupDisplayNameTr ?? '',
}}
onClose={() => setPermissionGroupDialogOpen(false)}
onSave={onPermissionGroupDefine}
/>
{createMenu && (
<>
{/* Menu Icon */}