DB Migration Logs
{/*
diff --git a/ui/src/components/ui/Widget/WidgetGroup.tsx b/ui/src/components/ui/Widget/WidgetGroup.tsx
index dabba552..bbabc30f 100644
--- a/ui/src/components/ui/Widget/WidgetGroup.tsx
+++ b/ui/src/components/ui/Widget/WidgetGroup.tsx
@@ -2,9 +2,15 @@ import classNames from 'classnames'
import Widget, { type colorType } from './Widget'
import { WidgetEditDto, WidgetGroupDto } from '@/proxy/form/models'
import { useNavigate } from 'react-router-dom'
+import { useLocalization } from '@/utils/hooks/useLocalization'
export default function WidgetGroup({ widgetGroups }: { widgetGroups: WidgetGroupDto[] }) {
const navigate = useNavigate()
+ const { translate } = useLocalization()
+
+ // Sorgudan gelen basliklar dil anahtaridir; karsiligi yoksa translate metni aynen dondurur,
+ // boylece duz metin donduren eski widget sorgulari da calismaya devam eder.
+ const asText = (value?: string) => (value ? translate('::' + value) : value)
return (
@@ -28,11 +34,11 @@ export default function WidgetGroup({ widgetGroups }: { widgetGroups: WidgetGrou
style={{ gridColumn: `span ${group.colSpan} / span ${group.colSpan}` }}
>
{
if (item.onClick) {
diff --git a/ui/src/proxy/form/models.ts b/ui/src/proxy/form/models.ts
index 2d0babff..0cd63506 100644
--- a/ui/src/proxy/form/models.ts
+++ b/ui/src/proxy/form/models.ts
@@ -497,6 +497,8 @@ export interface GridEditingDto {
isBranch: boolean
isOrganizationUnit: boolean
addPageUrl?: string
+ addDialogName?: string
+ addDialogParameters?: string
}
export interface EditingFormDto {
diff --git a/ui/src/routes/route.constant.ts b/ui/src/routes/route.constant.ts
index e638da16..73df520f 100644
--- a/ui/src/routes/route.constant.ts
+++ b/ui/src/routes/route.constant.ts
@@ -41,9 +41,6 @@
entitiesEdit: '/admin/developerkit/entities/edit/:id',
components: '/admin/developerkit/components',
componentsEdit: '/admin/developerkit/components/edit/:id',
- dynamicServices: '/admin/developerkit/dynamic-services',
- dynamicServicesNew: '/admin/developerkit/dynamic-services/new',
- dynamicServicesEdit: '/admin/developerkit/dynamic-services/edit/:id',
},
reports: {
view: '/admin/reports/:report/view/:id?/:listFormCode?',
diff --git a/ui/src/services/UiEvalService.tsx b/ui/src/services/UiEvalService.tsx
index c856d780..ff814c26 100644
--- a/ui/src/services/UiEvalService.tsx
+++ b/ui/src/services/UiEvalService.tsx
@@ -4,6 +4,7 @@ import { getLocalization } from '@/services/localization.service'
import { store } from '@/store'
import { clearRedisCache } from './languageText.service'
import { kickUser } from './identity.service'
+import { dynamicServiceService } from './dynamicService.service'
import { streamSetupMigrate } from './setup.service'
import { openDbMigrateLogPanel, dispatchMigrateLog, dispatchMigrateDone } from '@/components/shared/DbMigrateLogPanel'
@@ -54,6 +55,39 @@ export abstract class UiEvalService {
}, '::App.KickUser.KickUserMessage')
}
+ /**
+ * Dynamic Service kaydını siler. Ham SQL yerine servisten geçer; yalnızca böyle
+ * yüklenmiş assembly ve Swagger kaydı da runtime'dan düşer.
+ */
+ static ApiDeleteDynamicService = (id: string, gridRef?: any) => {
+ if (!id) return
+ if (!window.confirm(UiEvalService.translate('::App.DeveloperKitDynamicServices.DeleteConfirm'))) {
+ return
+ }
+
+ void dynamicServiceService
+ .delete(id)
+ .then(() => {
+ if (gridRef?.current) {
+ gridRef.current.instance().refresh()
+ }
+ toast.push(
+
+ {UiEvalService.translate('::App.Platform.ObjectDeletedSuccessfully')}
+ ,
+ { placement: 'bottom-end' },
+ )
+ })
+ .catch(() => {
+ toast.push(
+
+ {UiEvalService.translate('::App.DeveloperKitDynamicServices.DeleteError')}
+ ,
+ { placement: 'bottom-end' },
+ )
+ })
+ }
+
/**
* Canlı sistemde migration tetikler (SQL Query Manager ve Wizard dosya yöneticisi ortak kullanır).
* Yetki sunucu tarafında `App.Setup.Migrate` izniyle doğrulanır.
diff --git a/ui/src/services/dynamicService.service.ts b/ui/src/services/dynamicService.service.ts
index dc7663c5..041117d9 100644
--- a/ui/src/services/dynamicService.service.ts
+++ b/ui/src/services/dynamicService.service.ts
@@ -57,25 +57,9 @@ export interface PublishDto {
isActive?: boolean
}
-export interface DynamicAppServiceListResult {
- items: DynamicServiceDto[]
- totalCount: number
-}
-
class DynamicServiceService {
private readonly baseUrl = '/api/app/dynamic-app-service'
- /**
- * Tüm dynamic app service'leri listele
- */
- async getList(): Promise {
- const response = await apiService.fetchData({
- url: this.baseUrl,
- method: 'GET',
- })
- return response.data
- }
-
/**
* ID'ye göre dynamic app service detayını getir
*/
@@ -87,30 +71,6 @@ class DynamicServiceService {
return response.data
}
- /**
- * Yeni dynamic app service oluştur
- */
- async create(data: DynamicServiceDto): Promise {
- const response = await apiService.fetchData({
- url: this.baseUrl,
- method: 'POST',
- data: data as any,
- })
- return response.data
- }
-
- /**
- * Dynamic app service güncelle
- */
- async update(id: string, data: Partial): Promise {
- const response = await apiService.fetchData({
- url: `${this.baseUrl}/${id}`,
- method: 'PUT',
- data: data as any,
- })
- return response.data
- }
-
/**
* Dynamic app service sil
*/
@@ -144,28 +104,6 @@ class DynamicServiceService {
})
return response.data
}
-
- /**
- * Dynamic app service'i aktif/pasif yap
- */
- async toggleActive(id: string, isActive: boolean): Promise {
- await apiService.fetchData({
- url: `${this.baseUrl}/${id}/toggle-active`,
- method: 'POST',
- data: { isActive } as any,
- })
- }
-
- /**
- * Compilation durumunu getir
- */
- async getCompilationStatus(id: string): Promise<{ status: string; error?: string }> {
- const response = await apiService.fetchData<{ status: string; error?: string }>({
- url: `${this.baseUrl}/${id}/compilation-status`,
- method: 'GET',
- })
- return response.data
- }
}
export const dynamicServiceService = new DynamicServiceService()
diff --git a/ui/src/views/admin/listForm/edit/FormTabEdit.tsx b/ui/src/views/admin/listForm/edit/FormTabEdit.tsx
index 15deeb46..a9386254 100644
--- a/ui/src/views/admin/listForm/edit/FormTabEdit.tsx
+++ b/ui/src/views/admin/listForm/edit/FormTabEdit.tsx
@@ -75,6 +75,38 @@ function FormTabEdit(props: FormEditProps & { listFormCode: string }) {
component={Input}
/>
+
+
+
+
+
+
+
+
+
+
+
+
-
-
+
-
-
-
(PivotSettingsAreaEnum
export const pivotSettingsGroupIntervalOptions = enumToList(PivotSettingsGroupIntervalEnum)
export const pivotSortDirectionOptions = enumToList(ColumnSortDirectionEnum)
export const widgetColorOptions = enumToList(WidgetColorEnum)
-export const widgetIconOptions = iconList.map((icon) => ({
- value: icon,
- label: icon,
-}))
// Tema renk haritası (border ve fill)
const styleColorMap: Record<
string,
diff --git a/ui/src/views/developerKit/DynamicServiceEditor.tsx b/ui/src/views/developerKit/DynamicServiceEditor.tsx
index abcf979a..15693d8c 100644
--- a/ui/src/views/developerKit/DynamicServiceEditor.tsx
+++ b/ui/src/views/developerKit/DynamicServiceEditor.tsx
@@ -1,5 +1,4 @@
import React, { useCallback, useEffect, useMemo, useState } from 'react'
-import { useParams, useNavigate, Link } from 'react-router-dom'
import { Editor } from '@monaco-editor/react'
import type { editor } from 'monaco-editor'
import {
@@ -8,7 +7,6 @@ import {
FaCheckCircle,
FaExclamationCircle,
FaSpinner,
- FaArrowLeft,
FaCog,
FaCode,
FaSave,
@@ -19,9 +17,9 @@ import {
type CompileResult,
type PublishResult,
} from '@/services/dynamicService.service'
-import PageTitle from '@/components/shared/PageTitle'
-import { ROUTES_ENUM } from '@/routes/route.constant'
+import { Container } from '@/components/shared'
import Button from '@/components/ui/Button'
+import Dialog from '@/components/ui/Dialog'
import { Notification, toast } from '@/components/ui'
import { useConfig } from '@/components/ui/ConfigProvider'
import Input from '@/components/ui/Input'
@@ -70,12 +68,12 @@ const INPUT_BASE_CLASS =
'w-full px-3 py-2 border rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-colors bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100'
const INPUT_IDLE_BORDER = 'border-slate-300 dark:border-gray-700'
const INPUT_ERROR_BORDER = 'border-red-500 bg-red-50 dark:bg-red-900/20'
-const LABEL_CLASS =
- 'block text-sm font-medium text-slate-700 dark:text-gray-300 mb-1'
+const LABEL_CLASS = 'block text-sm font-medium text-slate-700 dark:text-gray-300 mb-1'
const getErrorMessage = (error: unknown, fallback: string): string => {
- const response = (error as { response?: { data?: { error?: { message?: string }; message?: string } } })
- ?.response
+ const response = (
+ error as { response?: { data?: { error?: { message?: string }; message?: string } } }
+ )?.response
return (
response?.data?.error?.message ||
response?.data?.message ||
@@ -84,14 +82,22 @@ const getErrorMessage = (error: unknown, fallback: string): string => {
)
}
-const DynamicServiceEditor: React.FC = () => {
- const { id } = useParams<{ id: string }>()
- const navigate = useNavigate()
+/**
+ * Dynamic Service kod editörü. Listeden (`App.DeveloperKit.DynamicServices`) toolbar ve satır
+ * butonlarıyla açılır; diyalog kapandığında grid kendini yeniler, bu yüzden yayınlama başarılı
+ * olunca diyalog kapatılır.
+ */
+const DynamicServiceEditor: React.FC<{
+ open: boolean
+ onDialogClose: () => void
+ id?: string
+}> = ({ open, onDialogClose, id }) => {
const { translate } = useLocalization()
const { mode } = useConfig()
const [code, setCode] = useState(DEFAULT_TEMPLATE)
const [serviceName, setServiceName] = useState('')
+ const [originalName, setOriginalName] = useState('')
const [displayName, setDisplayName] = useState('')
const [description, setDescription] = useState('')
const [primaryEntityType, setPrimaryEntityType] = useState('')
@@ -134,6 +140,7 @@ const DynamicServiceEditor: React.FC = () => {
if (cancelled) return
setCode(data.code || DEFAULT_TEMPLATE)
setServiceName(data.name)
+ setOriginalName(data.name)
setDisplayName(data.displayName || '')
setDescription(data.description || '')
setPrimaryEntityType(data.primaryEntityType || '')
@@ -162,10 +169,7 @@ const DynamicServiceEditor: React.FC = () => {
} catch (error) {
const failed: CompileResult = {
success: false,
- errorMessage: getErrorMessage(
- error,
- translate('::App.DynamicServicesEditor.CompileError'),
- ),
+ errorMessage: getErrorMessage(error, translate('::App.DynamicServicesEditor.CompileError')),
compilationTimeMs: 0,
hasWarnings: false,
errors: [],
@@ -198,9 +202,10 @@ const DynamicServiceEditor: React.FC = () => {
setIsPublishing(true)
setPublishResult(null)
try {
- // Edit modunda eski servis silinip yeniden yayınlanır; silmeden önce
- // kodun derlendiğinden emin ol ki başarısız derlemede servis kaybolmasın.
- if (id) {
+ // Publish servisi ada göre bulur: ad aynıysa mevcut kayıt güncellenir ve sürüm artar.
+ // Ad değiştiyse eski kayıt kalırdı; onu silmek gerekiyor. Silmeden önce kodun
+ // derlendiğinden emin ol ki başarısız derlemede servis kaybolmasın.
+ if (id && originalName && originalName !== name) {
const compiled = await runCompile()
if (!compiled.success) {
return
@@ -219,17 +224,15 @@ const DynamicServiceEditor: React.FC = () => {
if (result.success) {
pushSuccess(translate('::App.Platform.SavedSuccessfully'))
- navigate(ROUTES_ENUM.protected.saas.developerKit.dynamicServices)
+ // Diyalog kapanışı listeyi yeniler.
+ onDialogClose()
} else {
setPublishResult(result)
}
} catch (error) {
setPublishResult({
success: false,
- errorMessage: getErrorMessage(
- error,
- translate('::App.DynamicServicesEditor.PublishError'),
- ),
+ errorMessage: getErrorMessage(error, translate('::App.DynamicServicesEditor.PublishError')),
})
} finally {
setIsPublishing(false)
@@ -242,7 +245,8 @@ const DynamicServiceEditor: React.FC = () => {
isActive,
isCompiling,
isPublishing,
- navigate,
+ onDialogClose,
+ originalName,
primaryEntityType,
pushSuccess,
runCompile,
@@ -259,7 +263,7 @@ const DynamicServiceEditor: React.FC = () => {
}
}, [code, pushError, pushSuccess, translate])
- const pageTitle = id
+ const title = id
? translate('::App.DynamicServicesEditor.EditTitle')
: translate('::App.DeveloperKitDynamicServices.NewService')
@@ -271,302 +275,283 @@ const DynamicServiceEditor: React.FC = () => {
const serviceNameError = submitted && !serviceName.trim()
const isBusy = isCompiling || isPublishing
- if (isLoading) {
- return (
-
-
-
- )
- }
-
return (
-
-
-
- {/* Header */}
-
-
- {/* Left: back + icon + title */}
-
-
-
+
+
-
- {/* Right: action buttons */}
-
-
-
- {translate('::App.DynamicServicesEditor.BackToServices')}
-
-
-
}
- >
- {translate('::App.DynamicServicesEditor.CopyCode')}
-
-
-
- ) : (
-
- )
- }
- >
- {isCompiling
- ? translate('::App.DynamicServicesEditor.Compiling')
- : translate('::App.DynamicServicesEditor.TestCompile')}
-
-
-
- ) : (
-
- )
- }
- >
- {isPublishing
- ? translate('::App.DynamicServicesEditor.Publishing')
- : translate('::App.DynamicServicesEditor.Publish')}
-
-
-
-
-
- {/* Compile / Publish result banners */}
- {compileResult && (
-
- {compileResult.success ? (
-
) : (
-
+ <>
+
+
+
+
+
+
+ {title}
+
+
+ {id
+ ? translate('::App.DynamicServicesEditor.EditSubtitle')
+ : translate('::App.DynamicServicesEditor.NewSubtitle')}
+
+
+
+
+ {/* Compile / Publish result banners */}
+ {compileResult && (
+
+ {compileResult.success ? (
+
+ ) : (
+
+ )}
+
+
+ {compileResult.success
+ ? translate('::App.DynamicServicesEditor.CompileSuccess')
+ : translate('::App.DynamicServicesEditor.CompileFailed')}
+
+ {!compileResult.success && (
+ <>
+ {compileResult.errorMessage && !compileResult.errors?.length && (
+
{compileResult.errorMessage}
+ )}
+ {!!compileResult.errors?.length && (
+
+ {compileResult.errors.map((e, i) => (
+ -
+ [{e.code}] {translate('::App.DynamicServicesEditor.Line')} {e.line}:{' '}
+ {e.message}
+
+ ))}
+
+ )}
+ >
+ )}
+
+
+ {compileResult.compilationTimeMs}ms
+
+
+ )}
+
+ {publishResult && !publishResult.success && (
+
+
+
+
+ {translate('::App.DynamicServicesEditor.PublishFailed')}
+
+ {publishResult.errorMessage && (
+
{publishResult.errorMessage}
+ )}
+
+
+ )}
+
+ {/* Two-panel layout */}
+
+ {/* LEFT PANEL — Servis Ayarları */}
+
+
+
+
+ {translate('::App.DynamicServicesEditor.ServiceSettings')}
+
+
+
+
+
+
setServiceName(e.target.value)}
+ placeholder={translate('::App.DynamicServicesEditor.ServiceNamePlaceholder')}
+ className={`${INPUT_BASE_CLASS} ${
+ serviceNameError ? INPUT_ERROR_BORDER : INPUT_IDLE_BORDER
+ }`}
+ aria-invalid={serviceNameError}
+ autoFocus
+ />
+ {serviceNameError && (
+
+ {translate('::App.DynamicServicesEditor.ServiceNameRequired')}
+
+ )}
+
+
+
+
+ setDisplayName(e.target.value)}
+ placeholder={translate('::App.DynamicServicesEditor.DisplayNamePlaceholder')}
+ className={`${INPUT_BASE_CLASS} ${INPUT_IDLE_BORDER}`}
+ />
+
+
+
+
+ setDescription(e.target.value)}
+ placeholder={translate('::App.DynamicServicesEditor.DescriptionPlaceholder')}
+ className={`${INPUT_BASE_CLASS} ${INPUT_IDLE_BORDER}`}
+ />
+
+
+
+
+ setPrimaryEntityType(e.target.value)}
+ placeholder={translate(
+ '::App.DynamicServicesEditor.PrimaryEntityTypePlaceholder',
+ )}
+ className={`${INPUT_BASE_CLASS} ${INPUT_IDLE_BORDER}`}
+ />
+
+
+
+ setIsActive(e.target.checked)}
+ className="w-4 h-4 rounded accent-blue-600 cursor-pointer"
+ />
+
+
+
+
+ {/* RIGHT PANEL — Editor */}
+
+
+
+
+
+
+ {translate('::App.DynamicServicesEditor.CodeEditor')}
+
+
+
+
+ {translate('::App.DynamicServicesEditor.LineCount')} {lineCount}
+
+ |
+
+ {translate('::App.DynamicServicesEditor.CharCount')} {charCount}
+
+
+
+
+ setCode(value ?? '')}
+ options={EDITOR_OPTIONS}
+ theme={mode === 'dark' ? 'vs-dark' : 'light'}
+ loading={}
+ />
+
+
+
+
+ >
)}
-
-
- {compileResult.success
- ? translate('::App.DynamicServicesEditor.CompileSuccess')
- : translate('::App.DynamicServicesEditor.CompileFailed')}
-
- {!compileResult.success && (
- <>
- {compileResult.errorMessage && !compileResult.errors?.length && (
-
{compileResult.errorMessage}
- )}
- {!!compileResult.errors?.length && (
-
- {compileResult.errors.map((e, i) => (
- -
- [{e.code}] {translate('::App.DynamicServicesEditor.Line')}{' '}
- {e.line}: {e.message}
-
- ))}
-
- )}
- >
- )}
-
-
- {compileResult.compilationTimeMs}ms
-
-
- )}
+
+
+ }
+ >
+ {translate('::App.DynamicServicesEditor.CopyCode')}
+
- {publishResult && !publishResult.success && (
-
-
-
-
- {translate('::App.DynamicServicesEditor.PublishFailed')}
-
- {publishResult.errorMessage && (
-
{publishResult.errorMessage}
- )}
-
-
- )}
+
+ ) : (
+
+ )
+ }
+ >
+ {isCompiling
+ ? translate('::App.DynamicServicesEditor.Compiling')
+ : translate('::App.DynamicServicesEditor.TestCompile')}
+
- {/* Two-panel layout */}
-
- {/* LEFT PANEL — Servis Ayarları */}
-
-
-
-
- {translate('::App.DynamicServicesEditor.ServiceSettings')}
-
-
-
- {/* Servis Adı */}
-
-
-
setServiceName(e.target.value)}
- placeholder={translate(
- '::App.DynamicServicesEditor.ServiceNamePlaceholder',
- )}
- className={`${INPUT_BASE_CLASS} ${
- serviceNameError ? INPUT_ERROR_BORDER : INPUT_IDLE_BORDER
- }`}
- aria-invalid={serviceNameError}
- autoFocus
- />
- {serviceNameError && (
-
- {translate('::App.DynamicServicesEditor.ServiceNameRequired')}
-
- )}
-
-
- {/* Görünen Ad */}
-
-
- setDisplayName(e.target.value)}
- placeholder={translate(
- '::App.DynamicServicesEditor.DisplayNamePlaceholder',
- )}
- className={`${INPUT_BASE_CLASS} ${INPUT_IDLE_BORDER}`}
- />
-
-
- {/* Açıklama */}
-
-
- setDescription(e.target.value)}
- placeholder={translate(
- '::App.DynamicServicesEditor.DescriptionPlaceholder',
- )}
- className={`${INPUT_BASE_CLASS} ${INPUT_IDLE_BORDER}`}
- />
-
-
- {/* Ana Entity Türü */}
-
-
- setPrimaryEntityType(e.target.value)}
- placeholder={translate(
- '::App.DynamicServicesEditor.PrimaryEntityTypePlaceholder',
- )}
- className={`${INPUT_BASE_CLASS} ${INPUT_IDLE_BORDER}`}
- />
-
-
- {/* Aktif */}
-
- setIsActive(e.target.checked)}
- className="w-4 h-4 rounded accent-blue-600 cursor-pointer"
- />
-
-
-
-
- {/* RIGHT PANEL — Editor */}
-
-
-
-
-
-
- {translate('::App.DynamicServicesEditor.CodeEditor')}
-
-
-
-
- {translate('::App.DynamicServicesEditor.LineCount')} {lineCount}
-
- |
-
- {translate('::App.DynamicServicesEditor.CharCount')} {charCount}
-
-
-
-
- setCode(value ?? '')}
- options={EDITOR_OPTIONS}
- theme={mode === 'dark' ? 'vs-dark' : 'light'}
- loading={
-
- }
- />
-
-
-
-
-
+
+ ) : (
+
+ )
+ }
+ >
+ {isPublishing
+ ? translate('::App.DynamicServicesEditor.Publishing')
+ : translate('::App.DynamicServicesEditor.Publish')}
+
+
+
+
)
}
diff --git a/ui/src/views/developerKit/DynamicServiceManager.tsx b/ui/src/views/developerKit/DynamicServiceManager.tsx
deleted file mode 100644
index c04eb609..00000000
--- a/ui/src/views/developerKit/DynamicServiceManager.tsx
+++ /dev/null
@@ -1,422 +0,0 @@
-import React, { useState, useEffect } from 'react'
-import { Link, useNavigate } from 'react-router-dom'
-import {
- FaPlus,
- FaSearch,
- FaRegEdit,
- FaTrashAlt,
- FaFilter,
- FaCalendarAlt,
- FaCode,
- FaSpinner,
- FaExternalLinkAlt,
- FaTh,
- FaList,
-} from 'react-icons/fa'
-import Widget from '@/components/common/Widget'
-import { useLocalization } from '@/utils/hooks/useLocalization'
-import { dynamicServiceService, type DynamicServiceDto } from '@/services/dynamicService.service'
-import PageTitle from '@/components/shared/PageTitle'
-import { ROUTES_ENUM } from '@/routes/route.constant'
-import Button from '@/components/ui/Button'
-import Input from '@/components/ui/Input'
-import Select from '@/components/ui/Select'
-import { useStoreActions, useStoreState } from '@/store/store'
-import type { ListViewLayoutType } from '../admin/listForm/edit/types'
-import { ACTION_BUTTON_CLASS, ACTION_BUTTON_HOVER } from '@/constants/actionButton.constant'
-
-/** Layout tercihi diger listelerle ayni yerde (admin.lists.states) saklanir. */
-const VIEW_STATE_CODE = 'developerkit-dynamicservices'
-
-const DynamicServiceManager: React.FC = () => {
- const { translate } = useLocalization()
- const navigate = useNavigate()
-
- const filterStatusOptions = [
- { value: 'all' as const, label: translate('::App.StaticLookup.All') },
- { value: 'Success' as const, label: translate('::App.Platform.Success') },
- { value: 'Failed' as const, label: translate('::App.Platform.Failed') },
- {
- value: 'Pending' as const,
- label: translate('::App.DeveloperKitDynamicServices.FilterPending'),
- },
- ]
- const [services, setServices] = useState
([])
- const [isLoading, setIsLoading] = useState(false)
- const [searchTerm, setSearchTerm] = useState('')
- const [filterStatus, setFilterStatus] = useState<'all' | 'Success' | 'Failed' | 'Pending'>('all')
-
- const { states } = useStoreState((state) => state.admin.lists)
- const { setStates } = useStoreActions((a) => a.admin.lists)
- const viewMode: ListViewLayoutType =
- states.find((s) => s.listFormCode === VIEW_STATE_CODE)?.layout === 'grid' ? 'grid' : 'card'
- const setViewMode = (layout: ListViewLayoutType) =>
- setStates({ listFormCode: VIEW_STATE_CODE, layout })
-
- useEffect(() => {
- loadServices()
- }, [])
-
- const loadServices = async () => {
- try {
- setIsLoading(true)
- const response = await dynamicServiceService.getList()
- setServices(response.items || [])
- } catch (error) {
- console.error('Servisler yüklenirken hata:', error)
- } finally {
- setIsLoading(false)
- }
- }
-
- const deleteService = async (serviceId: string) => {
- if (!confirm(translate('::App.DeveloperKitDynamicServices.DeleteConfirm'))) return
- try {
- await dynamicServiceService.delete(serviceId)
- await loadServices()
- } catch (error) {
- console.error('Servis silinirken hata:', error)
- alert(translate('::App.DeveloperKitDynamicServices.DeleteError'))
- }
- }
-
- const totalServices = services.length
- const successServices = services.filter((s) => s.compilationStatus === 'Success').length
- const failedServices = services.filter((s) => s.compilationStatus === 'Failed').length
- const activeServices = services.filter((s) => s.isActive).length
- const inactiveServices = services.filter((s) => !s.isActive).length
-
- const filteredServices = services.filter((service) => {
- const matchesSearch =
- service.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
- (service.displayName || '').toLowerCase().includes(searchTerm.toLowerCase())
- const matchesFilter = filterStatus === 'all' || service.compilationStatus === filterStatus
- return matchesSearch && matchesFilter
- })
-
- const statusBadge = (status: string) => {
- if (status === 'Success')
- return 'bg-emerald-100 text-emerald-700 dark:bg-emerald-900/30 dark:text-emerald-300'
- if (status === 'Failed') return 'bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-300'
- return 'bg-yellow-100 text-yellow-700 dark:bg-yellow-900/30 dark:text-yellow-300'
- }
-
- const openSwagger = () => {
- window.open(`${import.meta.env.VITE_API_URL}/swagger/index.html`, '_blank')
- }
-
- const renderStatusBadge = (service: DynamicServiceDto) => (
-
- {service.compilationStatus} · v{service.version}
-
- )
-
- const renderActions = (service: DynamicServiceDto) => (
-
- }
- className={`${ACTION_BUTTON_CLASS} ${ACTION_BUTTON_HOVER.blue}`}
- onClick={() =>
- navigate(
- ROUTES_ENUM.protected.saas.developerKit.dynamicServicesEdit.replace(':id', service.id),
- )
- }
- />
- }
- className={`${ACTION_BUTTON_CLASS} ${ACTION_BUTTON_HOVER.red}`}
- onClick={() => deleteService(service.id)}
- />
-
- )
-
- return (
-
-
-
- {/* Stats */}
-
-
-
-
-
-
-
-
- {/* Toolbar */}
-
-
-
- setSearchTerm(e.target.value)}
- />
-
-
-
-
-
- }
- className="w-full !h-auto !items-center !justify-center gap-2 whitespace-nowrap !rounded-lg px-4 py-2 text-white transition-colors hover:!bg-green-700 dark:!bg-green-700 dark:hover:!bg-green-800 sm:w-auto"
- onClick={openSwagger}
- >
- Swagger
-
-
-
-
-
- {translate('::App.DeveloperKitDynamicServices.NewService')}
-
-
-
- }
- variant={viewMode === 'card' ? 'solid' : 'default'}
- title={translate('::App.Platform.CardView')}
- onClick={() => setViewMode('card')}
- />
- }
- variant={viewMode === 'grid' ? 'solid' : 'default'}
- title={translate('::App.Platform.ListView')}
- onClick={() => setViewMode('grid')}
- />
-
-
-
- {/* List */}
- {isLoading ? (
-
-
-
- ) : filteredServices.length > 0 ? (
- viewMode === 'card' ? (
-
- {filteredServices.map((service) => (
-
-
-
- {/* Sol taraf */}
-
-
-
- {service.displayName && (
-
- {service.displayName}
-
- )}
-
-
{renderStatusBadge(service)}
-
- {service.description && (
-
- {service.description}
-
- )}
-
-
- {/* Sag taraf */}
- {service.lastSuccessfulCompilation && (
-
-
-
- {new Date(service.lastSuccessfulCompilation).toLocaleDateString()}
-
-
- )}
-
-
- {/* Actions */}
-
- {renderActions(service)}
-
-
-
- ))}
-
- ) : (
-
-
-
-
- |
- {translate('::App.Platform.Name')}
- |
-
- {translate('::App.Platform.DisplayName')}
- |
-
- {translate('::App.Platform.Description')}
- |
-
- {translate('::App.Listform.ListformField.Version')}
- |
-
- {translate('::App.Platform.Modified')}
- |
-
- {translate('::App.Platform.Status')}
- |
-
- {translate('::App.Platform.Actions')}
- |
-
-
-
- {filteredServices.map((service) => (
-
- |
-
- |
-
- {service.displayName}
- |
-
- {service.description}
- |
-
- v{service.version}
- |
-
- {service.lastSuccessfulCompilation
- ? new Date(service.lastSuccessfulCompilation).toLocaleDateString()
- : ''}
- |
- {renderStatusBadge(service)} |
-
- {renderActions(service)}
- |
-
- ))}
-
-
-
- )
- ) : (
-
-
-
-
-
- {searchTerm || filterStatus !== 'all'
- ? translate('::App.Platform.NoResults')
- : translate('::App.DeveloperKitDynamicServices.NoServicesYet')}
-
-
- {searchTerm || filterStatus !== 'all'
- ? translate('::App.DeveloperKitDynamicServices.TryChangingFilter')
- : translate('::App.DeveloperKitDynamicServices.GetStarted')}
-
- {!searchTerm && filterStatus === 'all' && (
-
-
- {translate('::App.DeveloperKitDynamicServices.CreateNewService')}
-
- )}
-
- )}
-
- )
-}
-
-export default DynamicServiceManager
diff --git a/ui/src/views/list/shared/cellStyles.ts b/ui/src/views/list/shared/cellStyles.ts
index 8e926679..4c3259e9 100644
--- a/ui/src/views/list/shared/cellStyles.ts
+++ b/ui/src/views/list/shared/cellStyles.ts
@@ -5,6 +5,41 @@
import type { GridDto } from '@/proxy/form/models'
import { controlStyleCondition } from '../Utils'
+/**
+ * DevExtreme, jQuery yüklüyse jQuery nesnesi, aksi hâlde düz DOM elemanı verir; ikisi de desteklenir.
+ */
+const addClass = (cellElement: any, cssClassName: string) => {
+ if (typeof cellElement?.addClass === 'function') {
+ cellElement.addClass(cssClassName)
+ return
+ }
+ cellElement?.classList?.add(...cssClassName.split(/\s+/).filter(Boolean))
+}
+
+/**
+ * `CssStyles` yazım biçimi `anahtar:değer | anahtar:değer`'dir; inline style'a eklenirken
+ * ayraç `;` olmalıdır.
+ */
+const appendStyles = (cellElement: any, cssStyles: string) => {
+ const declarations = cssStyles
+ .split('|')
+ .map((declaration) => declaration.trim())
+ .filter(Boolean)
+ .join(';')
+
+ if (!declarations) return
+
+ if (typeof cellElement?.attr === 'function') {
+ const current = cellElement.attr('style')
+ cellElement.attr('style', current ? `${current};${declarations}` : declarations)
+ return
+ }
+ if (cellElement?.style) {
+ const current = cellElement.style.cssText
+ cellElement.style.cssText = current ? `${current};${declarations}` : declarations
+ }
+}
+
/**
* `onCellPrepared` handler'ı üretir. Yapılandırılmış stil bulunmayan
* liste formlarında hiç iş yapmaması için önceden filtreleme yapılır.
@@ -28,10 +63,10 @@ export const createConditionalCellStyleHandler = (gridDto?: GridDto) => {
if (colStyle.conditionValue && !controlStyleCondition(e.data, fieldName, colStyle)) return
if (colStyle.cssClassName) {
- e.cellElement.addClass(colStyle.cssClassName)
+ addClass(e.cellElement, colStyle.cssClassName)
}
if (colStyle.cssStyles) {
- e.cellElement.attr('style', `${e.cellElement.attr('style')};${colStyle.cssStyles}`)
+ appendStyles(e.cellElement, colStyle.cssStyles)
}
})
})
diff --git a/ui/src/views/list/useToolbar.tsx b/ui/src/views/list/useToolbar.tsx
index 9684df07..84c72c6e 100644
--- a/ui/src/views/list/useToolbar.tsx
+++ b/ui/src/views/list/useToolbar.tsx
@@ -14,6 +14,7 @@ import { useStoreState } from '@/store'
import { workflowService } from '@/services/workflow.service'
import type { WorkflowRunResultDto } from '@/services/workflow.service'
import { splitWorkflowApprovers, translateWorkflowLabel } from '@/utils/workflow/workflowHelpers'
+import { safeJsonParse } from './Utils'
type ToolbarModalData = {
open: boolean
@@ -105,7 +106,27 @@ const useToolbar = ({
// Add InsertNewRecord button
if (grdOpt.editingOptionDto?.allowAdding && checkPermission(grdOpt.permissionDto?.c)) {
- if (grdOpt.editingOptionDto?.addPageUrl) {
+ if (grdOpt.editingOptionDto?.addDialogName) {
+ items.push({
+ widget: 'dxButton',
+ showText: 'always',
+ name: 'addRowButton',
+ location: 'after',
+ options: {
+ icon: 'add',
+ text: translateToolbar('::App.Platform.Add'),
+ hint: translate('::App.Platform.Add'),
+ onClick() {
+ dialog.setConfig({
+ component: grdOpt.editingOptionDto?.addDialogName,
+ props: safeJsonParse(grdOpt.editingOptionDto?.addDialogParameters, {}),
+ // Diyalog kapaninca liste yenilenir; yeni kayit hemen gorunur.
+ onClose: () => refreshData(),
+ })
+ },
+ },
+ })
+ } else if (grdOpt.editingOptionDto?.addPageUrl) {
items.push({
widget: 'dxButton',
showText: 'always',
@@ -683,6 +704,8 @@ const useToolbar = ({
dialog.setConfig({
component: action.dialogName,
props: dynamicMap,
+ // Satir butonuyla ayni davranis: diyalog kapaninca liste yenilenir.
+ onClose: () => refreshData(),
})
}
} else if (action.onClick) {
diff --git a/ui/src/views/shared/DialogContext/DialogShowComponent.tsx b/ui/src/views/shared/DialogContext/DialogShowComponent.tsx
index aef6f252..2a9b5fec 100644
--- a/ui/src/views/shared/DialogContext/DialogShowComponent.tsx
+++ b/ui/src/views/shared/DialogContext/DialogShowComponent.tsx
@@ -6,6 +6,7 @@ import AuditLogDetail from '@/views/admin/auditLog/AuditLogDetail'
import RolesPermission from '@/views/admin/role-management/RolesPermission'
import UsersPermission from '@/views/admin/user-management/UsersPermission'
import BranchSeed from '@/views/branch/BranchSeed'
+import DynamicServiceEditor from '@/views/developerKit/DynamicServiceEditor'
import { ReactElement } from 'react'
const DialogShowComponent = (): ReactElement => {
@@ -68,6 +69,14 @@ const DialogShowComponent = (): ReactElement => {
{...dialogContext.config?.props}
>
)
+ case 'DynamicServiceEditor':
+ return (
+
+ )
case 'BranchSeed':
return (