2025-08-15 08:07:51 +00:00
|
|
|
|
import React, { useState, useEffect } from 'react'
|
2025-08-16 19:47:24 +00:00
|
|
|
|
import { FaSave, FaTimes, FaFileAlt, FaCode, FaCog } from 'react-icons/fa'
|
2025-08-15 08:07:51 +00:00
|
|
|
|
import { ReportHtmlEditor } from './ReportHtmlEditor'
|
2025-08-15 19:39:11 +00:00
|
|
|
|
import { ReportParameterDto, ReportTemplateDto, ReportCategoryDto } from '@/proxy/reports/models'
|
2025-08-15 08:07:51 +00:00
|
|
|
|
import { Button, Input, Dialog } from '../ui'
|
2025-08-15 19:39:11 +00:00
|
|
|
|
import { useLocalization } from '@/utils/hooks/useLocalization'
|
2025-08-15 08:07:51 +00:00
|
|
|
|
|
|
|
|
|
|
interface TemplateEditorProps {
|
|
|
|
|
|
isOpen: boolean
|
|
|
|
|
|
onClose: () => void
|
|
|
|
|
|
onSave: (template: ReportTemplateDto) => Promise<void>
|
|
|
|
|
|
template?: ReportTemplateDto | null
|
2025-08-15 19:39:11 +00:00
|
|
|
|
categories: ReportCategoryDto[]
|
2025-08-15 08:07:51 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export const TemplateEditor: React.FC<TemplateEditorProps> = ({
|
|
|
|
|
|
isOpen,
|
|
|
|
|
|
onClose,
|
|
|
|
|
|
onSave,
|
|
|
|
|
|
template,
|
2025-08-15 19:39:11 +00:00
|
|
|
|
categories,
|
2025-08-15 08:07:51 +00:00
|
|
|
|
}) => {
|
2025-08-15 19:39:11 +00:00
|
|
|
|
const [activeTab, setActiveTab] = useState<'info' | 'parameters' | 'content'>('info')
|
2025-08-15 08:07:51 +00:00
|
|
|
|
const [formData, setFormData] = useState({
|
|
|
|
|
|
name: '',
|
|
|
|
|
|
description: '',
|
|
|
|
|
|
htmlContent: '',
|
2025-10-08 11:31:29 +00:00
|
|
|
|
categoryId: '',
|
2025-08-15 08:07:51 +00:00
|
|
|
|
tags: [] as string[],
|
|
|
|
|
|
parameters: [] as ReportParameterDto[],
|
|
|
|
|
|
})
|
|
|
|
|
|
|
2025-08-15 19:39:11 +00:00
|
|
|
|
const { translate } = useLocalization()
|
2025-08-15 08:07:51 +00:00
|
|
|
|
const [tagInput, setTagInput] = useState('')
|
|
|
|
|
|
const [isSaving, setIsSaving] = useState(false)
|
|
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
if (template) {
|
|
|
|
|
|
setFormData({
|
|
|
|
|
|
name: template.name,
|
2025-08-15 11:52:30 +00:00
|
|
|
|
description: template.description || '',
|
2025-08-15 08:07:51 +00:00
|
|
|
|
htmlContent: template.htmlContent,
|
2025-10-08 11:31:29 +00:00
|
|
|
|
categoryId: template.categoryId!,
|
2025-08-15 08:07:51 +00:00
|
|
|
|
tags: template.tags,
|
|
|
|
|
|
parameters: template.parameters,
|
|
|
|
|
|
})
|
|
|
|
|
|
} else {
|
|
|
|
|
|
setFormData({
|
|
|
|
|
|
name: '',
|
|
|
|
|
|
description: '',
|
|
|
|
|
|
htmlContent: '',
|
2025-10-08 11:31:29 +00:00
|
|
|
|
categoryId: '',
|
2025-08-15 08:07:51 +00:00
|
|
|
|
tags: [],
|
|
|
|
|
|
parameters: [],
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
setActiveTab('info')
|
|
|
|
|
|
}, [template, isOpen])
|
|
|
|
|
|
|
|
|
|
|
|
// Otomatik parametre algılama
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
const extractParameters = (htmlContent: string) => {
|
2025-08-15 19:39:11 +00:00
|
|
|
|
const paramRegex = /@@([\p{L}0-9_]+)/gu
|
2025-08-15 08:07:51 +00:00
|
|
|
|
const matches = [...htmlContent.matchAll(paramRegex)]
|
|
|
|
|
|
const uniqueParams = [...new Set(matches.map((match) => match[1]))]
|
|
|
|
|
|
|
|
|
|
|
|
const newParameters: ReportParameterDto[] = uniqueParams.map((paramName) => {
|
|
|
|
|
|
// Mevcut parametreyi kontrol et
|
|
|
|
|
|
const existingParam = formData.parameters.find((p) => p.name === paramName)
|
|
|
|
|
|
if (existingParam) {
|
|
|
|
|
|
return existingParam
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Yeni parametre oluştur
|
|
|
|
|
|
return {
|
|
|
|
|
|
id: crypto.randomUUID(),
|
2025-10-08 11:31:29 +00:00
|
|
|
|
templateId: template?.id || '',
|
2025-08-15 08:07:51 +00:00
|
|
|
|
name: paramName,
|
|
|
|
|
|
placeholder: `@@${paramName}`,
|
|
|
|
|
|
type: 'text',
|
|
|
|
|
|
required: true,
|
|
|
|
|
|
description: `${paramName} parametresi`,
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
setFormData((prev) => ({
|
|
|
|
|
|
...prev,
|
|
|
|
|
|
parameters: newParameters,
|
|
|
|
|
|
}))
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (formData.htmlContent) {
|
|
|
|
|
|
extractParameters(formData.htmlContent)
|
|
|
|
|
|
}
|
|
|
|
|
|
}, [formData.htmlContent])
|
|
|
|
|
|
|
|
|
|
|
|
const handleSave = async () => {
|
|
|
|
|
|
if (!formData.name.trim() || !formData.htmlContent.trim()) {
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-08 11:31:29 +00:00
|
|
|
|
console.log('FormData before save:', formData)
|
|
|
|
|
|
console.log('Categories available:', categories)
|
|
|
|
|
|
|
2025-08-15 08:07:51 +00:00
|
|
|
|
setIsSaving(true)
|
|
|
|
|
|
try {
|
2025-08-15 11:52:30 +00:00
|
|
|
|
await onSave(formData as unknown as ReportTemplateDto)
|
2025-08-15 08:07:51 +00:00
|
|
|
|
onClose()
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Error saving template:', error)
|
|
|
|
|
|
// Handle error - could show toast notification
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
setIsSaving(false)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const addTag = () => {
|
|
|
|
|
|
if (tagInput.trim() && !formData.tags.includes(tagInput.trim())) {
|
|
|
|
|
|
setFormData((prev) => ({
|
|
|
|
|
|
...prev,
|
|
|
|
|
|
tags: [...prev.tags, tagInput.trim()],
|
|
|
|
|
|
}))
|
|
|
|
|
|
setTagInput('')
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const removeTag = (tagToRemove: string) => {
|
|
|
|
|
|
setFormData((prev) => ({
|
|
|
|
|
|
...prev,
|
|
|
|
|
|
tags: prev.tags.filter((tag) => tag !== tagToRemove),
|
|
|
|
|
|
}))
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-15 19:39:11 +00:00
|
|
|
|
const updateParameter = (paramId: string, updates: Partial<ReportParameterDto>) => {
|
|
|
|
|
|
setFormData((prev) => ({
|
|
|
|
|
|
...prev,
|
|
|
|
|
|
parameters: prev.parameters.map((param) =>
|
2025-08-16 19:47:24 +00:00
|
|
|
|
param.id === paramId ? { ...param, ...updates } : param,
|
2025-08-15 19:39:11 +00:00
|
|
|
|
),
|
|
|
|
|
|
}))
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-15 08:07:51 +00:00
|
|
|
|
const tabs = [
|
2025-08-17 12:51:31 +00:00
|
|
|
|
{ id: 'info', label: translate('::App.Reports.TemplateEditor.Tab.Info'), icon: FaFileAlt },
|
|
|
|
|
|
{
|
|
|
|
|
|
id: 'parameters',
|
|
|
|
|
|
label: translate('::App.Reports.TemplateEditor.Tab.Parameters'),
|
|
|
|
|
|
icon: FaCog,
|
|
|
|
|
|
},
|
|
|
|
|
|
{ id: 'content', label: translate('::App.Reports.TemplateEditor.Tab.Content'), icon: FaCode },
|
2025-08-15 08:07:51 +00:00
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
<>
|
2025-08-15 19:39:11 +00:00
|
|
|
|
<Dialog isOpen={isOpen} onClose={onClose} width="100%">
|
2025-08-17 12:51:31 +00:00
|
|
|
|
<h5 className="mb-4">
|
|
|
|
|
|
{template
|
|
|
|
|
|
? translate('::App.Reports.TemplateEditor.TitleEdit')
|
|
|
|
|
|
: translate('::App.Reports.TemplateEditor.TitleNew')}
|
|
|
|
|
|
</h5>
|
2025-08-15 08:07:51 +00:00
|
|
|
|
<div className="flex flex-col h-full">
|
|
|
|
|
|
{/* Tab Navigation */}
|
|
|
|
|
|
<div className="border-b border-gray-200 bg-gray-50 flex-shrink-0">
|
|
|
|
|
|
<nav className="flex space-x-8 px-6">
|
|
|
|
|
|
{tabs.map((tab) => {
|
|
|
|
|
|
const Icon = tab.icon
|
|
|
|
|
|
return (
|
|
|
|
|
|
<button
|
|
|
|
|
|
key={tab.id}
|
2025-08-15 19:39:11 +00:00
|
|
|
|
onClick={() => setActiveTab(tab.id as 'info' | 'content' | 'parameters')}
|
2025-08-15 08:07:51 +00:00
|
|
|
|
className={`
|
|
|
|
|
|
flex items-center py-4 px-1 border-b-2 font-medium text-sm transition-colors
|
|
|
|
|
|
${
|
|
|
|
|
|
activeTab === tab.id
|
|
|
|
|
|
? 'border-blue-500 text-blue-600'
|
|
|
|
|
|
: 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300'
|
|
|
|
|
|
}
|
|
|
|
|
|
`}
|
|
|
|
|
|
>
|
|
|
|
|
|
<Icon className="h-4 w-4 mr-2" />
|
|
|
|
|
|
{tab.label}
|
|
|
|
|
|
</button>
|
|
|
|
|
|
)
|
|
|
|
|
|
})}
|
|
|
|
|
|
</nav>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{/* Tab Content */}
|
|
|
|
|
|
<div className="flex-1 flex flex-col min-h-0">
|
|
|
|
|
|
{activeTab === 'info' && (
|
2025-08-15 19:39:11 +00:00
|
|
|
|
<div className="overflow-y-auto flex-1 p-3">
|
2025-08-21 13:54:01 +00:00
|
|
|
|
<div className="mx-auto">
|
2025-08-15 19:39:11 +00:00
|
|
|
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8">
|
|
|
|
|
|
{/* Left Column - Basic Info */}
|
|
|
|
|
|
<div className="space-y-6">
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
2025-08-17 12:51:31 +00:00
|
|
|
|
{translate('::App.Reports.TemplateEditor.Label.Name')}
|
2025-08-15 19:39:11 +00:00
|
|
|
|
</label>
|
2025-08-15 08:07:51 +00:00
|
|
|
|
<input
|
2025-08-15 19:39:11 +00:00
|
|
|
|
autoFocus
|
|
|
|
|
|
value={formData.name}
|
|
|
|
|
|
onChange={(e) =>
|
|
|
|
|
|
setFormData((prev) => ({
|
|
|
|
|
|
...prev,
|
|
|
|
|
|
name: e.target.value,
|
|
|
|
|
|
}))
|
|
|
|
|
|
}
|
2025-08-17 12:51:31 +00:00
|
|
|
|
placeholder={translate('::App.Reports.TemplateEditor.Placeholder.Name')}
|
2025-08-15 19:39:11 +00:00
|
|
|
|
className="w-full flex-1 px-3 py-2 border border-gray-300 rounded-lg shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
2025-08-15 08:07:51 +00:00
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
2025-08-15 19:39:11 +00:00
|
|
|
|
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
2025-08-17 12:51:31 +00:00
|
|
|
|
{translate('::App.Reports.TemplateEditor.Label.Category')}
|
2025-08-15 19:39:11 +00:00
|
|
|
|
</label>
|
|
|
|
|
|
<select
|
2025-10-08 11:31:29 +00:00
|
|
|
|
value={formData.categoryId}
|
|
|
|
|
|
onChange={(e) => {
|
|
|
|
|
|
console.log('Category selected:', e.target.value)
|
2025-08-15 19:39:11 +00:00
|
|
|
|
setFormData((prev) => ({
|
|
|
|
|
|
...prev,
|
2025-10-08 11:31:29 +00:00
|
|
|
|
categoryId: e.target.value,
|
2025-08-15 19:39:11 +00:00
|
|
|
|
}))
|
2025-10-08 11:31:29 +00:00
|
|
|
|
}}
|
2025-08-15 19:39:11 +00:00
|
|
|
|
className="block w-full px-3 py-2 border border-gray-300 rounded-lg shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
|
|
|
|
|
>
|
2025-10-08 11:31:29 +00:00
|
|
|
|
<option value="">
|
|
|
|
|
|
{translate('::App.Reports.TemplateEditor.Placeholder.SelectCategory')}
|
|
|
|
|
|
</option>
|
2025-08-15 19:39:11 +00:00
|
|
|
|
{categories.map((category) => (
|
2025-10-08 11:31:29 +00:00
|
|
|
|
<option key={category.id} value={category.id}>
|
2025-08-15 19:39:11 +00:00
|
|
|
|
{category.name}
|
|
|
|
|
|
</option>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</select>
|
2025-08-15 08:07:51 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
|
2025-08-15 19:39:11 +00:00
|
|
|
|
<div>
|
|
|
|
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
2025-08-17 12:51:31 +00:00
|
|
|
|
{translate('::App.Reports.TemplateEditor.Label.Tags')}
|
2025-08-15 19:39:11 +00:00
|
|
|
|
</label>
|
|
|
|
|
|
<div className="flex space-x-2 mb-3">
|
|
|
|
|
|
<input
|
|
|
|
|
|
type="text"
|
|
|
|
|
|
value={tagInput}
|
|
|
|
|
|
onChange={(e) => setTagInput(e.target.value)}
|
|
|
|
|
|
onKeyPress={(e) => e.key === 'Enter' && addTag()}
|
2025-08-17 12:51:31 +00:00
|
|
|
|
placeholder={translate(
|
|
|
|
|
|
'::App.Reports.TemplateEditor.Placeholder.AddTag',
|
|
|
|
|
|
)}
|
2025-08-15 19:39:11 +00:00
|
|
|
|
className="flex-1 px-3 py-2 border border-gray-300 rounded-lg shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
|
|
|
|
|
/>
|
|
|
|
|
|
<Button type="button" onClick={addTag} size="sm">
|
2025-08-17 12:51:31 +00:00
|
|
|
|
{translate('::App.Reports.TemplateEditor.Button.Add')}
|
2025-08-15 19:39:11 +00:00
|
|
|
|
</Button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div className="flex flex-wrap gap-2">
|
|
|
|
|
|
{formData.tags.map((tag) => (
|
|
|
|
|
|
<span
|
|
|
|
|
|
key={tag}
|
|
|
|
|
|
className="inline-flex items-center px-3 py-1 text-sm bg-blue-100 text-blue-800 rounded-full"
|
2025-08-15 08:07:51 +00:00
|
|
|
|
>
|
2025-08-15 19:39:11 +00:00
|
|
|
|
{tag}
|
|
|
|
|
|
<button
|
|
|
|
|
|
onClick={() => removeTag(tag)}
|
|
|
|
|
|
className="ml-2 text-blue-600 hover:text-blue-800"
|
|
|
|
|
|
>
|
2025-08-16 19:47:24 +00:00
|
|
|
|
<FaTimes className="h-3 w-3" />
|
2025-08-15 19:39:11 +00:00
|
|
|
|
</button>
|
|
|
|
|
|
</span>
|
2025-08-15 08:07:51 +00:00
|
|
|
|
))}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2025-08-15 19:39:11 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{/* Right Column - Description */}
|
|
|
|
|
|
<div className="space-y-6">
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
2025-08-17 12:51:31 +00:00
|
|
|
|
{translate('::App.Reports.TemplateEditor.Label.Description')}
|
2025-08-15 19:39:11 +00:00
|
|
|
|
</label>
|
|
|
|
|
|
<Input
|
|
|
|
|
|
value={formData.description}
|
|
|
|
|
|
onChange={(e) =>
|
|
|
|
|
|
setFormData((prev) => ({
|
|
|
|
|
|
...prev,
|
|
|
|
|
|
description: e.target.value,
|
|
|
|
|
|
}))
|
|
|
|
|
|
}
|
2025-08-17 12:51:31 +00:00
|
|
|
|
placeholder={translate(
|
|
|
|
|
|
'::App.Reports.TemplateEditor.Placeholder.Description',
|
|
|
|
|
|
)}
|
2025-08-15 19:39:11 +00:00
|
|
|
|
textArea={true}
|
|
|
|
|
|
rows={12}
|
|
|
|
|
|
className="text-left h-full"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2025-08-15 08:07:51 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
|
|
{activeTab === 'content' && (
|
2025-08-15 19:39:11 +00:00
|
|
|
|
<div className="flex-1 flex flex-col min-h-0">
|
|
|
|
|
|
<div className="flex-1 p-3 min-h-0">
|
|
|
|
|
|
<ReportHtmlEditor
|
|
|
|
|
|
value={formData.htmlContent}
|
|
|
|
|
|
onChange={(content) =>
|
|
|
|
|
|
setFormData((prev) => ({ ...prev, htmlContent: content }))
|
|
|
|
|
|
}
|
|
|
|
|
|
height="50vh"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
|
|
{activeTab === 'parameters' && (
|
|
|
|
|
|
<div className="overflow-y-auto flex-1 p-6">
|
|
|
|
|
|
<div className="max-w-full mx-auto">
|
|
|
|
|
|
{formData.parameters.length === 0 ? (
|
|
|
|
|
|
<div className="text-center py-12">
|
2025-08-16 19:47:24 +00:00
|
|
|
|
<FaCog className="h-16 w-16 text-gray-400 mx-auto mb-4" />
|
2025-08-17 12:51:31 +00:00
|
|
|
|
<p className="text-gray-500 text-lg mb-2">
|
|
|
|
|
|
{translate('::App.Reports.TemplateEditor.NoParameters')}
|
|
|
|
|
|
</p>
|
2025-08-15 19:39:11 +00:00
|
|
|
|
<p className="text-gray-400 text-sm">
|
2025-08-17 12:51:31 +00:00
|
|
|
|
{translate('::App.Reports.TemplateEditor.NoParametersDescription')}
|
2025-08-15 19:39:11 +00:00
|
|
|
|
</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
) : (
|
|
|
|
|
|
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4">
|
|
|
|
|
|
{formData.parameters.map((param) => (
|
|
|
|
|
|
<div
|
|
|
|
|
|
key={param.id}
|
|
|
|
|
|
className="bg-white p-3 rounded-lg border border-gray-200 shadow-sm hover:shadow-md transition-shadow min-w-0"
|
|
|
|
|
|
>
|
|
|
|
|
|
<div className="flex items-start justify-between mb-2 min-w-0">
|
|
|
|
|
|
<div className="flex items-center gap-1 min-w-0 flex-1">
|
|
|
|
|
|
<div className="w-2 h-2 bg-blue-500 rounded-full flex-shrink-0"></div>
|
|
|
|
|
|
<code className="text-xs font-mono text-blue-700 bg-blue-50 px-1.5 py-0.5 rounded truncate">
|
|
|
|
|
|
@@{param.name}
|
|
|
|
|
|
</code>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<select
|
|
|
|
|
|
value={param.type}
|
2025-08-16 19:47:24 +00:00
|
|
|
|
onChange={(e) =>
|
|
|
|
|
|
updateParameter(param.id, { type: e.target.value as any })
|
|
|
|
|
|
}
|
2025-08-15 19:39:11 +00:00
|
|
|
|
className="text-xs bg-gray-100 text-gray-600 px-1.5 py-0.5 rounded-full flex-shrink-0 ml-1 border-none outline-none cursor-pointer"
|
|
|
|
|
|
>
|
|
|
|
|
|
<option value="text">text</option>
|
|
|
|
|
|
<option value="number">number</option>
|
|
|
|
|
|
<option value="date">date</option>
|
|
|
|
|
|
<option value="select">select</option>
|
|
|
|
|
|
<option value="checkbox">checkbox</option>
|
|
|
|
|
|
</select>
|
|
|
|
|
|
</div>
|
2025-08-16 19:47:24 +00:00
|
|
|
|
|
2025-08-15 19:39:11 +00:00
|
|
|
|
<div className="mb-2">
|
|
|
|
|
|
<input
|
|
|
|
|
|
type="text"
|
|
|
|
|
|
value={param.description || ''}
|
2025-08-16 19:47:24 +00:00
|
|
|
|
onChange={(e) =>
|
|
|
|
|
|
updateParameter(param.id, { description: e.target.value })
|
|
|
|
|
|
}
|
2025-08-17 12:51:31 +00:00
|
|
|
|
placeholder={translate(
|
|
|
|
|
|
'::App.Reports.TemplateEditor.Placeholder.ParameterDescription',
|
|
|
|
|
|
)}
|
2025-08-15 19:39:11 +00:00
|
|
|
|
className="w-full text-xs text-gray-600 bg-transparent border-none outline-none resize-none"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div className="mb-2">
|
|
|
|
|
|
<input
|
|
|
|
|
|
type="text"
|
|
|
|
|
|
value={param.defaultValue || ''}
|
2025-08-16 19:47:24 +00:00
|
|
|
|
onChange={(e) =>
|
|
|
|
|
|
updateParameter(param.id, { defaultValue: e.target.value })
|
|
|
|
|
|
}
|
2025-08-17 12:51:31 +00:00
|
|
|
|
placeholder={translate(
|
|
|
|
|
|
'::App.Reports.TemplateEditor.Placeholder.DefaultValue',
|
|
|
|
|
|
)}
|
2025-08-15 19:39:11 +00:00
|
|
|
|
className="w-full text-xs bg-gray-50 px-1.5 py-0.5 rounded border border-gray-200 outline-none"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div className="flex items-center gap-2">
|
|
|
|
|
|
<label className="flex items-center gap-1 cursor-pointer">
|
|
|
|
|
|
<input
|
|
|
|
|
|
type="checkbox"
|
|
|
|
|
|
checked={param.required}
|
2025-08-16 19:47:24 +00:00
|
|
|
|
onChange={(e) =>
|
|
|
|
|
|
updateParameter(param.id, { required: e.target.checked })
|
|
|
|
|
|
}
|
2025-08-15 19:39:11 +00:00
|
|
|
|
className="w-3 h-3 text-red-600 rounded border-gray-300 focus:ring-red-500"
|
|
|
|
|
|
/>
|
2025-08-17 12:51:31 +00:00
|
|
|
|
<span className="text-xs text-gray-600">
|
|
|
|
|
|
{translate('::App.Reports.TemplateEditor.Label.Required')}
|
|
|
|
|
|
</span>
|
2025-08-15 19:39:11 +00:00
|
|
|
|
</label>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
2025-08-15 08:07:51 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{/* Tab Footer */}
|
|
|
|
|
|
<div className="flex justify-between">
|
2025-08-15 19:39:11 +00:00
|
|
|
|
<Button variant="default" onClick={onClose} disabled={isSaving}>
|
2025-08-17 12:51:31 +00:00
|
|
|
|
{translate('::App.Reports.TemplateEditor.Button.Cancel')}
|
2025-08-15 08:07:51 +00:00
|
|
|
|
</Button>
|
2025-08-15 19:39:11 +00:00
|
|
|
|
|
|
|
|
|
|
<Button
|
|
|
|
|
|
variant="solid"
|
|
|
|
|
|
onClick={handleSave}
|
|
|
|
|
|
className="bg-blue-600 hover:bg-blue-700 font-medium px-2 py-2 rounded-lg shadow-md hover:shadow-lg transition-all duration-200 flex items-center space-x-2"
|
|
|
|
|
|
>
|
2025-08-16 19:47:24 +00:00
|
|
|
|
<FaSave className="h-5 w-5" />
|
2025-08-17 12:51:31 +00:00
|
|
|
|
{isSaving
|
|
|
|
|
|
? translate('::App.Reports.TemplateEditor.Button.Saving')
|
|
|
|
|
|
: template
|
|
|
|
|
|
? translate('::App.Reports.TemplateEditor.Button.Update')
|
|
|
|
|
|
: translate('::App.Reports.TemplateEditor.Button.Save')}
|
2025-08-15 08:07:51 +00:00
|
|
|
|
</Button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</Dialog>
|
|
|
|
|
|
</>
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|