Dil Desteği

This commit is contained in:
Sedat Öztürk 2025-08-17 15:51:31 +03:00
parent f946ecfdef
commit dd7458093c
11 changed files with 1282 additions and 237 deletions

File diff suppressed because it is too large Load diff

View file

@ -1,10 +1,6 @@
import { useLocalization } from '@/utils/hooks/useLocalization'
import React, { useCallback, useState } from 'react'
import {
FaUpload,
FaFile,
FaTimes,
FaRegCircle
} from 'react-icons/fa';
import { FaUpload, FaFile, FaTimes, FaRegCircle } from 'react-icons/fa'
interface FileUploadAreaProps {
onFileUpload: (file: File) => void
@ -22,6 +18,7 @@ export const FileUploadArea: React.FC<FileUploadAreaProps> = ({
const [dragActive, setDragActive] = useState(false)
const [selectedFile, setSelectedFile] = useState<File | null>(null)
const [error, setError] = useState<string | null>(null)
const { translate } = useLocalization()
const validateFile = (file: File): boolean => {
setError(null)
@ -35,7 +32,9 @@ export const FileUploadArea: React.FC<FileUploadAreaProps> = ({
// Check file type
const fileExtension = '.' + file.name.split('.').pop()?.toLowerCase()
if (!acceptedFormats.includes(fileExtension)) {
setError(`File type not supported. Accepted formats: ${acceptedFormats.join(', ')}`)
setError(
translate('::App.Listforms.ImportManager.FileTypeError') + `${acceptedFormats.join(', ')}`,
)
return false
}
@ -121,11 +120,16 @@ export const FileUploadArea: React.FC<FileUploadAreaProps> = ({
className={`mx-auto h-3 w-3 ${dragActive ? 'text-blue-500' : 'text-slate-400'}`}
/>
<div className="text-lg font-medium text-slate-700 mb-2">
{dragActive ? 'Drop the file here' : 'Upload your file'}
{dragActive
? translate('::App.Listforms.ImportManager.DropHere')
: translate('::App.Listforms.ImportManager.UploadYourFile')}
</div>
<p className="text-slate-500 mb-4">Drag & drop or click to select</p>
<p className="text-slate-500 mb-4">
{translate('::App.Listforms.ImportManager.DragOrClick')}
</p>
<div className="text-sm text-slate-400">
Supported formats: {acceptedFormats.join(', ')} Max size: {maxSize}MB
{translate('::App.Listforms.ImportManager.SupportedFormats')}{' '}
{acceptedFormats.join(', ')} Max size: {maxSize}MB
</div>
</div>
</div>
@ -165,12 +169,12 @@ export const FileUploadArea: React.FC<FileUploadAreaProps> = ({
{loading ? (
<>
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-white"></div>
<span>Uploading...</span>
<span>{translate('::App.Listforms.ImportManager.Uploading')}</span>
</>
) : (
<>
<FaUpload className="w-4 h-4" />
<span>Upload File</span>
<span>{translate('::App.Listforms.ImportManager.UploadFile')}</span>
</>
)}
</button>

View file

@ -9,15 +9,15 @@ import {
FaTrashAlt,
FaDownload,
FaFileExcel,
FaFileAlt
} from 'react-icons/fa';
FaFileAlt,
} from 'react-icons/fa'
import { FileUploadArea } from './FileUploadArea'
import { ImportPreview } from './ImportPreview'
import { ImportProgress } from './ImportProgress'
import { ListFormImportDto, ListFormImportExecuteDto } from '@/proxy/imports/models'
import { ImportService } from '@/services/import.service'
import { GridDto } from '@/proxy/form/models'
import { ColumnFormatEditDto } from '@/proxy/admin/list-form-field/models';
import { useLocalization } from '@/utils/hooks/useLocalization'
interface ImportDashboardProps {
gridDto: GridDto
@ -26,6 +26,8 @@ interface ImportDashboardProps {
export type TabNames = 'import' | 'preview' | 'history'
export const ImportDashboard: React.FC<ImportDashboardProps> = ({ gridDto }) => {
const { translate } = useLocalization()
const [activeTab, setActiveTab] = useState<TabNames>('import')
const [currentSession, setCurrentSession] = useState<ListFormImportDto | null>(null)
const [importHistory, setImportHistory] = useState<ListFormImportDto[]>([])
@ -208,11 +210,7 @@ export const ImportDashboard: React.FC<ImportDashboardProps> = ({ gridDto }) =>
}
const getEditableColumns = () => {
return gridDto.columnFormats.filter(
(col: any) =>
col.permissionDto.i &&
col.fieldName !== 'Id',
)
return gridDto.columnFormats.filter((col: any) => col.permissionDto.i && col.fieldName !== 'Id')
}
const editableColumns = getEditableColumns()
@ -250,7 +248,8 @@ export const ImportDashboard: React.FC<ImportDashboardProps> = ({ gridDto }) =>
<div className="px-3 py-3 border-b flex items-center justify-between">
<h3 className="text-xl font-semibold text-slate-800 flex items-center">
<FaDownload className="w-4 h-4 mr-2" />
Template Columns ({editableColumns.length})
{translate('::App.Listforms.ImportManager.TemplateColumns')} (
{editableColumns.length})
</h3>
{/* Template Options */}
@ -261,7 +260,9 @@ export const ImportDashboard: React.FC<ImportDashboardProps> = ({ gridDto }) =>
className="flex items-center gap-1.5 px-3 py-1.5 border border-green-200 rounded-md hover:border-green-300 hover:bg-green-50 transition-all duration-200 group disabled:opacity-50 disabled:cursor-not-allowed bg-white text-xs"
>
<FaFileExcel className="w-3.5 h-3.5 text-green-500 group-hover:scale-110 transition-transform" />
<span className="font-medium text-slate-700">Excel Template</span>
<span className="font-medium text-slate-700">
{translate('::App.Listforms.ImportManager.ExcelTemplate')}
</span>
</button>
<button
@ -270,7 +271,9 @@ export const ImportDashboard: React.FC<ImportDashboardProps> = ({ gridDto }) =>
className="flex items-center gap-1.5 px-3 py-1.5 border border-blue-200 rounded-md hover:border-blue-300 hover:bg-blue-50 transition-all duration-200 group disabled:opacity-50 disabled:cursor-not-allowed bg-white text-xs"
>
<FaFileAlt className="w-3.5 h-3.5 text-blue-500 group-hover:scale-110 transition-transform" />
<span className="font-medium text-slate-700">CSV Template</span>
<span className="font-medium text-slate-700">
{translate('::App.Listforms.ImportManager.CsvTemplate')}
</span>
</button>
</div>
</div>
@ -280,16 +283,16 @@ export const ImportDashboard: React.FC<ImportDashboardProps> = ({ gridDto }) =>
<thead className="bg-slate-100 sticky top-0">
<tr>
<th className="px-4 py-2 text-left text-xs font-medium text-slate-500 uppercase">
Column
{translate('::App.Listforms.ImportManager.Column')}
</th>
<th className="px-4 py-2 text-left text-xs font-medium text-slate-500 uppercase">
Type
{translate('::App.Listforms.ImportManager.Type')}
</th>
<th className="px-4 py-2 text-left text-xs font-medium text-slate-500 uppercase">
Required
{translate('::App.Listforms.ImportManager.Required')}
</th>
<th className="px-4 py-2 text-left text-xs font-medium text-slate-500 uppercase">
Default
{translate('::App.Listforms.ImportManager.Default')}
</th>
</tr>
</thead>
@ -318,9 +321,13 @@ export const ImportDashboard: React.FC<ImportDashboardProps> = ({ gridDto }) =>
{column.validationRuleDto.some(
(rule: any) => rule.type === 'required',
) ? (
<span className="text-red-500 font-medium">Yes</span>
<span className="text-red-500 font-medium">
{translate('::App.Listforms.ImportManager.Yes')}
</span>
) : (
<span className="text-slate-400">No</span>
<span className="text-slate-400">
{translate('::App.Listforms.ImportManager.No')}
</span>
)}
</td>
<td className="px-4 py-2 text-slate-600 text-sm">
@ -337,7 +344,9 @@ export const ImportDashboard: React.FC<ImportDashboardProps> = ({ gridDto }) =>
{generating && (
<div className="flex items-center justify-center py-4">
<div className="animate-spin rounded-full h-6 w-6 border-b-2 border-blue-500"></div>
<span className="ml-2 text-slate-600">Generating template...</span>
<span className="ml-2 text-slate-600">
{translate('::App.Listforms.ImportManager.GeneratingTemplate')}
</span>
</div>
)}
</div>
@ -348,7 +357,7 @@ export const ImportDashboard: React.FC<ImportDashboardProps> = ({ gridDto }) =>
<div className="bg-white rounded-xl shadow-sm border border-slate-200 p-4 h-full">
<h2 className="text-xl font-semibold text-slate-800 mb-4 flex items-center">
<FaUpload className="w-5 h-5 mr-2 text-green-500" />
Upload Data
{translate('::App.Listforms.ImportManager.UploadData')}
</h2>
<FileUploadArea
onFileUpload={handleFileUpload}
@ -386,8 +395,10 @@ export const ImportDashboard: React.FC<ImportDashboardProps> = ({ gridDto }) =>
<div className="bg-white rounded-xl shadow-sm border border-slate-200 p-12">
<div className="text-center text-slate-500">
<FaEye className="w-16 h-16 mx-auto mb-4 text-slate-300" />
<div className="text-xl font-medium mb-2">No Data to Preview</div>
<div>Upload a file to see the preview here</div>
<div className="text-xl font-medium mb-2">
{translate('::App.Listforms.ImportManager.NoDataToPreview')}
</div>
<div>{translate('::App.Listforms.ImportManager.UploadFileToPreview')}</div>
</div>
</div>
)}
@ -399,7 +410,7 @@ export const ImportDashboard: React.FC<ImportDashboardProps> = ({ gridDto }) =>
<div className="p-3 border-b border-slate-200">
<h2 className="text-xl font-semibold text-slate-800 flex items-center">
<FaClock className="w-5 h-5 mr-2 text-indigo-500" />
Import History
{translate('::App.Listforms.ImportManager.ImportHistory')}
</h2>
</div>
@ -425,7 +436,7 @@ export const ImportDashboard: React.FC<ImportDashboardProps> = ({ gridDto }) =>
</div>
{currentSession?.id === session.id && (
<span className="inline-flex items-center px-2 py-1 rounded-full text-xs font-medium bg-blue-100 text-blue-800">
Aktif
{translate('::App.Listforms.ImportManager.Active')}
</span>
)}
</div>
@ -434,7 +445,7 @@ export const ImportDashboard: React.FC<ImportDashboardProps> = ({ gridDto }) =>
<div className="flex items-center space-x-4">
<div className="text-right">
<div className="text-sm font-medium text-slate-800">
{session.totalRows} total rows
{session.totalRows} {translate('::App.Listforms.ImportManager.TotalRows')}
</div>
</div>
@ -454,7 +465,7 @@ export const ImportDashboard: React.FC<ImportDashboardProps> = ({ gridDto }) =>
? 'text-red-500 bg-red-50 hover:text-red-600 hover:bg-red-100'
: 'text-slate-400 hover:text-slate-600 hover:bg-slate-100'
}`}
title="View execution details"
title={translate('::App.Listforms.ImportManager.ViewExecutionDetails')}
>
<FaEye className="w-4 h-4" />
</button>
@ -483,7 +494,7 @@ export const ImportDashboard: React.FC<ImportDashboardProps> = ({ gridDto }) =>
}
}}
className="p-2 rounded-lg transition-colors text-slate-400 hover:text-blue-500 hover:bg-blue-50"
title="Refresh execution details"
title={translate('::App.Listforms.ImportManager.RefreshExecutionDetails')}
>
<FaSync className="w-4 h-4" />
</button>
@ -503,8 +514,8 @@ export const ImportDashboard: React.FC<ImportDashboardProps> = ({ gridDto }) =>
}`}
title={
currentSession?.id === session.id
? 'Cannot delete the current active session'
: 'Delete this import session'
? translate('::App.Listforms.ImportManager.CannotDeleteActiveSession')
: translate('::App.Listforms.ImportManager.DeleteImportSession')
}
>
<FaTrashAlt className="w-4 h-4" />
@ -520,7 +531,9 @@ export const ImportDashboard: React.FC<ImportDashboardProps> = ({ gridDto }) =>
{loadingExecutes.has(session.id) ? (
<div className="flex items-center space-x-2 text-slate-500 py-2">
<FaSync className="w-4 h-4 animate-spin" />
<span className="text-sm">Loading execution details...</span>
<span className="text-sm">
{translate('::App.Listforms.ImportManager.LoadingExecutionDetails')}
</span>
</div>
) : sessionExecutes[session.id] && sessionExecutes[session.id].length > 0 ? (
<div className="space-y-2">
@ -540,19 +553,25 @@ export const ImportDashboard: React.FC<ImportDashboardProps> = ({ gridDto }) =>
<div className="font-medium text-slate-800">
{execute.execRows}
</div>
<div className="text-slate-500">executed</div>
<div className="text-slate-500">
{translate('::App.Listforms.ImportManager.Executed')}
</div>
</div>
<div className="text-center">
<div className="font-medium text-green-600">
{execute.validRows}
</div>
<div className="text-slate-500">valid</div>
<div className="text-slate-500">
{translate('::App.Listforms.ImportManager.Valid')}
</div>
</div>
<div className="text-center">
<div className="font-medium text-red-600">
{execute.errorRows}
</div>
<div className="text-slate-500">errors</div>
<div className="text-slate-500">
{translate('::App.Listforms.ImportManager.Errors')}
</div>
</div>
</div>
@ -592,7 +611,7 @@ export const ImportDashboard: React.FC<ImportDashboardProps> = ({ gridDto }) =>
</div>
) : (
<div className="text-sm text-slate-500 py-2">
No execution records found
{translate('::App.Listforms.ImportManager.NoExecutionRecords')}
</div>
)}
</div>
@ -604,8 +623,10 @@ export const ImportDashboard: React.FC<ImportDashboardProps> = ({ gridDto }) =>
{importHistory.length === 0 && (
<div className="p-12 text-center text-slate-500">
<FaClock className="w-12 h-12 mx-auto mb-4 text-slate-300" />
<div className="text-lg font-medium mb-2">No import history</div>
<div>Your import history will appear here</div>
<div className="text-lg font-medium mb-2">
{translate('::App.Listforms.ImportManager.NoImportHistory')}
</div>
<div>{translate('::App.Listforms.ImportManager.ImportHistoryHint')}</div>
</div>
)}
</div>

View file

@ -1,14 +1,9 @@
import React, { useState, useEffect, useRef } from 'react'
import {
FaCheckCircle,
FaExclamationTriangle,
FaEye,
FaPlay,
FaTimes
} from 'react-icons/fa';
import { FaCheckCircle, FaExclamationTriangle, FaEye, FaPlay, FaTimes } from 'react-icons/fa'
import { ListFormImportDto } from '@/proxy/imports/models'
import { GridDto } from '@/proxy/form/models'
import { ImportService } from '@/services/import.service'
import { useLocalization } from '@/utils/hooks/useLocalization'
interface ImportPreviewProps {
session: ListFormImportDto
@ -40,6 +35,7 @@ export const ImportPreview: React.FC<ImportPreviewProps> = ({
successCount: number
} | null>(null)
const hasCalledOnPreviewLoaded = useRef(false)
const { translate } = useLocalization()
// Selection handlers
const handleRowSelect = (rowIndex: number) => {
@ -156,7 +152,9 @@ export const ImportPreview: React.FC<ImportPreviewProps> = ({
{/* Başlık kısmı - Üstte mobile, solda desktop */}
<div className="flex items-center order-1 lg:order-none">
<FaEye className="w-5 h-5 mr-2 text-blue-500" />
<h3 className="text-xl font-semibold text-slate-800">Import Preview</h3>
<h3 className="text-xl font-semibold text-slate-800">
{translate('::App.Listforms.ImportManager.ImportPreviewTitle')}
</h3>
</div>
{/* İstatistik kartları - Mobile'da alt alta, desktop'ta yan yana */}
@ -164,7 +162,9 @@ export const ImportPreview: React.FC<ImportPreviewProps> = ({
<div className="flex flex-col sm:flex-row justify-center gap-2">
<div className="text-center px-3 py-1 bg-blue-50 rounded-full border border-blue-200 font-bold text-blue-600">
{previewData?.rows?.length || session.totalRows || 0}{' '}
<span className="text-xs text-blue-700">Total Rows</span>
<span className="text-xs text-blue-700">
{translate('::App.Listforms.ImportManager.TotalRows')}
</span>
</div>
</div>
</div>
@ -185,7 +185,9 @@ export const ImportPreview: React.FC<ImportPreviewProps> = ({
{/* Preview Data */}
{previewData && previewData.headers && previewData.headers.length > 0 ? (
<div className="p-3 border-b border-slate-200">
<h4 className="font-semibold text-slate-800 mb-4">Data Preview</h4>
<h4 className="font-semibold text-slate-800 mb-4">
{translate('::App.Listforms.ImportManager.DataPreviewTitle')}
</h4>
<div className="overflow-auto border border-slate-200 rounded-lg max-h-90">
<table className="w-full text-sm min-w-full">
@ -243,9 +245,11 @@ export const ImportPreview: React.FC<ImportPreviewProps> = ({
<div className="p-6 border-b border-slate-200">
<div className="text-center py-8">
<FaExclamationTriangle className="w-12 h-12 mx-auto mb-4 text-yellow-500" />
<h4 className="font-semibold text-slate-800 mb-2">No Data Found</h4>
<h4 className="font-semibold text-slate-800 mb-2">
{translate('::App.Listforms.ImportManager.NoDataFoundTitle')}
</h4>
<p className="text-slate-600">
Unable to parse data from the uploaded file. Please check the file format and content.
{translate('::App.Listforms.ImportManager.NoDataFoundDescription')}
</p>
</div>
</div>
@ -253,8 +257,12 @@ export const ImportPreview: React.FC<ImportPreviewProps> = ({
<div className="p-6 border-b border-slate-200">
<div className="text-center py-8">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-500 mx-auto mb-4"></div>
<h4 className="font-semibold text-slate-800 mb-2">Loading Preview...</h4>
<p className="text-slate-600">Analyzing uploaded file content...</p>
<h4 className="font-semibold text-slate-800 mb-2">
{translate('::App.Listforms.ImportManager.LoadingPreviewTitle')}
</h4>
<p className="text-slate-600">
{translate('::App.Listforms.ImportManager.LoadingPreviewDescription')}
</p>
</div>
</div>
)}
@ -267,8 +275,9 @@ export const ImportPreview: React.FC<ImportPreviewProps> = ({
<div className="flex items-center space-x-2 text-green-700">
<FaCheckCircle className="w-5 h-5" />
<span className="font-medium">
Import completed successfully! {lastExecutionResult.successCount} of{' '}
{lastExecutionResult.selectedCount} rows were imported.
{translate('::App.Listforms.ImportManager.ImportSuccessMessage')}{' '}
{lastExecutionResult.successCount} of {lastExecutionResult.selectedCount} rows were
imported.
</span>
</div>
</div>
@ -279,21 +288,27 @@ export const ImportPreview: React.FC<ImportPreviewProps> = ({
{selectedRows.length === 0 && (previewData?.rows?.length || 0) > 0 && (
<div className="flex items-center space-x-2 text-orange-600">
<FaExclamationTriangle className="w-5 h-5" />
<span className="font-medium">Please select rows to import using checkboxes</span>
<span className="font-medium">
{translate('::App.Listforms.ImportManager.SelectRowsWarning')}
</span>
</div>
)}
{selectedRows.length > 0 && (
<div className="flex items-center space-x-2 text-blue-600">
<FaCheckCircle className="w-5 h-5" />
<span className="font-medium">{selectedRows.length} rows selected for import</span>
<span className="font-medium">
{selectedRows.length} {translate('::App.Listforms.ImportManager.RowsSelected')}
</span>
</div>
)}
{(previewData?.rows?.length || 0) === 0 && (
<div className="flex items-center space-x-2 text-red-600">
<FaExclamationTriangle className="w-5 h-5" />
<span className="font-medium">No data available for import</span>
<span className="font-medium">
{translate('::App.Listforms.ImportManager.NoRowsAvailable')}
</span>
</div>
)}
</div>
@ -301,7 +316,7 @@ export const ImportPreview: React.FC<ImportPreviewProps> = ({
<div className="flex space-x-3">
<button className="px-4 py-2 text-slate-600 hover:text-slate-800 hover:bg-slate-100 rounded-lg transition-colors flex items-center space-x-2">
<FaTimes className="w-4 h-4" />
<span>Cancel</span>
<span>{translate('::App.Listforms.ImportManager.Button.Cancel')}</span>
</button>
<button
@ -312,12 +327,12 @@ export const ImportPreview: React.FC<ImportPreviewProps> = ({
{loading ? (
<>
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-white"></div>
<span>Processing...</span>
<span>{translate('::App.Listforms.ImportManager.Button.Processing')}</span>
</>
) : (
<>
<FaPlay className="w-4 h-4" />
<span>Execute Import</span>
<span>{translate('::App.Listforms.ImportManager.Button.ExecuteImport')}</span>
</>
)}
</button>

View file

@ -1,12 +1,15 @@
import React from 'react'
import { FaSync, FaUpload, FaCheckCircle } from 'react-icons/fa';
import { FaSync, FaUpload, FaCheckCircle } from 'react-icons/fa'
import { ListFormImportDto } from '@/proxy/imports/models'
import { useLocalization } from '@/utils/hooks/useLocalization'
interface ImportProgressProps {
session: ListFormImportDto
}
export const ImportProgress: React.FC<ImportProgressProps> = ({ session }) => {
const { translate } = useLocalization()
const getProgressPercentage = () => {
if (session.status === 'uploaded') return 100
if (session.status === 'failed') return 0
@ -27,17 +30,17 @@ export const ImportProgress: React.FC<ImportProgressProps> = ({ session }) => {
const getStatusMessage = () => {
switch (session.status) {
case 'uploading':
return 'Uploading file...'
return translate('::App.Listforms.ImportManager.ImportProgress.Status.Uploading')
case 'validating':
return 'Validating data...'
return translate('::App.Listforms.ImportManager.ImportProgress.Status.Validating')
case 'processing':
return 'Processing import...'
return translate('::App.Listforms.ImportManager.ImportProgress.Status.Processing')
case 'uploaded':
return 'Import completed successfully!'
return translate('::App.Listforms.ImportManager.ImportProgress.Status.Uploaded')
case 'failed':
return 'Import failed. Please check the errors.'
return translate('::App.Listforms.ImportManager.ImportProgress.Status.Failed')
default:
return 'Processing...'
return translate('::App.Listforms.ImportManager.ImportProgress.Status.Default')
}
}
@ -64,13 +67,16 @@ export const ImportProgress: React.FC<ImportProgressProps> = ({ session }) => {
{/* Status Message */}
<div>
<h3 className="text-xl font-semibold text-slate-800 mb-2">{getStatusMessage()}</h3>
<p className="text-slate-600">Processing {session.blobName}</p>
<p className="text-slate-600">
{translate('::App.Listforms.ImportManager.ImportProgress.ProcessingFile')}{' '}
{session.blobName}
</p>
</div>
{/* Progress Bar */}
<div className="w-full max-w-md mx-auto">
<div className="flex justify-between text-sm text-slate-600 mb-2">
<span>Progress</span>
<span>{translate('::App.Listforms.ImportManager.ImportProgress.ProgressLabel')}</span>
<span>{Math.round(getProgressPercentage())}%</span>
</div>
<div className="w-full bg-slate-200 rounded-full h-2">

View file

@ -2,14 +2,8 @@ import React, { useState, useMemo, useEffect } from 'react'
import { TemplateEditor } from '../reports/TemplateEditor'
import { ReportGenerator } from '../reports/ReportGenerator'
import { TemplateCard } from './TemplateCard'
import { Button, Input} from '../ui'
import {
FaPlus,
FaSearch,
FaFilter,
FaFileAlt,
FaChartBar
} from 'react-icons/fa';
import { Button, Input } from '../ui'
import { FaPlus, FaSearch, FaFilter, FaFileAlt, FaChartBar } from 'react-icons/fa'
import { ReportCategoryDto, ReportTemplateDto } from '@/proxy/reports/models'
import { useReports } from '@/utils/hooks/useReports'
import { useLocalization } from '@/utils/hooks/useLocalization'
@ -25,14 +19,16 @@ export const Dashboard: React.FC = () => {
generateReport,
} = useReports()
const { translate } = useLocalization()
const tumuCategory = useMemo(
() => ({
id: 'tumu-category',
name: 'Tümü',
description: 'Tüm kategorilere ait raporlar',
name: translate('::App.Reports.Dashboard.All'),
description: translate('::App.Reports.Dashboard.AllDescription'),
icon: '📋',
}),
[],
[translate],
)
const [showEditor, setShowEditor] = useState(false)
@ -40,10 +36,16 @@ export const Dashboard: React.FC = () => {
const [editingTemplate, setEditingTemplate] = useState<ReportTemplateDto | null>(null)
const [generatingTemplate, setGeneratingTemplate] = useState<ReportTemplateDto | null>(null)
const [searchQuery, setSearchQuery] = useState('')
const [selectedCategory, setSelectedCategory] = useState<ReportCategoryDto | null>(tumuCategory)
const { translate } = useLocalization()
const [selectedCategory, setSelectedCategory] = useState<ReportCategoryDto | null>(null)
// Create category options with "Tümü" at the top
// Set initial selected category to tumuCategory
useEffect(() => {
if (!selectedCategory) {
setSelectedCategory(tumuCategory)
}
}, [tumuCategory, selectedCategory])
// Create category options with "tumu-category" at the top
const categoryOptions = useMemo(() => {
const categoryNames = [tumuCategory, ...categories.map((cat) => cat)]
return categoryNames
@ -52,12 +54,12 @@ export const Dashboard: React.FC = () => {
// Filter templates based on selected category and search query
const filteredTemplates = useMemo(() => {
let filtered = templates
// Filter by category (if not "Tümü")
if (selectedCategory && selectedCategory.name !== 'Tümü') {
filtered = filtered.filter(template => template.categoryName === selectedCategory.name)
// Filter by category (if not "tumu-category")
if (selectedCategory && selectedCategory.id !== 'tumu-category') {
filtered = filtered.filter((template) => template.categoryName === selectedCategory.name)
}
// Filter by search query
if (searchQuery) {
filtered = filtered.filter((template) => {
@ -106,7 +108,7 @@ export const Dashboard: React.FC = () => {
}
const handleDeleteTemplate = async (id: string) => {
if (window.confirm('Bu şablonu silmek istediğinizden emin misiniz?')) {
if (window.confirm(translate('::App.Reports.Dashboard.DeleteTemplateConfirmation'))) {
try {
await deleteTemplate(id)
} catch (error) {
@ -140,7 +142,7 @@ export const Dashboard: React.FC = () => {
<div className="flex items-center justify-center min-h-96">
<div className="text-center">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600 mx-auto mb-4"></div>
<p className="text-gray-600">Yükleniyor...</p>
<p className="text-gray-600">{translate('::App.Reports.Dashboard.Loading')}</p>
</div>
</div>
) : (
@ -163,12 +165,12 @@ export const Dashboard: React.FC = () => {
{category.icon && <span className="text-lg">{category.icon}</span>}
<span className="font-medium">{category.name}</span>
</div>
{category.name !== 'Tümü' && (
{category.id !== 'tumu-category' && (
<span className="text-sm text-gray-500 ml-2">
({templates.filter((t) => t.categoryName === category.name).length})
</span>
)}
{category.name === 'Tümü' && (
{category.id === 'tumu-category' && (
<span className="text-sm text-gray-500 ml-2">({templates.length})</span>
)}
</button>
@ -188,19 +190,19 @@ export const Dashboard: React.FC = () => {
{translate('::' + selectedCategory?.description)}
</div>
</div>
<div className="flex items-center space-x-4">
{/* Search Input */}
<div className="relative">
<FaSearch className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-gray-400" />
<Input
placeholder="Şablon ara..."
placeholder={translate('::App.Reports.Dashboard.SearchTemplate')}
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="pl-10 w-64"
/>
</div>
{/* New Template Button */}
<Button
variant="solid"
@ -208,7 +210,7 @@ export const Dashboard: React.FC = () => {
className="bg-blue-600 hover:bg-blue-700 font-medium px-6 py-2.5 rounded-lg shadow-md hover:shadow-lg transition-all duration-200 flex items-center space-x-2"
>
<FaPlus className="h-5 w-5" />
<span>Yeni Şablon</span>
<span>{translate('::App.Reports.Dashboard.NewTemplate')}</span>
</Button>
</div>
</div>
@ -219,7 +221,9 @@ export const Dashboard: React.FC = () => {
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium text-gray-600">
{selectedCategory?.name === 'Tümü' ? 'Toplam Şablon' : 'Kategori Şablonları'}
{selectedCategory?.id === 'tumu-category'
? translate('::App.Reports.Dashboard.TotalTemplates')
: translate('::App.Reports.Dashboard.CategoryTemplates')}
</p>
<p className="text-2xl font-bold text-gray-900">{filteredTemplates.length}</p>
</div>
@ -232,7 +236,7 @@ export const Dashboard: React.FC = () => {
<div className="bg-white rounded-xl shadow-md p-6 border border-gray-200">
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium text-gray-600">Aktif Kategoriler</p>
<p className="text-sm font-medium text-gray-600">{translate('::App.Reports.Dashboard.ActiveCategories')}</p>
<p className="text-2xl font-bold text-gray-900">{categories.length}</p>
</div>
<div className="bg-emerald-100 p-3 rounded-full">
@ -245,11 +249,11 @@ export const Dashboard: React.FC = () => {
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium text-gray-600">
{selectedCategory?.name === 'Tümü' ? 'Toplam Parametre' : 'Kategori Parametreleri'}
</p>
<p className="text-2xl font-bold text-gray-900">
{totalParameters}
{selectedCategory?.id === 'tumu-category'
? translate('::App.Reports.Dashboard.TotalParameters')
: translate('::App.Reports.Dashboard.CategoryParameters')}
</p>
<p className="text-2xl font-bold text-gray-900">{totalParameters}</p>
</div>
<div className="bg-purple-100 p-3 rounded-full">
<FaChartBar className="h-6 w-6 text-purple-600" />
@ -257,7 +261,7 @@ export const Dashboard: React.FC = () => {
</div>
</div>
</div>
{/* Templates Grid */}
{filteredTemplates.length === 0 ? (
<div className="bg-white rounded-xl shadow-md p-12 border border-gray-200 text-center">
@ -267,8 +271,8 @@ export const Dashboard: React.FC = () => {
</h3>
<p className="text-gray-500 mb-6">
{templates.length === 0
? 'İlk rapor şablonunuzu oluşturarak başlayın.'
: 'Arama kriterlerinize uygun şablon bulunamadı.'}
? translate('::App.Reports.Dashboard.CreateFirstTemplate')
: translate('::App.Reports.Dashboard.NoSearchResults')}
</p>
</div>
) : (

View file

@ -1,7 +1,8 @@
import React, { useState } from 'react'
import { Button, Input, Dialog } from '../ui'
import { FaFileAlt } from 'react-icons/fa';
import { FaFileAlt } from 'react-icons/fa'
import { ReportTemplateDto } from '@/proxy/reports/models'
import { useLocalization } from '@/utils/hooks/useLocalization'
interface ReportGeneratorProps {
isOpen: boolean
@ -18,6 +19,8 @@ export const ReportGenerator: React.FC<ReportGeneratorProps> = ({
}) => {
const [parameterValues, setParameterValues] = useState<Record<string, string>>({})
const [isGenerating, setIsGenerating] = useState(false)
const { translate } = useLocalization()
// const [showPrintPreview, setShowPrintPreview] = useState(false);
React.useEffect(() => {
@ -69,7 +72,7 @@ export const ReportGenerator: React.FC<ReportGeneratorProps> = ({
return (
<>
<Dialog isOpen={isOpen} onClose={onClose} width="60%">
<h5 className="mb-4">{`${template.name} - Rapor Parametreleri`}</h5>
<h5 className="mb-4">{`${template.name} - ${translate('::App.Reports.ReportGenerator.ReportParameters')}`}</h5>
<div className="space-y-6">
<div className="bg-gray-50 p-4 rounded-lg">
<h3 className="font-medium text-gray-900 mb-2">{template.name}</h3>
@ -88,7 +91,9 @@ export const ReportGenerator: React.FC<ReportGeneratorProps> = ({
{template.parameters.length > 0 ? (
<div className="space-y-4">
<h4 className="font-medium text-gray-900">Parametre Değerleri</h4>
<h4 className="font-medium text-gray-900">
{translate('::App.Reports.ReportGenerator.ParameterValues')}
</h4>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{template.parameters.map((param) => (
<Input
@ -102,27 +107,33 @@ export const ReportGenerator: React.FC<ReportGeneratorProps> = ({
))}
</div>
{template.parameters.some((p) => p.required) && (
<p className="text-sm text-gray-500">* Zorunlu alanlar</p>
<p className="text-sm text-gray-500">
{translate('::App.Reports.ReportGenerator.RequiredFieldsNote')}
</p>
)}
</div>
) : (
<div className="py-8 text-gray-500">
<FaFileAlt className="h-12 w-12 text-gray-400 mx-auto mb-4" />
<p>Bu şablon için parametre tanımlanmamış.</p>
<p className="text-sm">Direkt rapor oluşturabilirsiniz.</p>
<p>{translate('::App.Reports.ReportGenerator.NoParameters')}</p>
<p className="text-sm">{translate('::App.Reports.ReportGenerator.DirectGenerate')}</p>
</div>
)}
<div className="flex justify-end space-x-3">
<Button onClick={onClose} disabled={isGenerating}>
İptal
<Button onClick={onClose} disabled={isGenerating}>
{translate('::App.Reports.ReportGenerator.Cancel')}
</Button>
<Button variant="solid" onClick={handleGenerateAndShow} disabled={!isValid || isGenerating}
<Button
variant="solid"
onClick={handleGenerateAndShow}
disabled={!isValid || isGenerating}
className="bg-blue-600 hover:bg-blue-700 font-medium px-2 sm:px-3 py-1.5 rounded text-xs flex items-center gap-1"
>
<FaFileAlt className="h-4 w-4 mr-2" />
{isGenerating ? 'Oluşturuluyor...' : 'Rapor Oluştur'}
{isGenerating
? translate('::App.Reports.ReportGenerator.Generating')
: translate('::App.Reports.ReportGenerator.GenerateReport')}
</Button>
</div>
</div>

View file

@ -9,6 +9,7 @@ import {
headerValues,
sizeValues,
} from '@/proxy/reports/data'
import { useLocalization } from '@/utils/hooks/useLocalization'
interface ReportHtmlEditorProps {
value: string
@ -20,16 +21,17 @@ interface ReportHtmlEditorProps {
export const ReportHtmlEditor: React.FC<ReportHtmlEditorProps> = ({
value,
onChange,
placeholder = 'Rapor şablonunuzu buraya yazın...',
height = '100%',
}) => {
const { translate } = useLocalization()
return (
<div className="flex flex-col space-y-2" style={{ height }}>
<HtmlEditor
value={value}
onValueChanged={(e) => onChange(e.value)}
height={height || '400px'}
placeholder={placeholder}
placeholder={translate('::App.Reports.ReportHtmlViewer.Placeholder')}
>
<MediaResizing enabled={true} />
<ImageUpload fileUploadMode="base64" />

View file

@ -14,6 +14,7 @@ import jsPDF from 'jspdf'
import { ReportGeneratedDto, ReportTemplateDto } from '@/proxy/reports/models'
import { useReports } from '@/utils/hooks/useReports'
import { ROUTES_ENUM } from '@/routes/route.constant'
import { useLocalization } from '@/utils/hooks/useLocalization'
export const ReportViewer: React.FC = () => {
const { id } = useParams<{ id: string }>()
@ -23,6 +24,7 @@ export const ReportViewer: React.FC = () => {
const [template, setTemplate] = useState<ReportTemplateDto | null>(null)
const [isLoading, setIsLoading] = useState(true)
const [error, setError] = useState<string | null>(null)
const { translate } = useLocalization()
const { getReportById, getTemplateById } = useReports()
@ -124,8 +126,10 @@ export const ReportViewer: React.FC = () => {
<div className="min-h-screen bg-gray-50 flex items-center justify-center">
<div className="text-center">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600 mx-auto mb-4"></div>
<h1 className="text-xl font-semibold text-gray-900 mb-2">Rapor yükleniyor...</h1>
<p className="text-gray-600">Lütfen bekleyin</p>
<h1 className="text-xl font-semibold text-gray-900 mb-2">
{translate('::App.Reports.ReportViewer.LoadingTitle')}
</h1>
<p className="text-gray-600">{translate('::App.Reports.ReportViewer.LoadingSubtitle')}</p>
</div>
</div>
)
@ -136,7 +140,9 @@ export const ReportViewer: React.FC = () => {
return (
<div className="min-h-screen bg-gray-50 flex items-center justify-center">
<div className="text-center">
<h1 className="text-2xl font-bold text-gray-900 mb-4">{error || 'Rapor bulunamadı'}</h1>
<h1 className="text-2xl font-bold text-gray-900 mb-4">
{error || translate('::App.Reports.ReportViewer.ErrorNotFound')}
</h1>
<Button onClick={() => navigate(ROUTES_ENUM.protected.dashboard)}>
<FaArrowLeft className="h-4 w-4 mr-2" />
Ana Sayfaya Dön
@ -150,7 +156,9 @@ export const ReportViewer: React.FC = () => {
return (
<div className="min-h-screen bg-gray-50 flex items-center justify-center">
<div className="text-center">
<h1 className="text-2xl font-bold text-gray-900 mb-4">Rapor bulunamadı</h1>
<h1 className="text-2xl font-bold text-gray-900 mb-4">
{translate('::App.Reports.ReportViewer.ErrorNotFound')}
</h1>
<Button onClick={() => navigate(ROUTES_ENUM.protected.dashboard)}>
<FaArrowLeft className="h-4 w-4 mr-2" />
Ana Sayfaya Dön
@ -165,13 +173,15 @@ export const ReportViewer: React.FC = () => {
return (
<div className="min-h-screen bg-gray-50 flex items-center justify-center">
<div className="text-center">
<h1 className="text-2xl font-bold text-gray-900 mb-4">Rapor bulunamadı</h1>
<h1 className="text-2xl font-bold text-gray-900 mb-4">
{translate('::App.Reports.ReportViewer.ErrorNotFound')}
</h1>
<p className="text-gray-600 mb-6">
Aradığınız rapor mevcut değil veya silinmiş olabilir.
{translate('::App.Reports.ReportViewer.ErrorNotFoundDescription')}
</p>
<Button onClick={() => navigate(ROUTES_ENUM.protected.dashboard)}>
<FaArrowLeft className="h-4 w-4 mr-2" />
Ana Sayfaya Dön
{translate('::App.Reports.ReportViewer.BackToDashboard')}
</Button>
</div>
</div>
@ -284,10 +294,10 @@ export const ReportViewer: React.FC = () => {
className="bg-white-600 hover:bg-white-700 font-medium px-2 sm:px-3 py-1.5 rounded text-xs flex items-center gap-1"
>
<FaDownload className="h-4 w-4 mr-2" />
PDF İndir
{translate('::App.Reports.ReportViewer.DownloadPDF')}
</Button>
<Button variant="solid" onClick={handlePrint}>
Yazdır
{translate('::App.Reports.ReportViewer.Print')}
</Button>
</div>
</div>
@ -473,7 +483,8 @@ export const ReportViewer: React.FC = () => {
{/* Sayfa Footer - Sayfa Numarası */}
<div className="page-footer">
Sayfa {index + 1} / {splitContentIntoPages(report.generatedContent).length}
{translate('::App.Reports.ReportViewer.Page')} {index + 1} /{' '}
{splitContentIntoPages(report.generatedContent).length}
</div>
</div>
))}

View file

@ -2,6 +2,7 @@ import React from 'react'
import { Button } from '../ui/Button'
import { FaFileAlt, FaEdit, FaTrash, FaPlay } from 'react-icons/fa';
import { ReportTemplateDto } from '@/proxy/reports/models'
import { useLocalization } from '@/utils/hooks/useLocalization';
interface TemplateCardProps {
template: ReportTemplateDto
@ -16,6 +17,8 @@ export const TemplateCard: React.FC<TemplateCardProps> = ({
onDelete,
onGenerate,
}) => {
const { translate } = useLocalization()
return (
<div className="bg-white rounded-xl shadow-md hover:shadow-lg transition-all duration-200 border border-gray-200 flex flex-col">
<div className="p-4 flex-1 flex flex-col">
@ -70,7 +73,7 @@ export const TemplateCard: React.FC<TemplateCardProps> = ({
className="bg-gray-600 hover:bg-gray-700 font-medium px-3 py-1.5 rounded text-xs flex items-center gap-1 flex-1 justify-center min-w-0"
>
<FaPlay className="h-3 w-3" />
<span className="truncate">Göster</span>
<span className="truncate">{translate('::App.Reports.TemplateCard.Show')}</span>
</Button>
<Button
@ -80,7 +83,7 @@ export const TemplateCard: React.FC<TemplateCardProps> = ({
className="font-medium px-3 py-1.5 rounded text-xs flex items-center gap-1 flex-1 justify-center min-w-0"
>
<FaEdit className="h-3 w-3" />
<span className="truncate">Düzenle</span>
<span className="truncate">{translate('::App.Reports.TemplateCard.Edit')}</span>
</Button>
<Button
@ -90,7 +93,7 @@ export const TemplateCard: React.FC<TemplateCardProps> = ({
className="bg-red-600 hover:bg-red-700 font-medium px-3 py-1.5 rounded text-xs flex items-center justify-center flex-1 min-w-0"
>
<FaTrash className="h-3 w-3" />
<span className="truncate">Sil</span>
<span className="truncate">{translate('::App.Reports.TemplateCard.Delete')}</span>
</Button>
</div>
</div>

View file

@ -138,15 +138,23 @@ export const TemplateEditor: React.FC<TemplateEditorProps> = ({
}
const tabs = [
{ id: 'info', label: translate('::Şablon Bilgileri'), icon: FaFileAlt },
{ id: 'parameters', label: translate('::Parametreler'), icon: FaCog },
{ id: 'content', label: translate('::HTML İçerik'), icon: FaCode },
{ 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 },
]
return (
<>
<Dialog isOpen={isOpen} onClose={onClose} width="100%">
<h5 className="mb-4">{template ? 'Şablon Düzenle' : 'Yeni Şablon Oluştur'}</h5>
<h5 className="mb-4">
{template
? translate('::App.Reports.TemplateEditor.TitleEdit')
: translate('::App.Reports.TemplateEditor.TitleNew')}
</h5>
<div className="flex flex-col h-full">
{/* Tab Navigation */}
<div className="border-b border-gray-200 bg-gray-50 flex-shrink-0">
@ -184,7 +192,7 @@ export const TemplateEditor: React.FC<TemplateEditorProps> = ({
<div className="space-y-6">
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Şablon Adı
{translate('::App.Reports.TemplateEditor.Label.Name')}
</label>
<input
autoFocus
@ -195,14 +203,14 @@ export const TemplateEditor: React.FC<TemplateEditorProps> = ({
name: e.target.value,
}))
}
placeholder="Rapor şablonu adı"
placeholder={translate('::App.Reports.TemplateEditor.Placeholder.Name')}
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"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Kategori
{translate('::App.Reports.TemplateEditor.Label.Category')}
</label>
<select
value={formData.categoryName}
@ -224,7 +232,7 @@ export const TemplateEditor: React.FC<TemplateEditorProps> = ({
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Etiketler
{translate('::App.Reports.TemplateEditor.Label.Tags')}
</label>
<div className="flex space-x-2 mb-3">
<input
@ -232,11 +240,13 @@ export const TemplateEditor: React.FC<TemplateEditorProps> = ({
value={tagInput}
onChange={(e) => setTagInput(e.target.value)}
onKeyPress={(e) => e.key === 'Enter' && addTag()}
placeholder="Etiket ekle..."
placeholder={translate(
'::App.Reports.TemplateEditor.Placeholder.AddTag',
)}
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">
Ekle
{translate('::App.Reports.TemplateEditor.Button.Add')}
</Button>
</div>
<div className="flex flex-wrap gap-2">
@ -262,7 +272,7 @@ export const TemplateEditor: React.FC<TemplateEditorProps> = ({
<div className="space-y-6">
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Şablon ıklaması
{translate('::App.Reports.TemplateEditor.Label.Description')}
</label>
<Input
value={formData.description}
@ -272,7 +282,9 @@ export const TemplateEditor: React.FC<TemplateEditorProps> = ({
description: e.target.value,
}))
}
placeholder="Şablon hakkında detaylııklama yazın..."
placeholder={translate(
'::App.Reports.TemplateEditor.Placeholder.Description',
)}
textArea={true}
rows={12}
className="text-left h-full"
@ -304,10 +316,11 @@ export const TemplateEditor: React.FC<TemplateEditorProps> = ({
{formData.parameters.length === 0 ? (
<div className="text-center py-12">
<FaCog className="h-16 w-16 text-gray-400 mx-auto mb-4" />
<p className="text-gray-500 text-lg mb-2">Henüz parametre algılanmadı</p>
<p className="text-gray-500 text-lg mb-2">
{translate('::App.Reports.TemplateEditor.NoParameters')}
</p>
<p className="text-gray-400 text-sm">
HTML içeriğinde @@PARAMETRE formatında parametreler kullandığınızda burada
görünecek.
{translate('::App.Reports.TemplateEditor.NoParametersDescription')}
</p>
</div>
) : (
@ -346,7 +359,9 @@ export const TemplateEditor: React.FC<TemplateEditorProps> = ({
onChange={(e) =>
updateParameter(param.id, { description: e.target.value })
}
placeholder="Parametre açıklaması"
placeholder={translate(
'::App.Reports.TemplateEditor.Placeholder.ParameterDescription',
)}
className="w-full text-xs text-gray-600 bg-transparent border-none outline-none resize-none"
/>
</div>
@ -358,7 +373,9 @@ export const TemplateEditor: React.FC<TemplateEditorProps> = ({
onChange={(e) =>
updateParameter(param.id, { defaultValue: e.target.value })
}
placeholder="Varsayılan değer"
placeholder={translate(
'::App.Reports.TemplateEditor.Placeholder.DefaultValue',
)}
className="w-full text-xs bg-gray-50 px-1.5 py-0.5 rounded border border-gray-200 outline-none"
/>
</div>
@ -373,7 +390,9 @@ export const TemplateEditor: React.FC<TemplateEditorProps> = ({
}
className="w-3 h-3 text-red-600 rounded border-gray-300 focus:ring-red-500"
/>
<span className="text-xs text-gray-600">Zorunlu</span>
<span className="text-xs text-gray-600">
{translate('::App.Reports.TemplateEditor.Label.Required')}
</span>
</label>
</div>
</div>
@ -388,7 +407,7 @@ export const TemplateEditor: React.FC<TemplateEditorProps> = ({
{/* Tab Footer */}
<div className="flex justify-between">
<Button variant="default" onClick={onClose} disabled={isSaving}>
İptal
{translate('::App.Reports.TemplateEditor.Button.Cancel')}
</Button>
<Button
@ -397,7 +416,11 @@ export const TemplateEditor: React.FC<TemplateEditorProps> = ({
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"
>
<FaSave className="h-5 w-5" />
{isSaving ? 'Kaydediliyor...' : template ? 'Güncelle' : 'Kaydet'}
{isSaving
? translate('::App.Reports.TemplateEditor.Button.Saving')
: template
? translate('::App.Reports.TemplateEditor.Button.Update')
: translate('::App.Reports.TemplateEditor.Button.Save')}
</Button>
</div>
</div>