Claude güncellemesi DynamicAppService değişiklikleri
This commit is contained in:
parent
b4c8fc3a2b
commit
c2f1b203ab
2 changed files with 221 additions and 131 deletions
|
|
@ -62,13 +62,6 @@ export interface DynamicAppServiceListResult {
|
||||||
totalCount: number
|
totalCount: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export const postTestCompile = (input: TestCompileDto) =>
|
|
||||||
apiService.fetchData<CompileResult>({
|
|
||||||
method: 'POST',
|
|
||||||
url: `/api/app/dynamic-app-service/test-compile`,
|
|
||||||
data: input as any,
|
|
||||||
})
|
|
||||||
|
|
||||||
class DynamicServiceService {
|
class DynamicServiceService {
|
||||||
private readonly baseUrl = '/api/app/dynamic-app-service'
|
private readonly baseUrl = '/api/app/dynamic-app-service'
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import React, { useState, useEffect } from 'react'
|
import React, { useCallback, useEffect, useMemo, useState } from 'react'
|
||||||
import { useParams, useNavigate, Link } from 'react-router-dom'
|
import { useParams, useNavigate, Link } from 'react-router-dom'
|
||||||
import { Editor } from '@monaco-editor/react'
|
import { Editor } from '@monaco-editor/react'
|
||||||
|
import type { editor } from 'monaco-editor'
|
||||||
import {
|
import {
|
||||||
FaPlay,
|
FaPlay,
|
||||||
FaCopy,
|
FaCopy,
|
||||||
|
|
@ -17,14 +18,14 @@ import {
|
||||||
dynamicServiceService,
|
dynamicServiceService,
|
||||||
type CompileResult,
|
type CompileResult,
|
||||||
type PublishResult,
|
type PublishResult,
|
||||||
postTestCompile,
|
|
||||||
type TestCompileDto,
|
|
||||||
} from '@/services/dynamicService.service'
|
} from '@/services/dynamicService.service'
|
||||||
import PageTitle from '@/components/shared/PageTitle'
|
import PageTitle from '@/components/shared/PageTitle'
|
||||||
import { ROUTES_ENUM } from '@/routes/route.constant'
|
import { ROUTES_ENUM } from '@/routes/route.constant'
|
||||||
import Button from '@/components/ui/Button'
|
import Button from '@/components/ui/Button'
|
||||||
|
import { Notification, toast } from '@/components/ui'
|
||||||
|
import { useConfig } from '@/components/ui/ConfigProvider'
|
||||||
|
|
||||||
const defaultTemplate = `using System;
|
const DEFAULT_TEMPLATE = `using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Volo.Abp.Application.Services;
|
using Volo.Abp.Application.Services;
|
||||||
|
|
@ -43,22 +44,52 @@ namespace DynamicServices
|
||||||
|
|
||||||
public virtual async Task<List<string>> GetSampleDataAsync()
|
public virtual async Task<List<string>> GetSampleDataAsync()
|
||||||
{
|
{
|
||||||
return await Task.FromResult(new List<string>
|
return await Task.FromResult(new List<string>
|
||||||
{
|
{
|
||||||
"Item 1",
|
"Item 1",
|
||||||
"Item 2",
|
"Item 2",
|
||||||
"Item 3"
|
"Item 3"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}`
|
}`
|
||||||
|
|
||||||
|
const EDITOR_OPTIONS: editor.IStandaloneEditorConstructionOptions = {
|
||||||
|
fontSize: 14,
|
||||||
|
lineNumbers: 'on',
|
||||||
|
roundedSelection: false,
|
||||||
|
scrollBeyondLastLine: false,
|
||||||
|
automaticLayout: true,
|
||||||
|
minimap: { enabled: false },
|
||||||
|
folding: true,
|
||||||
|
wordWrap: 'on',
|
||||||
|
}
|
||||||
|
|
||||||
|
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 getErrorMessage = (error: unknown, fallback: string): string => {
|
||||||
|
const response = (error as { response?: { data?: { error?: { message?: string }; message?: string } } })
|
||||||
|
?.response
|
||||||
|
return (
|
||||||
|
response?.data?.error?.message ||
|
||||||
|
response?.data?.message ||
|
||||||
|
(error instanceof Error ? error.message : undefined) ||
|
||||||
|
fallback
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
const DynamicServiceEditor: React.FC = () => {
|
const DynamicServiceEditor: React.FC = () => {
|
||||||
const { id } = useParams<{ id: string }>()
|
const { id } = useParams<{ id: string }>()
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const { translate } = useLocalization()
|
const { translate } = useLocalization()
|
||||||
|
const { mode } = useConfig()
|
||||||
|
|
||||||
const [code, setCode] = useState(defaultTemplate)
|
const [code, setCode] = useState(DEFAULT_TEMPLATE)
|
||||||
const [serviceName, setServiceName] = useState('')
|
const [serviceName, setServiceName] = useState('')
|
||||||
const [displayName, setDisplayName] = useState('')
|
const [displayName, setDisplayName] = useState('')
|
||||||
const [description, setDescription] = useState('')
|
const [description, setDescription] = useState('')
|
||||||
|
|
@ -68,120 +99,177 @@ const DynamicServiceEditor: React.FC = () => {
|
||||||
|
|
||||||
const [isCompiling, setIsCompiling] = useState(false)
|
const [isCompiling, setIsCompiling] = useState(false)
|
||||||
const [isPublishing, setIsPublishing] = useState(false)
|
const [isPublishing, setIsPublishing] = useState(false)
|
||||||
const [isLoading, setIsLoading] = useState(false)
|
const [isLoading, setIsLoading] = useState(Boolean(id))
|
||||||
|
|
||||||
const [compileResult, setCompileResult] = useState<CompileResult | null>(null)
|
const [compileResult, setCompileResult] = useState<CompileResult | null>(null)
|
||||||
const [publishResult, setPublishResult] = useState<PublishResult | null>(null)
|
const [publishResult, setPublishResult] = useState<PublishResult | null>(null)
|
||||||
|
|
||||||
const editorOptions = {
|
const pushError = useCallback((message: string) => {
|
||||||
fontSize: 14,
|
toast.push(
|
||||||
lineNumbers: 'on' as const,
|
<Notification type="danger" duration={3000}>
|
||||||
roundedSelection: false,
|
{message}
|
||||||
scrollBeyondLastLine: false,
|
</Notification>,
|
||||||
automaticLayout: true,
|
)
|
||||||
minimap: { enabled: false },
|
}, [])
|
||||||
folding: true,
|
|
||||||
wordWrap: 'on' as const,
|
const pushSuccess = useCallback((message: string) => {
|
||||||
}
|
toast.push(
|
||||||
|
<Notification type="success" duration={2000}>
|
||||||
|
{message}
|
||||||
|
</Notification>,
|
||||||
|
)
|
||||||
|
}, [])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (id) {
|
if (!id) {
|
||||||
loadService(id)
|
|
||||||
}
|
|
||||||
}, [id])
|
|
||||||
|
|
||||||
const loadService = async (serviceId: string) => {
|
|
||||||
try {
|
|
||||||
setIsLoading(true)
|
|
||||||
const data = await dynamicServiceService.getById(serviceId)
|
|
||||||
setCode(data.code)
|
|
||||||
setServiceName(data.name)
|
|
||||||
setDisplayName(data.displayName || '')
|
|
||||||
setDescription(data.description || '')
|
|
||||||
setPrimaryEntityType(data.primaryEntityType || '')
|
|
||||||
setIsActive(data.isActive ?? true)
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Servis yüklenirken hata:', error)
|
|
||||||
} finally {
|
|
||||||
setIsLoading(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleTestCompile = async () => {
|
|
||||||
if (!code.trim()) {
|
|
||||||
alert(translate('::App.DynamicServicesEditor.PleaseEnterCode'))
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
let cancelled = false
|
||||||
|
|
||||||
|
const loadService = async () => {
|
||||||
|
try {
|
||||||
|
setIsLoading(true)
|
||||||
|
const data = await dynamicServiceService.getById(id)
|
||||||
|
if (cancelled) return
|
||||||
|
setCode(data.code || DEFAULT_TEMPLATE)
|
||||||
|
setServiceName(data.name)
|
||||||
|
setDisplayName(data.displayName || '')
|
||||||
|
setDescription(data.description || '')
|
||||||
|
setPrimaryEntityType(data.primaryEntityType || '')
|
||||||
|
setIsActive(data.isActive ?? true)
|
||||||
|
} catch (error) {
|
||||||
|
if (cancelled) return
|
||||||
|
pushError(getErrorMessage(error, translate('::App.Platform.ErrorLoading')))
|
||||||
|
} finally {
|
||||||
|
if (!cancelled) setIsLoading(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
loadService()
|
||||||
|
return () => {
|
||||||
|
cancelled = true
|
||||||
|
}
|
||||||
|
}, [id, pushError, translate])
|
||||||
|
|
||||||
|
const runCompile = useCallback(async (): Promise<CompileResult> => {
|
||||||
|
setIsCompiling(true)
|
||||||
|
setCompileResult(null)
|
||||||
try {
|
try {
|
||||||
setIsCompiling(true)
|
const result = await dynamicServiceService.testCompile({ code })
|
||||||
setCompileResult(null)
|
setCompileResult(result)
|
||||||
const result = await postTestCompile({ code } as TestCompileDto)
|
return result
|
||||||
setCompileResult(result.data)
|
} catch (error) {
|
||||||
} catch (error: any) {
|
const failed: CompileResult = {
|
||||||
setCompileResult({
|
|
||||||
success: false,
|
success: false,
|
||||||
errorMessage:
|
errorMessage: getErrorMessage(
|
||||||
error.response?.data?.message ||
|
error,
|
||||||
translate('::App.DynamicServicesEditor.CompileError'),
|
translate('::App.DynamicServicesEditor.CompileError'),
|
||||||
|
),
|
||||||
compilationTimeMs: 0,
|
compilationTimeMs: 0,
|
||||||
hasWarnings: false,
|
hasWarnings: false,
|
||||||
errors: [],
|
errors: [],
|
||||||
})
|
}
|
||||||
|
setCompileResult(failed)
|
||||||
|
return failed
|
||||||
} finally {
|
} finally {
|
||||||
setIsCompiling(false)
|
setIsCompiling(false)
|
||||||
}
|
}
|
||||||
}
|
}, [code, translate])
|
||||||
|
|
||||||
const handlePublish = async () => {
|
const handleTestCompile = useCallback(async () => {
|
||||||
setSubmitted(true)
|
if (isCompiling || isPublishing) return
|
||||||
if (!code.trim() || !serviceName.trim()) {
|
if (!code.trim()) {
|
||||||
|
pushError(translate('::App.DynamicServicesEditor.PleaseEnterCode'))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (isPublishing) return
|
await runCompile()
|
||||||
try {
|
}, [code, isCompiling, isPublishing, pushError, runCompile, translate])
|
||||||
setIsPublishing(true)
|
|
||||||
setPublishResult(null)
|
|
||||||
|
|
||||||
// Edit modunda: önce eskiyi sil, sonra yeniden yayınla
|
const handlePublish = useCallback(async () => {
|
||||||
|
if (isPublishing || isCompiling) return
|
||||||
|
|
||||||
|
setSubmitted(true)
|
||||||
|
const name = serviceName.trim()
|
||||||
|
if (!code.trim() || !name) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
if (id) {
|
||||||
|
const compiled = await runCompile()
|
||||||
|
if (!compiled.success) {
|
||||||
|
return
|
||||||
|
}
|
||||||
await dynamicServiceService.delete(id)
|
await dynamicServiceService.delete(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await dynamicServiceService.publish({
|
const result = await dynamicServiceService.publish({
|
||||||
name: serviceName,
|
name,
|
||||||
code,
|
code,
|
||||||
displayName,
|
displayName: displayName.trim(),
|
||||||
description,
|
description: description.trim(),
|
||||||
primaryEntityType,
|
primaryEntityType: primaryEntityType.trim(),
|
||||||
isActive,
|
isActive,
|
||||||
})
|
})
|
||||||
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
|
pushSuccess(translate('::App.Platform.SavedSuccessfully'))
|
||||||
navigate(ROUTES_ENUM.protected.saas.developerKit.dynamicServices)
|
navigate(ROUTES_ENUM.protected.saas.developerKit.dynamicServices)
|
||||||
} else {
|
} else {
|
||||||
setPublishResult(result)
|
setPublishResult(result)
|
||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error) {
|
||||||
setPublishResult({
|
setPublishResult({
|
||||||
success: false,
|
success: false,
|
||||||
errorMessage:
|
errorMessage: getErrorMessage(
|
||||||
error.response?.data?.message ||
|
error,
|
||||||
translate('::App.DynamicServicesEditor.PublishError'),
|
translate('::App.DynamicServicesEditor.PublishError'),
|
||||||
|
),
|
||||||
})
|
})
|
||||||
} finally {
|
} finally {
|
||||||
setIsPublishing(false)
|
setIsPublishing(false)
|
||||||
}
|
}
|
||||||
}
|
}, [
|
||||||
|
code,
|
||||||
|
description,
|
||||||
|
displayName,
|
||||||
|
id,
|
||||||
|
isActive,
|
||||||
|
isCompiling,
|
||||||
|
isPublishing,
|
||||||
|
navigate,
|
||||||
|
primaryEntityType,
|
||||||
|
pushSuccess,
|
||||||
|
runCompile,
|
||||||
|
serviceName,
|
||||||
|
translate,
|
||||||
|
])
|
||||||
|
|
||||||
const copyCode = () => {
|
const copyCode = useCallback(async () => {
|
||||||
navigator.clipboard.writeText(code)
|
try {
|
||||||
alert(translate('::App.DynamicServicesEditor.CodeCopied'))
|
await navigator.clipboard.writeText(code)
|
||||||
}
|
pushSuccess(translate('::App.DynamicServicesEditor.CodeCopied'))
|
||||||
|
} catch (error) {
|
||||||
|
pushError(getErrorMessage(error, translate('::App.Platform.Error')))
|
||||||
|
}
|
||||||
|
}, [code, pushError, pushSuccess, translate])
|
||||||
|
|
||||||
const pageTitle = id
|
const pageTitle = id
|
||||||
? translate('::App.DynamicServicesEditor.EditTitle')
|
? translate('::App.DynamicServicesEditor.EditTitle')
|
||||||
: translate('::App.DeveloperKitDynamicServices.NewService')
|
: translate('::App.DeveloperKitDynamicServices.NewService')
|
||||||
|
|
||||||
|
const { lineCount, charCount } = useMemo(
|
||||||
|
() => ({ lineCount: code.split('\n').length, charCount: code.length }),
|
||||||
|
[code],
|
||||||
|
)
|
||||||
|
|
||||||
|
const serviceNameError = submitted && !serviceName.trim()
|
||||||
|
const isBusy = isCompiling || isPublishing
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center justify-center h-64">
|
<div className="flex items-center justify-center h-64">
|
||||||
|
|
@ -190,8 +278,6 @@ const DynamicServiceEditor: React.FC = () => {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const serviceNameError = submitted && !serviceName.trim()
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<PageTitle title={pageTitle} />
|
<PageTitle title={pageTitle} />
|
||||||
|
|
@ -216,16 +302,16 @@ const DynamicServiceEditor: React.FC = () => {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Right: action buttons + swagger + publish */}
|
{/* Right: action buttons */}
|
||||||
<div className="flex items-center gap-2 pr-3">
|
<div className="flex items-center gap-2 pr-3">
|
||||||
<Link
|
<Link
|
||||||
to={ROUTES_ENUM.protected.saas.developerKit.dynamicServices}
|
to={ROUTES_ENUM.protected.saas.developerKit.dynamicServices}
|
||||||
className="flex items-center gap-2 text-slate-600 dark:text-gray-300 text-black dark:text-white px-4 py-2 rounded-lg hover:text-slate-700 dark:hover:text-gray-100 transition-colors"
|
className="flex items-center gap-2 text-slate-600 dark:text-gray-300 px-4 py-2 rounded-lg hover:text-slate-800 dark:hover:text-gray-100 transition-colors"
|
||||||
>
|
>
|
||||||
<FaArrowLeft className="w-3.5 h-3.5" />
|
<FaArrowLeft className="w-3.5 h-3.5" />
|
||||||
{translate('::App.DynamicServicesEditor.BackToServices')}
|
{translate('::App.DynamicServicesEditor.BackToServices')}
|
||||||
</Link>
|
</Link>
|
||||||
<div className="h-6 w-px bg-slate-300 dark:bg-gray-700"></div>
|
<div className="h-6 w-px bg-slate-300 dark:bg-gray-700" />
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={copyCode}
|
onClick={copyCode}
|
||||||
|
|
@ -238,7 +324,7 @@ const DynamicServiceEditor: React.FC = () => {
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={handleTestCompile}
|
onClick={handleTestCompile}
|
||||||
disabled={isCompiling || !code.trim()}
|
disabled={isBusy || !code.trim()}
|
||||||
variant="solid"
|
variant="solid"
|
||||||
icon={
|
icon={
|
||||||
isCompiling ? (
|
isCompiling ? (
|
||||||
|
|
@ -256,7 +342,7 @@ const DynamicServiceEditor: React.FC = () => {
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={handlePublish}
|
onClick={handlePublish}
|
||||||
disabled={isPublishing}
|
disabled={isBusy || !code.trim()}
|
||||||
variant="solid"
|
variant="solid"
|
||||||
color="green-500"
|
color="green-500"
|
||||||
icon={
|
icon={
|
||||||
|
|
@ -295,15 +381,22 @@ const DynamicServiceEditor: React.FC = () => {
|
||||||
? translate('::App.DynamicServicesEditor.CompileSuccess')
|
? translate('::App.DynamicServicesEditor.CompileSuccess')
|
||||||
: translate('::App.DynamicServicesEditor.CompileFailed')}
|
: translate('::App.DynamicServicesEditor.CompileFailed')}
|
||||||
</span>
|
</span>
|
||||||
{!compileResult.success && compileResult.errors && compileResult.errors.length > 0 && (
|
{!compileResult.success && (
|
||||||
<ul className="mt-1 space-y-0.5">
|
<>
|
||||||
{compileResult.errors.map((e, i) => (
|
{compileResult.errorMessage && !compileResult.errors?.length && (
|
||||||
<li key={i} className="text-xs font-mono">
|
<p className="text-xs mt-0.5">{compileResult.errorMessage}</p>
|
||||||
[{e.code}] {translate('::App.DynamicServicesEditor.Line')}{' '}
|
)}
|
||||||
{e.line}: {e.message}
|
{!!compileResult.errors?.length && (
|
||||||
</li>
|
<ul className="mt-1 space-y-0.5">
|
||||||
))}
|
{compileResult.errors.map((e, i) => (
|
||||||
</ul>
|
<li key={`${e.code}-${e.line}-${i}`} className="text-xs font-mono">
|
||||||
|
[{e.code}] {translate('::App.DynamicServicesEditor.Line')}{' '}
|
||||||
|
{e.line}: {e.message}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<span className="text-xs text-slate-400 shrink-0">
|
<span className="text-xs text-slate-400 shrink-0">
|
||||||
|
|
@ -330,7 +423,6 @@ const DynamicServiceEditor: React.FC = () => {
|
||||||
<div className="flex flex-col xl:flex-row gap-4 items-stretch xl:items-start">
|
<div className="flex flex-col xl:flex-row gap-4 items-stretch xl:items-start">
|
||||||
{/* LEFT PANEL — Servis Ayarları */}
|
{/* LEFT PANEL — Servis Ayarları */}
|
||||||
<div className="w-full xl:w-1/4 shrink-0 bg-white dark:bg-gray-900 rounded-lg border border-slate-200 dark:border-gray-700 p-5 space-y-4">
|
<div className="w-full xl:w-1/4 shrink-0 bg-white dark:bg-gray-900 rounded-lg border border-slate-200 dark:border-gray-700 p-5 space-y-4">
|
||||||
{/* Panel header */}
|
|
||||||
<div className="flex items-center gap-2 pb-3 border-b border-slate-100 dark:border-gray-700">
|
<div className="flex items-center gap-2 pb-3 border-b border-slate-100 dark:border-gray-700">
|
||||||
<FaCog className="w-4 h-4 text-blue-500" />
|
<FaCog className="w-4 h-4 text-blue-500" />
|
||||||
<h2 className="font-semibold text-slate-700 dark:text-gray-100 text-sm">
|
<h2 className="font-semibold text-slate-700 dark:text-gray-100 text-sm">
|
||||||
|
|
@ -340,24 +432,21 @@ const DynamicServiceEditor: React.FC = () => {
|
||||||
|
|
||||||
{/* Servis Adı */}
|
{/* Servis Adı */}
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-slate-700 dark:text-gray-300 mb-1">
|
<label className={LABEL_CLASS} htmlFor="dse-service-name">
|
||||||
{translate('::App.DynamicServicesEditor.ServiceName')}
|
{translate('::App.DynamicServicesEditor.ServiceName')}
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
|
id="dse-service-name"
|
||||||
type="text"
|
type="text"
|
||||||
value={serviceName}
|
value={serviceName}
|
||||||
onChange={(e) => {
|
onChange={(e) => setServiceName(e.target.value)}
|
||||||
setServiceName(e.target.value)
|
|
||||||
setSubmitted(false)
|
|
||||||
}}
|
|
||||||
placeholder={translate(
|
placeholder={translate(
|
||||||
'::App.DynamicServicesEditor.ServiceNamePlaceholder',
|
'::App.DynamicServicesEditor.ServiceNamePlaceholder',
|
||||||
)}
|
)}
|
||||||
className={`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 ${
|
className={`${INPUT_BASE_CLASS} ${
|
||||||
serviceNameError
|
serviceNameError ? INPUT_ERROR_BORDER : INPUT_IDLE_BORDER
|
||||||
? 'border-red-500 bg-red-50 dark:bg-red-900/20'
|
|
||||||
: 'border-slate-300 dark:border-gray-700'
|
|
||||||
}`}
|
}`}
|
||||||
|
aria-invalid={serviceNameError}
|
||||||
autoFocus
|
autoFocus
|
||||||
/>
|
/>
|
||||||
{serviceNameError && (
|
{serviceNameError && (
|
||||||
|
|
@ -369,70 +458,76 @@ const DynamicServiceEditor: React.FC = () => {
|
||||||
|
|
||||||
{/* Görünen Ad */}
|
{/* Görünen Ad */}
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-slate-700 dark:text-gray-300 mb-1">
|
<label className={LABEL_CLASS} htmlFor="dse-display-name">
|
||||||
{translate('::App.Platform.DisplayName')}
|
{translate('::App.Platform.DisplayName')}
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
|
id="dse-display-name"
|
||||||
type="text"
|
type="text"
|
||||||
value={displayName}
|
value={displayName}
|
||||||
onChange={(e) => setDisplayName(e.target.value)}
|
onChange={(e) => setDisplayName(e.target.value)}
|
||||||
placeholder={translate(
|
placeholder={translate(
|
||||||
'::App.DynamicServicesEditor.DisplayNamePlaceholder',
|
'::App.DynamicServicesEditor.DisplayNamePlaceholder',
|
||||||
)}
|
)}
|
||||||
className="w-full px-3 py-2 border border-slate-300 dark:border-gray-700 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100"
|
className={`${INPUT_BASE_CLASS} ${INPUT_IDLE_BORDER}`}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Açıklama */}
|
{/* Açıklama */}
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-slate-700 dark:text-gray-300 mb-1">
|
<label className={LABEL_CLASS} htmlFor="dse-description">
|
||||||
{translate('::App.Listform.ListformField.Description')}
|
{translate('::App.Listform.ListformField.Description')}
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
|
id="dse-description"
|
||||||
type="text"
|
type="text"
|
||||||
value={description}
|
value={description}
|
||||||
onChange={(e) => setDescription(e.target.value)}
|
onChange={(e) => setDescription(e.target.value)}
|
||||||
placeholder={translate(
|
placeholder={translate(
|
||||||
'::App.DynamicServicesEditor.DescriptionPlaceholder',
|
'::App.DynamicServicesEditor.DescriptionPlaceholder',
|
||||||
)}
|
)}
|
||||||
className="w-full px-3 py-2 border border-slate-300 dark:border-gray-700 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100"
|
className={`${INPUT_BASE_CLASS} ${INPUT_IDLE_BORDER}`}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Ana Entity Türü */}
|
{/* Ana Entity Türü */}
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-slate-700 dark:text-gray-300 mb-1">
|
<label className={LABEL_CLASS} htmlFor="dse-primary-entity">
|
||||||
{translate('::App.DynamicServicesEditor.PrimaryEntityType')}
|
{translate('::App.DynamicServicesEditor.PrimaryEntityType')}
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
|
id="dse-primary-entity"
|
||||||
type="text"
|
type="text"
|
||||||
value={primaryEntityType}
|
value={primaryEntityType}
|
||||||
onChange={(e) => setPrimaryEntityType(e.target.value)}
|
onChange={(e) => setPrimaryEntityType(e.target.value)}
|
||||||
placeholder={translate(
|
placeholder={translate(
|
||||||
'::App.DynamicServicesEditor.PrimaryEntityTypePlaceholder',
|
'::App.DynamicServicesEditor.PrimaryEntityTypePlaceholder',
|
||||||
)}
|
)}
|
||||||
className="w-full px-3 py-2 border border-slate-300 dark:border-gray-700 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100"
|
className={`${INPUT_BASE_CLASS} ${INPUT_IDLE_BORDER}`}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Aktif */}
|
{/* Aktif */}
|
||||||
<div>
|
<div className="flex items-center gap-2">
|
||||||
<label className="block text-sm font-medium mb-1">
|
|
||||||
{translate('::App.Listform.ListformField.IsActive')}
|
|
||||||
</label>
|
|
||||||
<input
|
<input
|
||||||
|
id="dse-is-active"
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={isActive}
|
checked={isActive}
|
||||||
onChange={(e) => setIsActive(e.target.checked)}
|
onChange={(e) => setIsActive(e.target.checked)}
|
||||||
className="w-4 h-4 rounded accent-blue-600 cursor-pointer"
|
className="w-4 h-4 rounded accent-blue-600 cursor-pointer"
|
||||||
/>
|
/>
|
||||||
|
<label
|
||||||
|
className="text-sm font-medium text-slate-700 dark:text-gray-300 cursor-pointer"
|
||||||
|
htmlFor="dse-is-active"
|
||||||
|
>
|
||||||
|
{translate('::App.Listform.ListformField.IsActive')}
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* RIGHT PANEL — Önizleme + Editor */}
|
{/* RIGHT PANEL — Editor */}
|
||||||
<div className="w-full flex-1 min-w-0 space-y-4">
|
<div className="w-full flex-1 min-w-0">
|
||||||
{/* Monaco Editor */}
|
<div className="bg-white dark:bg-gray-900 rounded-lg border border-slate-200 dark:border-gray-700 overflow-hidden">
|
||||||
<div className="bg-white rounded-lg border border-slate-200 overflow-hidden">
|
|
||||||
<div className="px-5 py-3 bg-slate-50 dark:bg-gray-800 border-b border-slate-200 dark:border-gray-700 flex items-center justify-between">
|
<div className="px-5 py-3 bg-slate-50 dark:bg-gray-800 border-b border-slate-200 dark:border-gray-700 flex items-center justify-between">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<FaCode className="w-4 h-4 text-slate-500 dark:text-gray-400" />
|
<FaCode className="w-4 h-4 text-slate-500 dark:text-gray-400" />
|
||||||
|
|
@ -442,22 +537,24 @@ const DynamicServiceEditor: React.FC = () => {
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2 text-xs text-slate-500 dark:text-gray-400">
|
<div className="flex items-center gap-2 text-xs text-slate-500 dark:text-gray-400">
|
||||||
<span>
|
<span>
|
||||||
{translate('::App.DynamicServicesEditor.LineCount')}{' '}
|
{translate('::App.DynamicServicesEditor.LineCount')} {lineCount}
|
||||||
{code.split('\n').length}
|
|
||||||
</span>
|
</span>
|
||||||
<span className="text-slate-300">|</span>
|
<span className="text-slate-300 dark:text-gray-600">|</span>
|
||||||
<span>
|
<span>
|
||||||
{translate('::App.DynamicServicesEditor.CharCount')} {code.length}
|
{translate('::App.DynamicServicesEditor.CharCount')} {charCount}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ height: '560px' }}>
|
<div className="h-[560px]">
|
||||||
<Editor
|
<Editor
|
||||||
defaultLanguage="csharp"
|
defaultLanguage="csharp"
|
||||||
value={code}
|
value={code}
|
||||||
onChange={(value) => setCode(value || '')}
|
onChange={(value) => setCode(value ?? '')}
|
||||||
options={editorOptions}
|
options={EDITOR_OPTIONS}
|
||||||
theme="vs-dark"
|
theme={mode === 'dark' ? 'vs-dark' : 'light'}
|
||||||
|
loading={
|
||||||
|
<FaSpinner className="w-6 h-6 animate-spin text-slate-400" />
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue