2026-03-02 07:36:38 +00:00
|
|
|
|
import React, { useState, useEffect } from 'react'
|
|
|
|
|
|
import { useParams, useNavigate, Link } from 'react-router-dom'
|
2026-02-24 20:44:16 +00:00
|
|
|
|
import { Editor } from '@monaco-editor/react'
|
|
|
|
|
|
import {
|
|
|
|
|
|
FaPlay,
|
2026-03-02 07:36:38 +00:00
|
|
|
|
FaCopy,
|
2026-02-24 20:44:16 +00:00
|
|
|
|
FaCheckCircle,
|
|
|
|
|
|
FaExclamationCircle,
|
|
|
|
|
|
FaSpinner,
|
|
|
|
|
|
FaExternalLinkAlt,
|
2026-03-02 07:36:38 +00:00
|
|
|
|
FaArrowLeft,
|
|
|
|
|
|
FaCog,
|
|
|
|
|
|
FaCode,
|
|
|
|
|
|
FaSave,
|
2026-02-24 20:44:16 +00:00
|
|
|
|
} from 'react-icons/fa'
|
|
|
|
|
|
import { useLocalization } from '@/utils/hooks/useLocalization'
|
|
|
|
|
|
import {
|
|
|
|
|
|
dynamicServiceService,
|
|
|
|
|
|
type CompileResult,
|
|
|
|
|
|
type PublishResult,
|
|
|
|
|
|
postTestCompile,
|
2026-03-02 07:36:38 +00:00
|
|
|
|
type TestCompileDto,
|
2026-02-24 20:44:16 +00:00
|
|
|
|
} from '@/services/dynamicService.service'
|
2026-03-01 20:43:25 +00:00
|
|
|
|
import { Helmet } from 'react-helmet'
|
|
|
|
|
|
import { APP_NAME } from '@/constants/app.constant'
|
2026-03-02 07:36:38 +00:00
|
|
|
|
import { ROUTES_ENUM } from '@/routes/route.constant'
|
2026-02-24 20:44:16 +00:00
|
|
|
|
|
2026-03-02 07:36:38 +00:00
|
|
|
|
const defaultTemplate = `using System;
|
2026-02-24 20:44:16 +00:00
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
|
using System.Threading.Tasks;
|
|
|
|
|
|
using Volo.Abp.Application.Services;
|
|
|
|
|
|
using Volo.Abp.Domain.Repositories;
|
|
|
|
|
|
using Microsoft.AspNetCore.Authorization;
|
|
|
|
|
|
|
|
|
|
|
|
namespace DynamicServices
|
|
|
|
|
|
{
|
|
|
|
|
|
[Authorize]
|
|
|
|
|
|
public class DynamicCustomerAppService : ApplicationService
|
|
|
|
|
|
{
|
|
|
|
|
|
public virtual async Task<string> GetHelloWorldAsync()
|
|
|
|
|
|
{
|
|
|
|
|
|
return await Task.FromResult("Hello World from Dynamic AppService!");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public virtual async Task<List<string>> GetSampleDataAsync()
|
|
|
|
|
|
{
|
|
|
|
|
|
return await Task.FromResult(new List<string>
|
|
|
|
|
|
{
|
|
|
|
|
|
"Item 1",
|
|
|
|
|
|
"Item 2",
|
|
|
|
|
|
"Item 3"
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}`
|
|
|
|
|
|
|
2026-03-02 07:36:38 +00:00
|
|
|
|
const DynamicServiceEditor: React.FC = () => {
|
|
|
|
|
|
const { id } = useParams<{ id: string }>()
|
|
|
|
|
|
const navigate = useNavigate()
|
|
|
|
|
|
const { translate } = useLocalization()
|
|
|
|
|
|
|
|
|
|
|
|
const [code, setCode] = useState(defaultTemplate)
|
|
|
|
|
|
const [serviceName, setServiceName] = useState('')
|
|
|
|
|
|
const [displayName, setDisplayName] = useState('')
|
|
|
|
|
|
const [description, setDescription] = useState('')
|
|
|
|
|
|
const [primaryEntityType, setPrimaryEntityType] = useState('')
|
|
|
|
|
|
const [isActive, setIsActive] = useState(true)
|
|
|
|
|
|
const [submitted, setSubmitted] = useState(false)
|
|
|
|
|
|
|
|
|
|
|
|
const [isCompiling, setIsCompiling] = useState(false)
|
|
|
|
|
|
const [isPublishing, setIsPublishing] = useState(false)
|
|
|
|
|
|
const [isLoading, setIsLoading] = useState(false)
|
|
|
|
|
|
|
|
|
|
|
|
const [compileResult, setCompileResult] = useState<CompileResult | null>(null)
|
|
|
|
|
|
const [publishResult, setPublishResult] = useState<PublishResult | null>(null)
|
2026-02-24 20:44:16 +00:00
|
|
|
|
|
|
|
|
|
|
const editorOptions = {
|
|
|
|
|
|
fontSize: 14,
|
|
|
|
|
|
lineNumbers: 'on' as const,
|
|
|
|
|
|
roundedSelection: false,
|
|
|
|
|
|
scrollBeyondLastLine: false,
|
|
|
|
|
|
automaticLayout: true,
|
|
|
|
|
|
minimap: { enabled: false },
|
|
|
|
|
|
folding: true,
|
|
|
|
|
|
wordWrap: 'on' as const,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-02 07:36:38 +00:00
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
if (id) {
|
|
|
|
|
|
loadService(id)
|
|
|
|
|
|
}
|
|
|
|
|
|
}, [id])
|
|
|
|
|
|
|
|
|
|
|
|
const loadService = async (serviceId: string) => {
|
2026-02-24 20:44:16 +00:00
|
|
|
|
try {
|
|
|
|
|
|
setIsLoading(true)
|
2026-03-02 07:36:38 +00:00
|
|
|
|
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)
|
2026-02-24 20:44:16 +00:00
|
|
|
|
} catch (error) {
|
2026-03-02 07:36:38 +00:00
|
|
|
|
console.error('Servis yüklenirken hata:', error)
|
2026-02-24 20:44:16 +00:00
|
|
|
|
} finally {
|
|
|
|
|
|
setIsLoading(false)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const handleTestCompile = async () => {
|
|
|
|
|
|
if (!code.trim()) {
|
|
|
|
|
|
alert('Lütfen kod girin')
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
try {
|
|
|
|
|
|
setIsCompiling(true)
|
|
|
|
|
|
setCompileResult(null)
|
2026-03-02 07:36:38 +00:00
|
|
|
|
const result = await postTestCompile({ code } as TestCompileDto)
|
2026-02-24 20:44:16 +00:00
|
|
|
|
setCompileResult(result.data)
|
|
|
|
|
|
} catch (error: any) {
|
|
|
|
|
|
setCompileResult({
|
|
|
|
|
|
success: false,
|
|
|
|
|
|
errorMessage: error.response?.data?.message || 'Derleme sırasında hata oluştu',
|
|
|
|
|
|
compilationTimeMs: 0,
|
|
|
|
|
|
hasWarnings: false,
|
|
|
|
|
|
errors: [],
|
|
|
|
|
|
})
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
setIsCompiling(false)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const handlePublish = async () => {
|
2026-03-02 07:36:38 +00:00
|
|
|
|
setSubmitted(true)
|
2026-02-24 20:44:16 +00:00
|
|
|
|
if (!code.trim() || !serviceName.trim()) {
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
2026-03-02 07:36:38 +00:00
|
|
|
|
if (isPublishing) return
|
2026-02-24 20:44:16 +00:00
|
|
|
|
try {
|
|
|
|
|
|
setIsPublishing(true)
|
|
|
|
|
|
setPublishResult(null)
|
|
|
|
|
|
|
2026-03-02 07:36:38 +00:00
|
|
|
|
// Edit modunda: önce eskiyi sil, sonra yeniden yayınla
|
|
|
|
|
|
if (id) {
|
|
|
|
|
|
await dynamicServiceService.delete(id)
|
2026-02-24 20:44:16 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-02 07:36:38 +00:00
|
|
|
|
const result = await dynamicServiceService.publish({
|
|
|
|
|
|
name: serviceName,
|
|
|
|
|
|
code,
|
|
|
|
|
|
displayName,
|
|
|
|
|
|
description,
|
|
|
|
|
|
primaryEntityType,
|
|
|
|
|
|
isActive,
|
|
|
|
|
|
})
|
2026-02-24 20:44:16 +00:00
|
|
|
|
|
|
|
|
|
|
if (result.success) {
|
2026-03-02 07:36:38 +00:00
|
|
|
|
navigate(ROUTES_ENUM.protected.saas.developerKit.dynamicServices)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
setPublishResult(result)
|
2026-02-24 20:44:16 +00:00
|
|
|
|
}
|
|
|
|
|
|
} catch (error: any) {
|
|
|
|
|
|
setPublishResult({
|
|
|
|
|
|
success: false,
|
|
|
|
|
|
errorMessage: error.response?.data?.message || 'Yayınlama sırasında hata oluştu',
|
|
|
|
|
|
})
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
setIsPublishing(false)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-02 07:36:38 +00:00
|
|
|
|
const copyCode = () => {
|
|
|
|
|
|
navigator.clipboard.writeText(code)
|
|
|
|
|
|
alert('Kod panoya kopyalandı')
|
2026-02-24 20:44:16 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-02 07:36:38 +00:00
|
|
|
|
const pageTitle = id ? `Servis Düzenle` : `Yeni Servis`
|
2026-02-24 20:44:16 +00:00
|
|
|
|
|
2026-03-02 07:36:38 +00:00
|
|
|
|
if (isLoading) {
|
|
|
|
|
|
return (
|
|
|
|
|
|
<div className="flex items-center justify-center h-64">
|
|
|
|
|
|
<FaSpinner className="w-8 h-8 animate-spin text-slate-400" />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)
|
2026-02-24 20:44:16 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-02 07:36:38 +00:00
|
|
|
|
const serviceNameError = submitted && !serviceName.trim()
|
2026-02-24 20:44:16 +00:00
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
<div className="space-y-4">
|
2026-03-02 07:36:38 +00:00
|
|
|
|
<Helmet titleTemplate={`%s | ${APP_NAME}`} title={pageTitle} defaultTitle={APP_NAME} />
|
2026-03-01 20:43:25 +00:00
|
|
|
|
|
2026-02-24 20:44:16 +00:00
|
|
|
|
{/* Header */}
|
2026-03-02 07:36:38 +00:00
|
|
|
|
<div className="bg-white shadow-lg border-b border-slate-200 sticky top-0 z-10">
|
|
|
|
|
|
<div className="flex items-center justify-between px-4 py-3">
|
|
|
|
|
|
{/* Left: back + icon + title */}
|
|
|
|
|
|
<div className="flex items-center gap-3">
|
|
|
|
|
|
<Link
|
|
|
|
|
|
to={ROUTES_ENUM.protected.saas.developerKit.dynamicServices}
|
|
|
|
|
|
className="flex items-center gap-2 text-slate-600 text-black px-4 py-2 rounded-lg hover:text-slate-700 transition-colors"
|
|
|
|
|
|
>
|
|
|
|
|
|
<FaArrowLeft className="w-3.5 h-3.5" />
|
|
|
|
|
|
Servislere Dön
|
|
|
|
|
|
</Link>
|
|
|
|
|
|
<div className="h-6 w-px bg-slate-300"></div>
|
|
|
|
|
|
<div className="flex items-center justify-center w-9 h-9 rounded-lg bg-gradient-to-r from-blue-500 to-purple-600 text-white shrink-0">
|
|
|
|
|
|
<FaCode className="w-4 h-4" />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<h1 className="font-semibold text-slate-800 text-sm leading-tight">{pageTitle}</h1>
|
|
|
|
|
|
<p className="text-xs text-slate-500 leading-tight">
|
|
|
|
|
|
{id ? 'Mevcut servisi düzenleyin' : 'Yeni bir dynamic servis oluşturun'}
|
|
|
|
|
|
</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{/* Right: action buttons + swagger + publish */}
|
|
|
|
|
|
<div className="flex items-center gap-2">
|
|
|
|
|
|
<button
|
|
|
|
|
|
onClick={copyCode}
|
|
|
|
|
|
className="flex items-center gap-2 px-4 py-2 border border-slate-300 rounded-lg text-slate-600 hover:bg-slate-50 transition-colors"
|
|
|
|
|
|
>
|
|
|
|
|
|
<FaCopy className="w-3.5 h-3.5" />
|
|
|
|
|
|
Kodu Kopyala
|
|
|
|
|
|
</button>
|
|
|
|
|
|
|
|
|
|
|
|
<button
|
|
|
|
|
|
onClick={handleTestCompile}
|
|
|
|
|
|
disabled={isCompiling || !code.trim()}
|
|
|
|
|
|
className="flex items-center gap-2 bg-orange-500 text-white px-4 py-2 rounded-lg hover:bg-orange-600 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
|
|
|
|
|
|
>
|
|
|
|
|
|
{isCompiling ? (
|
|
|
|
|
|
<FaSpinner className="w-3.5 h-3.5 animate-spin" />
|
|
|
|
|
|
) : (
|
|
|
|
|
|
<FaPlay className="w-3.5 h-3.5" />
|
|
|
|
|
|
)}
|
|
|
|
|
|
{isCompiling ? 'Derleniyor...' : 'Test Compile'}
|
|
|
|
|
|
</button>
|
|
|
|
|
|
|
|
|
|
|
|
<button
|
|
|
|
|
|
onClick={handlePublish}
|
|
|
|
|
|
disabled={isPublishing}
|
|
|
|
|
|
className="flex items-center gap-2 bg-emerald-600 text-white px-4 py-2 rounded-lg hover:bg-emerald-700 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
|
|
|
|
|
|
>
|
|
|
|
|
|
{isPublishing ? (
|
|
|
|
|
|
<FaSpinner className="w-3.5 h-3.5 animate-spin" />
|
|
|
|
|
|
) : (
|
|
|
|
|
|
<FaSave className="w-3.5 h-3.5" />
|
|
|
|
|
|
)}
|
|
|
|
|
|
{isPublishing ? 'Yayınlanıyor...' : 'Yayınla'}
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
2026-02-24 20:44:16 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
2026-03-02 07:36:38 +00:00
|
|
|
|
{/* Compile / Publish result banners */}
|
|
|
|
|
|
{compileResult && (
|
|
|
|
|
|
<div
|
|
|
|
|
|
className={`flex items-start gap-3 rounded-lg border px-4 py-3 text-sm ${
|
|
|
|
|
|
compileResult.success
|
|
|
|
|
|
? 'bg-emerald-50 border-emerald-200 text-emerald-800'
|
|
|
|
|
|
: 'bg-red-50 border-red-200 text-red-800'
|
|
|
|
|
|
}`}
|
|
|
|
|
|
>
|
|
|
|
|
|
{compileResult.success ? (
|
|
|
|
|
|
<FaCheckCircle className="w-4 h-4 mt-0.5 shrink-0 text-emerald-600" />
|
|
|
|
|
|
) : (
|
|
|
|
|
|
<FaExclamationCircle className="w-4 h-4 mt-0.5 shrink-0 text-red-600" />
|
|
|
|
|
|
)}
|
|
|
|
|
|
<div className="flex-1">
|
|
|
|
|
|
<span className="font-medium">
|
|
|
|
|
|
Derleme {compileResult.success ? 'Başarılı' : 'Başarısız'}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
{!compileResult.success && compileResult.errors && compileResult.errors.length > 0 && (
|
|
|
|
|
|
<ul className="mt-1 space-y-0.5">
|
|
|
|
|
|
{compileResult.errors.map((e, i) => (
|
|
|
|
|
|
<li key={i} className="text-xs font-mono">
|
|
|
|
|
|
[{e.code}] Satır {e.line}: {e.message}
|
|
|
|
|
|
</li>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</ul>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<span className="text-xs text-slate-400 shrink-0">
|
|
|
|
|
|
{compileResult.compilationTimeMs}ms
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
|
|
{publishResult && !publishResult.success && (
|
|
|
|
|
|
<div className="flex items-start gap-3 rounded-lg border bg-red-50 border-red-200 text-red-800 px-4 py-3 text-sm">
|
|
|
|
|
|
<FaExclamationCircle className="w-4 h-4 mt-0.5 shrink-0 text-red-600" />
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<span className="font-medium">Yayınlama Başarısız</span>
|
|
|
|
|
|
{publishResult.errorMessage && (
|
|
|
|
|
|
<p className="text-xs mt-0.5">{publishResult.errorMessage}</p>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
|
|
{/* Two-panel layout */}
|
|
|
|
|
|
<div className="flex gap-4 items-start">
|
|
|
|
|
|
{/* LEFT PANEL — Servis Ayarları */}
|
|
|
|
|
|
<div className="w-1/4 shrink-0 bg-white rounded-lg border border-slate-200 p-5 space-y-4">
|
|
|
|
|
|
{/* Panel header */}
|
|
|
|
|
|
<div className="flex items-center gap-2 pb-3 border-b border-slate-100">
|
|
|
|
|
|
<FaCog className="w-4 h-4 text-blue-500" />
|
|
|
|
|
|
<h2 className="font-semibold text-slate-700 text-sm">Servis Ayarları</h2>
|
|
|
|
|
|
</div>
|
2026-02-24 20:44:16 +00:00
|
|
|
|
|
2026-03-02 07:36:38 +00:00
|
|
|
|
{/* Servis Adı */}
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<label className="block text-sm font-medium text-slate-700 mb-1">Servis Adı</label>
|
|
|
|
|
|
<input
|
|
|
|
|
|
type="text"
|
|
|
|
|
|
value={serviceName}
|
|
|
|
|
|
onChange={(e) => {
|
|
|
|
|
|
setServiceName(e.target.value)
|
|
|
|
|
|
setSubmitted(false)
|
|
|
|
|
|
}}
|
|
|
|
|
|
placeholder="ör: DynamicCustomerAppService"
|
|
|
|
|
|
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 ${
|
|
|
|
|
|
serviceNameError ? 'border-red-500 bg-red-50' : 'border-slate-300'
|
|
|
|
|
|
}`}
|
|
|
|
|
|
/>
|
|
|
|
|
|
{serviceNameError && <p className="text-red-500 text-xs mt-1">Servis adı zorunludur</p>}
|
2026-02-24 20:44:16 +00:00
|
|
|
|
</div>
|
2026-03-02 07:36:38 +00:00
|
|
|
|
|
|
|
|
|
|
{/* Görünen Ad */}
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<label className="block text-sm font-medium text-slate-700 mb-1">Görünen Ad</label>
|
|
|
|
|
|
<input
|
|
|
|
|
|
type="text"
|
|
|
|
|
|
value={displayName}
|
|
|
|
|
|
onChange={(e) => setDisplayName(e.target.value)}
|
|
|
|
|
|
placeholder="ör: Müşteri Yönetimi"
|
|
|
|
|
|
className="w-full px-3 py-2 border border-slate-300 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
|
|
|
|
|
/>
|
2026-02-24 20:44:16 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
|
2026-03-02 07:36:38 +00:00
|
|
|
|
{/* Açıklama */}
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<label className="block text-sm font-medium text-slate-700 mb-1">Açıklama</label>
|
|
|
|
|
|
<input
|
|
|
|
|
|
type="text"
|
|
|
|
|
|
value={description}
|
|
|
|
|
|
onChange={(e) => setDescription(e.target.value)}
|
|
|
|
|
|
placeholder="Bu servisin kısa açıklaması"
|
|
|
|
|
|
className="w-full px-3 py-2 border border-slate-300 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
|
|
|
|
|
/>
|
2026-02-24 20:44:16 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
|
2026-03-02 07:36:38 +00:00
|
|
|
|
{/* Ana Entity Türü */}
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<label className="block text-sm font-medium text-slate-700 mb-1">Ana Entity Türü</label>
|
|
|
|
|
|
<input
|
|
|
|
|
|
type="text"
|
|
|
|
|
|
value={primaryEntityType}
|
|
|
|
|
|
onChange={(e) => setPrimaryEntityType(e.target.value)}
|
|
|
|
|
|
placeholder="ör: Customer"
|
|
|
|
|
|
className="w-full px-3 py-2 border border-slate-300 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
2026-02-24 20:44:16 +00:00
|
|
|
|
|
2026-03-02 07:36:38 +00:00
|
|
|
|
{/* Aktif */}
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<label className="block text-sm font-medium text-slate-700 mb-1">Aktif</label>
|
|
|
|
|
|
<input
|
|
|
|
|
|
type="checkbox"
|
|
|
|
|
|
checked={isActive}
|
|
|
|
|
|
onChange={(e) => setIsActive(e.target.checked)}
|
|
|
|
|
|
className="w-4 h-4 rounded accent-blue-600 cursor-pointer"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2026-02-24 20:44:16 +00:00
|
|
|
|
|
2026-03-02 07:36:38 +00:00
|
|
|
|
{/* RIGHT PANEL — Önizleme + Editor */}
|
|
|
|
|
|
<div className="flex-1 min-w-0 space-y-4">
|
2026-02-24 20:44:16 +00:00
|
|
|
|
{/* Monaco Editor */}
|
2026-03-02 07:36:38 +00:00
|
|
|
|
<div className="bg-white rounded-lg border border-slate-200 overflow-hidden">
|
|
|
|
|
|
<div className="px-5 py-3 bg-slate-50 border-b border-slate-200 flex items-center justify-between">
|
|
|
|
|
|
<div className="flex items-center gap-2">
|
|
|
|
|
|
<FaCode className="w-4 h-4 text-slate-500" />
|
|
|
|
|
|
<h3 className="font-medium text-slate-700 text-sm">C# Kod Editörü</h3>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div className="flex items-center gap-2 text-xs text-slate-500">
|
|
|
|
|
|
<span>Satır: {code.split('\n').length}</span>
|
|
|
|
|
|
<span className="text-slate-300">|</span>
|
|
|
|
|
|
<span>Karakter: {code.length}</span>
|
2026-02-24 20:44:16 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2026-03-02 07:36:38 +00:00
|
|
|
|
<div style={{ height: '560px' }}>
|
2026-02-24 20:44:16 +00:00
|
|
|
|
<Editor
|
|
|
|
|
|
defaultLanguage="csharp"
|
|
|
|
|
|
value={code}
|
|
|
|
|
|
onChange={(value) => setCode(value || '')}
|
|
|
|
|
|
options={editorOptions}
|
|
|
|
|
|
theme="vs-dark"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-02 07:36:38 +00:00
|
|
|
|
export default DynamicServiceEditor
|