Dil Desteği
This commit is contained in:
parent
f946ecfdef
commit
dd7458093c
11 changed files with 1282 additions and 237 deletions
File diff suppressed because it is too large
Load diff
|
|
@ -1,10 +1,6 @@
|
||||||
|
import { useLocalization } from '@/utils/hooks/useLocalization'
|
||||||
import React, { useCallback, useState } from 'react'
|
import React, { useCallback, useState } from 'react'
|
||||||
import {
|
import { FaUpload, FaFile, FaTimes, FaRegCircle } from 'react-icons/fa'
|
||||||
FaUpload,
|
|
||||||
FaFile,
|
|
||||||
FaTimes,
|
|
||||||
FaRegCircle
|
|
||||||
} from 'react-icons/fa';
|
|
||||||
|
|
||||||
interface FileUploadAreaProps {
|
interface FileUploadAreaProps {
|
||||||
onFileUpload: (file: File) => void
|
onFileUpload: (file: File) => void
|
||||||
|
|
@ -22,6 +18,7 @@ export const FileUploadArea: React.FC<FileUploadAreaProps> = ({
|
||||||
const [dragActive, setDragActive] = useState(false)
|
const [dragActive, setDragActive] = useState(false)
|
||||||
const [selectedFile, setSelectedFile] = useState<File | null>(null)
|
const [selectedFile, setSelectedFile] = useState<File | null>(null)
|
||||||
const [error, setError] = useState<string | null>(null)
|
const [error, setError] = useState<string | null>(null)
|
||||||
|
const { translate } = useLocalization()
|
||||||
|
|
||||||
const validateFile = (file: File): boolean => {
|
const validateFile = (file: File): boolean => {
|
||||||
setError(null)
|
setError(null)
|
||||||
|
|
@ -35,7 +32,9 @@ export const FileUploadArea: React.FC<FileUploadAreaProps> = ({
|
||||||
// Check file type
|
// Check file type
|
||||||
const fileExtension = '.' + file.name.split('.').pop()?.toLowerCase()
|
const fileExtension = '.' + file.name.split('.').pop()?.toLowerCase()
|
||||||
if (!acceptedFormats.includes(fileExtension)) {
|
if (!acceptedFormats.includes(fileExtension)) {
|
||||||
setError(`File type not supported. Accepted formats: ${acceptedFormats.join(', ')}`)
|
setError(
|
||||||
|
translate('::App.Listforms.ImportManager.FileTypeError') + `${acceptedFormats.join(', ')}`,
|
||||||
|
)
|
||||||
return false
|
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'}`}
|
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">
|
<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>
|
</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">
|
<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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -165,12 +169,12 @@ export const FileUploadArea: React.FC<FileUploadAreaProps> = ({
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<>
|
<>
|
||||||
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-white"></div>
|
<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" />
|
<FaUpload className="w-4 h-4" />
|
||||||
<span>Upload File</span>
|
<span>{translate('::App.Listforms.ImportManager.UploadFile')}</span>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
|
|
|
||||||
|
|
@ -9,15 +9,15 @@ import {
|
||||||
FaTrashAlt,
|
FaTrashAlt,
|
||||||
FaDownload,
|
FaDownload,
|
||||||
FaFileExcel,
|
FaFileExcel,
|
||||||
FaFileAlt
|
FaFileAlt,
|
||||||
} from 'react-icons/fa';
|
} from 'react-icons/fa'
|
||||||
import { FileUploadArea } from './FileUploadArea'
|
import { FileUploadArea } from './FileUploadArea'
|
||||||
import { ImportPreview } from './ImportPreview'
|
import { ImportPreview } from './ImportPreview'
|
||||||
import { ImportProgress } from './ImportProgress'
|
import { ImportProgress } from './ImportProgress'
|
||||||
import { ListFormImportDto, ListFormImportExecuteDto } from '@/proxy/imports/models'
|
import { ListFormImportDto, ListFormImportExecuteDto } from '@/proxy/imports/models'
|
||||||
import { ImportService } from '@/services/import.service'
|
import { ImportService } from '@/services/import.service'
|
||||||
import { GridDto } from '@/proxy/form/models'
|
import { GridDto } from '@/proxy/form/models'
|
||||||
import { ColumnFormatEditDto } from '@/proxy/admin/list-form-field/models';
|
import { useLocalization } from '@/utils/hooks/useLocalization'
|
||||||
|
|
||||||
interface ImportDashboardProps {
|
interface ImportDashboardProps {
|
||||||
gridDto: GridDto
|
gridDto: GridDto
|
||||||
|
|
@ -26,6 +26,8 @@ interface ImportDashboardProps {
|
||||||
export type TabNames = 'import' | 'preview' | 'history'
|
export type TabNames = 'import' | 'preview' | 'history'
|
||||||
|
|
||||||
export const ImportDashboard: React.FC<ImportDashboardProps> = ({ gridDto }) => {
|
export const ImportDashboard: React.FC<ImportDashboardProps> = ({ gridDto }) => {
|
||||||
|
const { translate } = useLocalization()
|
||||||
|
|
||||||
const [activeTab, setActiveTab] = useState<TabNames>('import')
|
const [activeTab, setActiveTab] = useState<TabNames>('import')
|
||||||
const [currentSession, setCurrentSession] = useState<ListFormImportDto | null>(null)
|
const [currentSession, setCurrentSession] = useState<ListFormImportDto | null>(null)
|
||||||
const [importHistory, setImportHistory] = useState<ListFormImportDto[]>([])
|
const [importHistory, setImportHistory] = useState<ListFormImportDto[]>([])
|
||||||
|
|
@ -208,11 +210,7 @@ export const ImportDashboard: React.FC<ImportDashboardProps> = ({ gridDto }) =>
|
||||||
}
|
}
|
||||||
|
|
||||||
const getEditableColumns = () => {
|
const getEditableColumns = () => {
|
||||||
return gridDto.columnFormats.filter(
|
return gridDto.columnFormats.filter((col: any) => col.permissionDto.i && col.fieldName !== 'Id')
|
||||||
(col: any) =>
|
|
||||||
col.permissionDto.i &&
|
|
||||||
col.fieldName !== 'Id',
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const editableColumns = getEditableColumns()
|
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">
|
<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">
|
<h3 className="text-xl font-semibold text-slate-800 flex items-center">
|
||||||
<FaDownload className="w-4 h-4 mr-2" />
|
<FaDownload className="w-4 h-4 mr-2" />
|
||||||
Template Columns ({editableColumns.length})
|
{translate('::App.Listforms.ImportManager.TemplateColumns')} (
|
||||||
|
{editableColumns.length})
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
{/* Template Options */}
|
{/* 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"
|
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" />
|
<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>
|
||||||
|
|
||||||
<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"
|
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" />
|
<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>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -280,16 +283,16 @@ export const ImportDashboard: React.FC<ImportDashboardProps> = ({ gridDto }) =>
|
||||||
<thead className="bg-slate-100 sticky top-0">
|
<thead className="bg-slate-100 sticky top-0">
|
||||||
<tr>
|
<tr>
|
||||||
<th className="px-4 py-2 text-left text-xs font-medium text-slate-500 uppercase">
|
<th className="px-4 py-2 text-left text-xs font-medium text-slate-500 uppercase">
|
||||||
Column
|
{translate('::App.Listforms.ImportManager.Column')}
|
||||||
</th>
|
</th>
|
||||||
<th className="px-4 py-2 text-left text-xs font-medium text-slate-500 uppercase">
|
<th className="px-4 py-2 text-left text-xs font-medium text-slate-500 uppercase">
|
||||||
Type
|
{translate('::App.Listforms.ImportManager.Type')}
|
||||||
</th>
|
</th>
|
||||||
<th className="px-4 py-2 text-left text-xs font-medium text-slate-500 uppercase">
|
<th className="px-4 py-2 text-left text-xs font-medium text-slate-500 uppercase">
|
||||||
Required
|
{translate('::App.Listforms.ImportManager.Required')}
|
||||||
</th>
|
</th>
|
||||||
<th className="px-4 py-2 text-left text-xs font-medium text-slate-500 uppercase">
|
<th className="px-4 py-2 text-left text-xs font-medium text-slate-500 uppercase">
|
||||||
Default
|
{translate('::App.Listforms.ImportManager.Default')}
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
|
|
@ -318,9 +321,13 @@ export const ImportDashboard: React.FC<ImportDashboardProps> = ({ gridDto }) =>
|
||||||
{column.validationRuleDto.some(
|
{column.validationRuleDto.some(
|
||||||
(rule: any) => rule.type === 'required',
|
(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>
|
||||||
<td className="px-4 py-2 text-slate-600 text-sm">
|
<td className="px-4 py-2 text-slate-600 text-sm">
|
||||||
|
|
@ -337,7 +344,9 @@ export const ImportDashboard: React.FC<ImportDashboardProps> = ({ gridDto }) =>
|
||||||
{generating && (
|
{generating && (
|
||||||
<div className="flex items-center justify-center py-4">
|
<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>
|
<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>
|
||||||
)}
|
)}
|
||||||
</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">
|
<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">
|
<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" />
|
<FaUpload className="w-5 h-5 mr-2 text-green-500" />
|
||||||
Upload Data
|
{translate('::App.Listforms.ImportManager.UploadData')}
|
||||||
</h2>
|
</h2>
|
||||||
<FileUploadArea
|
<FileUploadArea
|
||||||
onFileUpload={handleFileUpload}
|
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="bg-white rounded-xl shadow-sm border border-slate-200 p-12">
|
||||||
<div className="text-center text-slate-500">
|
<div className="text-center text-slate-500">
|
||||||
<FaEye className="w-16 h-16 mx-auto mb-4 text-slate-300" />
|
<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 className="text-xl font-medium mb-2">
|
||||||
<div>Upload a file to see the preview here</div>
|
{translate('::App.Listforms.ImportManager.NoDataToPreview')}
|
||||||
|
</div>
|
||||||
|
<div>{translate('::App.Listforms.ImportManager.UploadFileToPreview')}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
@ -399,7 +410,7 @@ export const ImportDashboard: React.FC<ImportDashboardProps> = ({ gridDto }) =>
|
||||||
<div className="p-3 border-b border-slate-200">
|
<div className="p-3 border-b border-slate-200">
|
||||||
<h2 className="text-xl font-semibold text-slate-800 flex items-center">
|
<h2 className="text-xl font-semibold text-slate-800 flex items-center">
|
||||||
<FaClock className="w-5 h-5 mr-2 text-indigo-500" />
|
<FaClock className="w-5 h-5 mr-2 text-indigo-500" />
|
||||||
Import History
|
{translate('::App.Listforms.ImportManager.ImportHistory')}
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -425,7 +436,7 @@ export const ImportDashboard: React.FC<ImportDashboardProps> = ({ gridDto }) =>
|
||||||
</div>
|
</div>
|
||||||
{currentSession?.id === session.id && (
|
{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">
|
<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>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -434,7 +445,7 @@ export const ImportDashboard: React.FC<ImportDashboardProps> = ({ gridDto }) =>
|
||||||
<div className="flex items-center space-x-4">
|
<div className="flex items-center space-x-4">
|
||||||
<div className="text-right">
|
<div className="text-right">
|
||||||
<div className="text-sm font-medium text-slate-800">
|
<div className="text-sm font-medium text-slate-800">
|
||||||
{session.totalRows} total rows
|
{session.totalRows} {translate('::App.Listforms.ImportManager.TotalRows')}
|
||||||
</div>
|
</div>
|
||||||
</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-red-500 bg-red-50 hover:text-red-600 hover:bg-red-100'
|
||||||
: 'text-slate-400 hover:text-slate-600 hover:bg-slate-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" />
|
<FaEye className="w-4 h-4" />
|
||||||
</button>
|
</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"
|
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" />
|
<FaSync className="w-4 h-4" />
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -503,8 +514,8 @@ export const ImportDashboard: React.FC<ImportDashboardProps> = ({ gridDto }) =>
|
||||||
}`}
|
}`}
|
||||||
title={
|
title={
|
||||||
currentSession?.id === session.id
|
currentSession?.id === session.id
|
||||||
? 'Cannot delete the current active session'
|
? translate('::App.Listforms.ImportManager.CannotDeleteActiveSession')
|
||||||
: 'Delete this import session'
|
: translate('::App.Listforms.ImportManager.DeleteImportSession')
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<FaTrashAlt className="w-4 h-4" />
|
<FaTrashAlt className="w-4 h-4" />
|
||||||
|
|
@ -520,7 +531,9 @@ export const ImportDashboard: React.FC<ImportDashboardProps> = ({ gridDto }) =>
|
||||||
{loadingExecutes.has(session.id) ? (
|
{loadingExecutes.has(session.id) ? (
|
||||||
<div className="flex items-center space-x-2 text-slate-500 py-2">
|
<div className="flex items-center space-x-2 text-slate-500 py-2">
|
||||||
<FaSync className="w-4 h-4 animate-spin" />
|
<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>
|
</div>
|
||||||
) : sessionExecutes[session.id] && sessionExecutes[session.id].length > 0 ? (
|
) : sessionExecutes[session.id] && sessionExecutes[session.id].length > 0 ? (
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
|
|
@ -540,19 +553,25 @@ export const ImportDashboard: React.FC<ImportDashboardProps> = ({ gridDto }) =>
|
||||||
<div className="font-medium text-slate-800">
|
<div className="font-medium text-slate-800">
|
||||||
{execute.execRows}
|
{execute.execRows}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-slate-500">executed</div>
|
<div className="text-slate-500">
|
||||||
|
{translate('::App.Listforms.ImportManager.Executed')}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<div className="font-medium text-green-600">
|
<div className="font-medium text-green-600">
|
||||||
{execute.validRows}
|
{execute.validRows}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-slate-500">valid</div>
|
<div className="text-slate-500">
|
||||||
|
{translate('::App.Listforms.ImportManager.Valid')}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<div className="font-medium text-red-600">
|
<div className="font-medium text-red-600">
|
||||||
{execute.errorRows}
|
{execute.errorRows}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-slate-500">errors</div>
|
<div className="text-slate-500">
|
||||||
|
{translate('::App.Listforms.ImportManager.Errors')}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -592,7 +611,7 @@ export const ImportDashboard: React.FC<ImportDashboardProps> = ({ gridDto }) =>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="text-sm text-slate-500 py-2">
|
<div className="text-sm text-slate-500 py-2">
|
||||||
No execution records found
|
{translate('::App.Listforms.ImportManager.NoExecutionRecords')}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -604,8 +623,10 @@ export const ImportDashboard: React.FC<ImportDashboardProps> = ({ gridDto }) =>
|
||||||
{importHistory.length === 0 && (
|
{importHistory.length === 0 && (
|
||||||
<div className="p-12 text-center text-slate-500">
|
<div className="p-12 text-center text-slate-500">
|
||||||
<FaClock className="w-12 h-12 mx-auto mb-4 text-slate-300" />
|
<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 className="text-lg font-medium mb-2">
|
||||||
<div>Your import history will appear here</div>
|
{translate('::App.Listforms.ImportManager.NoImportHistory')}
|
||||||
|
</div>
|
||||||
|
<div>{translate('::App.Listforms.ImportManager.ImportHistoryHint')}</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,9 @@
|
||||||
import React, { useState, useEffect, useRef } from 'react'
|
import React, { useState, useEffect, useRef } from 'react'
|
||||||
import {
|
import { FaCheckCircle, FaExclamationTriangle, FaEye, FaPlay, FaTimes } from 'react-icons/fa'
|
||||||
FaCheckCircle,
|
|
||||||
FaExclamationTriangle,
|
|
||||||
FaEye,
|
|
||||||
FaPlay,
|
|
||||||
FaTimes
|
|
||||||
} from 'react-icons/fa';
|
|
||||||
import { ListFormImportDto } from '@/proxy/imports/models'
|
import { ListFormImportDto } from '@/proxy/imports/models'
|
||||||
import { GridDto } from '@/proxy/form/models'
|
import { GridDto } from '@/proxy/form/models'
|
||||||
import { ImportService } from '@/services/import.service'
|
import { ImportService } from '@/services/import.service'
|
||||||
|
import { useLocalization } from '@/utils/hooks/useLocalization'
|
||||||
|
|
||||||
interface ImportPreviewProps {
|
interface ImportPreviewProps {
|
||||||
session: ListFormImportDto
|
session: ListFormImportDto
|
||||||
|
|
@ -40,6 +35,7 @@ export const ImportPreview: React.FC<ImportPreviewProps> = ({
|
||||||
successCount: number
|
successCount: number
|
||||||
} | null>(null)
|
} | null>(null)
|
||||||
const hasCalledOnPreviewLoaded = useRef(false)
|
const hasCalledOnPreviewLoaded = useRef(false)
|
||||||
|
const { translate } = useLocalization()
|
||||||
|
|
||||||
// Selection handlers
|
// Selection handlers
|
||||||
const handleRowSelect = (rowIndex: number) => {
|
const handleRowSelect = (rowIndex: number) => {
|
||||||
|
|
@ -156,7 +152,9 @@ export const ImportPreview: React.FC<ImportPreviewProps> = ({
|
||||||
{/* Başlık kısmı - Üstte mobile, solda desktop */}
|
{/* Başlık kısmı - Üstte mobile, solda desktop */}
|
||||||
<div className="flex items-center order-1 lg:order-none">
|
<div className="flex items-center order-1 lg:order-none">
|
||||||
<FaEye className="w-5 h-5 mr-2 text-blue-500" />
|
<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>
|
</div>
|
||||||
|
|
||||||
{/* İstatistik kartları - Mobile'da alt alta, desktop'ta yan yana */}
|
{/* İ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="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">
|
<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}{' '}
|
{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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -185,7 +185,9 @@ export const ImportPreview: React.FC<ImportPreviewProps> = ({
|
||||||
{/* Preview Data */}
|
{/* Preview Data */}
|
||||||
{previewData && previewData.headers && previewData.headers.length > 0 ? (
|
{previewData && previewData.headers && previewData.headers.length > 0 ? (
|
||||||
<div className="p-3 border-b border-slate-200">
|
<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">
|
<div className="overflow-auto border border-slate-200 rounded-lg max-h-90">
|
||||||
<table className="w-full text-sm min-w-full">
|
<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="p-6 border-b border-slate-200">
|
||||||
<div className="text-center py-8">
|
<div className="text-center py-8">
|
||||||
<FaExclamationTriangle className="w-12 h-12 mx-auto mb-4 text-yellow-500" />
|
<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">
|
<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>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -253,8 +257,12 @@ export const ImportPreview: React.FC<ImportPreviewProps> = ({
|
||||||
<div className="p-6 border-b border-slate-200">
|
<div className="p-6 border-b border-slate-200">
|
||||||
<div className="text-center py-8">
|
<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>
|
<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>
|
<h4 className="font-semibold text-slate-800 mb-2">
|
||||||
<p className="text-slate-600">Analyzing uploaded file content...</p>
|
{translate('::App.Listforms.ImportManager.LoadingPreviewTitle')}
|
||||||
|
</h4>
|
||||||
|
<p className="text-slate-600">
|
||||||
|
{translate('::App.Listforms.ImportManager.LoadingPreviewDescription')}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
@ -267,8 +275,9 @@ export const ImportPreview: React.FC<ImportPreviewProps> = ({
|
||||||
<div className="flex items-center space-x-2 text-green-700">
|
<div className="flex items-center space-x-2 text-green-700">
|
||||||
<FaCheckCircle className="w-5 h-5" />
|
<FaCheckCircle className="w-5 h-5" />
|
||||||
<span className="font-medium">
|
<span className="font-medium">
|
||||||
Import completed successfully! {lastExecutionResult.successCount} of{' '}
|
{translate('::App.Listforms.ImportManager.ImportSuccessMessage')}{' '}
|
||||||
{lastExecutionResult.selectedCount} rows were imported.
|
{lastExecutionResult.successCount} of {lastExecutionResult.selectedCount} rows were
|
||||||
|
imported.
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -279,21 +288,27 @@ export const ImportPreview: React.FC<ImportPreviewProps> = ({
|
||||||
{selectedRows.length === 0 && (previewData?.rows?.length || 0) > 0 && (
|
{selectedRows.length === 0 && (previewData?.rows?.length || 0) > 0 && (
|
||||||
<div className="flex items-center space-x-2 text-orange-600">
|
<div className="flex items-center space-x-2 text-orange-600">
|
||||||
<FaExclamationTriangle className="w-5 h-5" />
|
<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>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{selectedRows.length > 0 && (
|
{selectedRows.length > 0 && (
|
||||||
<div className="flex items-center space-x-2 text-blue-600">
|
<div className="flex items-center space-x-2 text-blue-600">
|
||||||
<FaCheckCircle className="w-5 h-5" />
|
<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>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{(previewData?.rows?.length || 0) === 0 && (
|
{(previewData?.rows?.length || 0) === 0 && (
|
||||||
<div className="flex items-center space-x-2 text-red-600">
|
<div className="flex items-center space-x-2 text-red-600">
|
||||||
<FaExclamationTriangle className="w-5 h-5" />
|
<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>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -301,7 +316,7 @@ export const ImportPreview: React.FC<ImportPreviewProps> = ({
|
||||||
<div className="flex space-x-3">
|
<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">
|
<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" />
|
<FaTimes className="w-4 h-4" />
|
||||||
<span>Cancel</span>
|
<span>{translate('::App.Listforms.ImportManager.Button.Cancel')}</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
|
|
@ -312,12 +327,12 @@ export const ImportPreview: React.FC<ImportPreviewProps> = ({
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<>
|
<>
|
||||||
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-white"></div>
|
<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" />
|
<FaPlay className="w-4 h-4" />
|
||||||
<span>Execute Import</span>
|
<span>{translate('::App.Listforms.ImportManager.Button.ExecuteImport')}</span>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,15 @@
|
||||||
import React from 'react'
|
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 { ListFormImportDto } from '@/proxy/imports/models'
|
||||||
|
import { useLocalization } from '@/utils/hooks/useLocalization'
|
||||||
|
|
||||||
interface ImportProgressProps {
|
interface ImportProgressProps {
|
||||||
session: ListFormImportDto
|
session: ListFormImportDto
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ImportProgress: React.FC<ImportProgressProps> = ({ session }) => {
|
export const ImportProgress: React.FC<ImportProgressProps> = ({ session }) => {
|
||||||
|
const { translate } = useLocalization()
|
||||||
|
|
||||||
const getProgressPercentage = () => {
|
const getProgressPercentage = () => {
|
||||||
if (session.status === 'uploaded') return 100
|
if (session.status === 'uploaded') return 100
|
||||||
if (session.status === 'failed') return 0
|
if (session.status === 'failed') return 0
|
||||||
|
|
@ -27,17 +30,17 @@ export const ImportProgress: React.FC<ImportProgressProps> = ({ session }) => {
|
||||||
const getStatusMessage = () => {
|
const getStatusMessage = () => {
|
||||||
switch (session.status) {
|
switch (session.status) {
|
||||||
case 'uploading':
|
case 'uploading':
|
||||||
return 'Uploading file...'
|
return translate('::App.Listforms.ImportManager.ImportProgress.Status.Uploading')
|
||||||
case 'validating':
|
case 'validating':
|
||||||
return 'Validating data...'
|
return translate('::App.Listforms.ImportManager.ImportProgress.Status.Validating')
|
||||||
case 'processing':
|
case 'processing':
|
||||||
return 'Processing import...'
|
return translate('::App.Listforms.ImportManager.ImportProgress.Status.Processing')
|
||||||
case 'uploaded':
|
case 'uploaded':
|
||||||
return 'Import completed successfully!'
|
return translate('::App.Listforms.ImportManager.ImportProgress.Status.Uploaded')
|
||||||
case 'failed':
|
case 'failed':
|
||||||
return 'Import failed. Please check the errors.'
|
return translate('::App.Listforms.ImportManager.ImportProgress.Status.Failed')
|
||||||
default:
|
default:
|
||||||
return 'Processing...'
|
return translate('::App.Listforms.ImportManager.ImportProgress.Status.Default')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -64,13 +67,16 @@ export const ImportProgress: React.FC<ImportProgressProps> = ({ session }) => {
|
||||||
{/* Status Message */}
|
{/* Status Message */}
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-xl font-semibold text-slate-800 mb-2">{getStatusMessage()}</h3>
|
<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>
|
</div>
|
||||||
|
|
||||||
{/* Progress Bar */}
|
{/* Progress Bar */}
|
||||||
<div className="w-full max-w-md mx-auto">
|
<div className="w-full max-w-md mx-auto">
|
||||||
<div className="flex justify-between text-sm text-slate-600 mb-2">
|
<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>
|
<span>{Math.round(getProgressPercentage())}%</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="w-full bg-slate-200 rounded-full h-2">
|
<div className="w-full bg-slate-200 rounded-full h-2">
|
||||||
|
|
|
||||||
|
|
@ -2,14 +2,8 @@ import React, { useState, useMemo, useEffect } from 'react'
|
||||||
import { TemplateEditor } from '../reports/TemplateEditor'
|
import { TemplateEditor } from '../reports/TemplateEditor'
|
||||||
import { ReportGenerator } from '../reports/ReportGenerator'
|
import { ReportGenerator } from '../reports/ReportGenerator'
|
||||||
import { TemplateCard } from './TemplateCard'
|
import { TemplateCard } from './TemplateCard'
|
||||||
import { Button, Input} from '../ui'
|
import { Button, Input } from '../ui'
|
||||||
import {
|
import { FaPlus, FaSearch, FaFilter, FaFileAlt, FaChartBar } from 'react-icons/fa'
|
||||||
FaPlus,
|
|
||||||
FaSearch,
|
|
||||||
FaFilter,
|
|
||||||
FaFileAlt,
|
|
||||||
FaChartBar
|
|
||||||
} from 'react-icons/fa';
|
|
||||||
import { ReportCategoryDto, ReportTemplateDto } from '@/proxy/reports/models'
|
import { ReportCategoryDto, ReportTemplateDto } from '@/proxy/reports/models'
|
||||||
import { useReports } from '@/utils/hooks/useReports'
|
import { useReports } from '@/utils/hooks/useReports'
|
||||||
import { useLocalization } from '@/utils/hooks/useLocalization'
|
import { useLocalization } from '@/utils/hooks/useLocalization'
|
||||||
|
|
@ -25,14 +19,16 @@ export const Dashboard: React.FC = () => {
|
||||||
generateReport,
|
generateReport,
|
||||||
} = useReports()
|
} = useReports()
|
||||||
|
|
||||||
|
const { translate } = useLocalization()
|
||||||
|
|
||||||
const tumuCategory = useMemo(
|
const tumuCategory = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
id: 'tumu-category',
|
id: 'tumu-category',
|
||||||
name: 'Tümü',
|
name: translate('::App.Reports.Dashboard.All'),
|
||||||
description: 'Tüm kategorilere ait raporlar',
|
description: translate('::App.Reports.Dashboard.AllDescription'),
|
||||||
icon: '📋',
|
icon: '📋',
|
||||||
}),
|
}),
|
||||||
[],
|
[translate],
|
||||||
)
|
)
|
||||||
|
|
||||||
const [showEditor, setShowEditor] = useState(false)
|
const [showEditor, setShowEditor] = useState(false)
|
||||||
|
|
@ -40,10 +36,16 @@ export const Dashboard: React.FC = () => {
|
||||||
const [editingTemplate, setEditingTemplate] = useState<ReportTemplateDto | null>(null)
|
const [editingTemplate, setEditingTemplate] = useState<ReportTemplateDto | null>(null)
|
||||||
const [generatingTemplate, setGeneratingTemplate] = useState<ReportTemplateDto | null>(null)
|
const [generatingTemplate, setGeneratingTemplate] = useState<ReportTemplateDto | null>(null)
|
||||||
const [searchQuery, setSearchQuery] = useState('')
|
const [searchQuery, setSearchQuery] = useState('')
|
||||||
const [selectedCategory, setSelectedCategory] = useState<ReportCategoryDto | null>(tumuCategory)
|
const [selectedCategory, setSelectedCategory] = useState<ReportCategoryDto | null>(null)
|
||||||
const { translate } = useLocalization()
|
|
||||||
|
|
||||||
// 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 categoryOptions = useMemo(() => {
|
||||||
const categoryNames = [tumuCategory, ...categories.map((cat) => cat)]
|
const categoryNames = [tumuCategory, ...categories.map((cat) => cat)]
|
||||||
return categoryNames
|
return categoryNames
|
||||||
|
|
@ -52,12 +54,12 @@ export const Dashboard: React.FC = () => {
|
||||||
// Filter templates based on selected category and search query
|
// Filter templates based on selected category and search query
|
||||||
const filteredTemplates = useMemo(() => {
|
const filteredTemplates = useMemo(() => {
|
||||||
let filtered = templates
|
let filtered = templates
|
||||||
|
|
||||||
// Filter by category (if not "Tümü")
|
// Filter by category (if not "tumu-category")
|
||||||
if (selectedCategory && selectedCategory.name !== 'Tümü') {
|
if (selectedCategory && selectedCategory.id !== 'tumu-category') {
|
||||||
filtered = filtered.filter(template => template.categoryName === selectedCategory.name)
|
filtered = filtered.filter((template) => template.categoryName === selectedCategory.name)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Filter by search query
|
// Filter by search query
|
||||||
if (searchQuery) {
|
if (searchQuery) {
|
||||||
filtered = filtered.filter((template) => {
|
filtered = filtered.filter((template) => {
|
||||||
|
|
@ -106,7 +108,7 @@ export const Dashboard: React.FC = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleDeleteTemplate = async (id: string) => {
|
const handleDeleteTemplate = async (id: string) => {
|
||||||
if (window.confirm('Bu şablonu silmek istediğinizden emin misiniz?')) {
|
if (window.confirm(translate('::App.Reports.Dashboard.DeleteTemplateConfirmation'))) {
|
||||||
try {
|
try {
|
||||||
await deleteTemplate(id)
|
await deleteTemplate(id)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
@ -140,7 +142,7 @@ export const Dashboard: React.FC = () => {
|
||||||
<div className="flex items-center justify-center min-h-96">
|
<div className="flex items-center justify-center min-h-96">
|
||||||
<div className="text-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>
|
<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>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
|
|
@ -163,12 +165,12 @@ export const Dashboard: React.FC = () => {
|
||||||
{category.icon && <span className="text-lg">{category.icon}</span>}
|
{category.icon && <span className="text-lg">{category.icon}</span>}
|
||||||
<span className="font-medium">{category.name}</span>
|
<span className="font-medium">{category.name}</span>
|
||||||
</div>
|
</div>
|
||||||
{category.name !== 'Tümü' && (
|
{category.id !== 'tumu-category' && (
|
||||||
<span className="text-sm text-gray-500 ml-2">
|
<span className="text-sm text-gray-500 ml-2">
|
||||||
({templates.filter((t) => t.categoryName === category.name).length})
|
({templates.filter((t) => t.categoryName === category.name).length})
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
{category.name === 'Tümü' && (
|
{category.id === 'tumu-category' && (
|
||||||
<span className="text-sm text-gray-500 ml-2">({templates.length})</span>
|
<span className="text-sm text-gray-500 ml-2">({templates.length})</span>
|
||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -188,19 +190,19 @@ export const Dashboard: React.FC = () => {
|
||||||
{translate('::' + selectedCategory?.description)}
|
{translate('::' + selectedCategory?.description)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center space-x-4">
|
<div className="flex items-center space-x-4">
|
||||||
{/* Search Input */}
|
{/* Search Input */}
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<FaSearch className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-gray-400" />
|
<FaSearch className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-gray-400" />
|
||||||
<Input
|
<Input
|
||||||
placeholder="Şablon ara..."
|
placeholder={translate('::App.Reports.Dashboard.SearchTemplate')}
|
||||||
value={searchQuery}
|
value={searchQuery}
|
||||||
onChange={(e) => setSearchQuery(e.target.value)}
|
onChange={(e) => setSearchQuery(e.target.value)}
|
||||||
className="pl-10 w-64"
|
className="pl-10 w-64"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* New Template Button */}
|
{/* New Template Button */}
|
||||||
<Button
|
<Button
|
||||||
variant="solid"
|
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"
|
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" />
|
<FaPlus className="h-5 w-5" />
|
||||||
<span>Yeni Şablon</span>
|
<span>{translate('::App.Reports.Dashboard.NewTemplate')}</span>
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -219,7 +221,9 @@ export const Dashboard: React.FC = () => {
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<p className="text-sm font-medium text-gray-600">
|
<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>
|
||||||
<p className="text-2xl font-bold text-gray-900">{filteredTemplates.length}</p>
|
<p className="text-2xl font-bold text-gray-900">{filteredTemplates.length}</p>
|
||||||
</div>
|
</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="bg-white rounded-xl shadow-md p-6 border border-gray-200">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div>
|
<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>
|
<p className="text-2xl font-bold text-gray-900">{categories.length}</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="bg-emerald-100 p-3 rounded-full">
|
<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 className="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<p className="text-sm font-medium text-gray-600">
|
<p className="text-sm font-medium text-gray-600">
|
||||||
{selectedCategory?.name === 'Tümü' ? 'Toplam Parametre' : 'Kategori Parametreleri'}
|
{selectedCategory?.id === 'tumu-category'
|
||||||
</p>
|
? translate('::App.Reports.Dashboard.TotalParameters')
|
||||||
<p className="text-2xl font-bold text-gray-900">
|
: translate('::App.Reports.Dashboard.CategoryParameters')}
|
||||||
{totalParameters}
|
|
||||||
</p>
|
</p>
|
||||||
|
<p className="text-2xl font-bold text-gray-900">{totalParameters}</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="bg-purple-100 p-3 rounded-full">
|
<div className="bg-purple-100 p-3 rounded-full">
|
||||||
<FaChartBar className="h-6 w-6 text-purple-600" />
|
<FaChartBar className="h-6 w-6 text-purple-600" />
|
||||||
|
|
@ -257,7 +261,7 @@ export const Dashboard: React.FC = () => {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Templates Grid */}
|
{/* Templates Grid */}
|
||||||
{filteredTemplates.length === 0 ? (
|
{filteredTemplates.length === 0 ? (
|
||||||
<div className="bg-white rounded-xl shadow-md p-12 border border-gray-200 text-center">
|
<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>
|
</h3>
|
||||||
<p className="text-gray-500 mb-6">
|
<p className="text-gray-500 mb-6">
|
||||||
{templates.length === 0
|
{templates.length === 0
|
||||||
? 'İlk rapor şablonunuzu oluşturarak başlayın.'
|
? translate('::App.Reports.Dashboard.CreateFirstTemplate')
|
||||||
: 'Arama kriterlerinize uygun şablon bulunamadı.'}
|
: translate('::App.Reports.Dashboard.NoSearchResults')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
import React, { useState } from 'react'
|
import React, { useState } from 'react'
|
||||||
import { Button, Input, Dialog } from '../ui'
|
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 { ReportTemplateDto } from '@/proxy/reports/models'
|
||||||
|
import { useLocalization } from '@/utils/hooks/useLocalization'
|
||||||
|
|
||||||
interface ReportGeneratorProps {
|
interface ReportGeneratorProps {
|
||||||
isOpen: boolean
|
isOpen: boolean
|
||||||
|
|
@ -18,6 +19,8 @@ export const ReportGenerator: React.FC<ReportGeneratorProps> = ({
|
||||||
}) => {
|
}) => {
|
||||||
const [parameterValues, setParameterValues] = useState<Record<string, string>>({})
|
const [parameterValues, setParameterValues] = useState<Record<string, string>>({})
|
||||||
const [isGenerating, setIsGenerating] = useState(false)
|
const [isGenerating, setIsGenerating] = useState(false)
|
||||||
|
const { translate } = useLocalization()
|
||||||
|
|
||||||
// const [showPrintPreview, setShowPrintPreview] = useState(false);
|
// const [showPrintPreview, setShowPrintPreview] = useState(false);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
|
|
@ -69,7 +72,7 @@ export const ReportGenerator: React.FC<ReportGeneratorProps> = ({
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Dialog isOpen={isOpen} onClose={onClose} width="60%">
|
<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="space-y-6">
|
||||||
<div className="bg-gray-50 p-4 rounded-lg">
|
<div className="bg-gray-50 p-4 rounded-lg">
|
||||||
<h3 className="font-medium text-gray-900 mb-2">{template.name}</h3>
|
<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 ? (
|
{template.parameters.length > 0 ? (
|
||||||
<div className="space-y-4">
|
<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">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
{template.parameters.map((param) => (
|
{template.parameters.map((param) => (
|
||||||
<Input
|
<Input
|
||||||
|
|
@ -102,27 +107,33 @@ export const ReportGenerator: React.FC<ReportGeneratorProps> = ({
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
{template.parameters.some((p) => p.required) && (
|
{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>
|
||||||
) : (
|
) : (
|
||||||
<div className="py-8 text-gray-500">
|
<div className="py-8 text-gray-500">
|
||||||
<FaFileAlt className="h-12 w-12 text-gray-400 mx-auto mb-4" />
|
<FaFileAlt className="h-12 w-12 text-gray-400 mx-auto mb-4" />
|
||||||
<p>Bu şablon için parametre tanımlanmamış.</p>
|
<p>{translate('::App.Reports.ReportGenerator.NoParameters')}</p>
|
||||||
<p className="text-sm">Direkt rapor oluşturabilirsiniz.</p>
|
<p className="text-sm">{translate('::App.Reports.ReportGenerator.DirectGenerate')}</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="flex justify-end space-x-3">
|
<div className="flex justify-end space-x-3">
|
||||||
<Button onClick={onClose} disabled={isGenerating}>
|
<Button onClick={onClose} disabled={isGenerating}>
|
||||||
İptal
|
{translate('::App.Reports.ReportGenerator.Cancel')}
|
||||||
</Button>
|
</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"
|
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" />
|
<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>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import {
|
||||||
headerValues,
|
headerValues,
|
||||||
sizeValues,
|
sizeValues,
|
||||||
} from '@/proxy/reports/data'
|
} from '@/proxy/reports/data'
|
||||||
|
import { useLocalization } from '@/utils/hooks/useLocalization'
|
||||||
|
|
||||||
interface ReportHtmlEditorProps {
|
interface ReportHtmlEditorProps {
|
||||||
value: string
|
value: string
|
||||||
|
|
@ -20,16 +21,17 @@ interface ReportHtmlEditorProps {
|
||||||
export const ReportHtmlEditor: React.FC<ReportHtmlEditorProps> = ({
|
export const ReportHtmlEditor: React.FC<ReportHtmlEditorProps> = ({
|
||||||
value,
|
value,
|
||||||
onChange,
|
onChange,
|
||||||
placeholder = 'Rapor şablonunuzu buraya yazın...',
|
|
||||||
height = '100%',
|
height = '100%',
|
||||||
}) => {
|
}) => {
|
||||||
|
const { translate } = useLocalization()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col space-y-2" style={{ height }}>
|
<div className="flex flex-col space-y-2" style={{ height }}>
|
||||||
<HtmlEditor
|
<HtmlEditor
|
||||||
value={value}
|
value={value}
|
||||||
onValueChanged={(e) => onChange(e.value)}
|
onValueChanged={(e) => onChange(e.value)}
|
||||||
height={height || '400px'}
|
height={height || '400px'}
|
||||||
placeholder={placeholder}
|
placeholder={translate('::App.Reports.ReportHtmlViewer.Placeholder')}
|
||||||
>
|
>
|
||||||
<MediaResizing enabled={true} />
|
<MediaResizing enabled={true} />
|
||||||
<ImageUpload fileUploadMode="base64" />
|
<ImageUpload fileUploadMode="base64" />
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ import jsPDF from 'jspdf'
|
||||||
import { ReportGeneratedDto, ReportTemplateDto } from '@/proxy/reports/models'
|
import { ReportGeneratedDto, ReportTemplateDto } from '@/proxy/reports/models'
|
||||||
import { useReports } from '@/utils/hooks/useReports'
|
import { useReports } from '@/utils/hooks/useReports'
|
||||||
import { ROUTES_ENUM } from '@/routes/route.constant'
|
import { ROUTES_ENUM } from '@/routes/route.constant'
|
||||||
|
import { useLocalization } from '@/utils/hooks/useLocalization'
|
||||||
|
|
||||||
export const ReportViewer: React.FC = () => {
|
export const ReportViewer: React.FC = () => {
|
||||||
const { id } = useParams<{ id: string }>()
|
const { id } = useParams<{ id: string }>()
|
||||||
|
|
@ -23,6 +24,7 @@ export const ReportViewer: React.FC = () => {
|
||||||
const [template, setTemplate] = useState<ReportTemplateDto | null>(null)
|
const [template, setTemplate] = useState<ReportTemplateDto | null>(null)
|
||||||
const [isLoading, setIsLoading] = useState(true)
|
const [isLoading, setIsLoading] = useState(true)
|
||||||
const [error, setError] = useState<string | null>(null)
|
const [error, setError] = useState<string | null>(null)
|
||||||
|
const { translate } = useLocalization()
|
||||||
|
|
||||||
const { getReportById, getTemplateById } = useReports()
|
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="min-h-screen bg-gray-50 flex items-center justify-center">
|
||||||
<div className="text-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>
|
<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>
|
<h1 className="text-xl font-semibold text-gray-900 mb-2">
|
||||||
<p className="text-gray-600">Lütfen bekleyin</p>
|
{translate('::App.Reports.ReportViewer.LoadingTitle')}
|
||||||
|
</h1>
|
||||||
|
<p className="text-gray-600">{translate('::App.Reports.ReportViewer.LoadingSubtitle')}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
@ -136,7 +140,9 @@ export const ReportViewer: React.FC = () => {
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-gray-50 flex items-center justify-center">
|
<div className="min-h-screen bg-gray-50 flex items-center justify-center">
|
||||||
<div className="text-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)}>
|
<Button onClick={() => navigate(ROUTES_ENUM.protected.dashboard)}>
|
||||||
<FaArrowLeft className="h-4 w-4 mr-2" />
|
<FaArrowLeft className="h-4 w-4 mr-2" />
|
||||||
Ana Sayfaya Dön
|
Ana Sayfaya Dön
|
||||||
|
|
@ -150,7 +156,9 @@ export const ReportViewer: React.FC = () => {
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-gray-50 flex items-center justify-center">
|
<div className="min-h-screen bg-gray-50 flex items-center justify-center">
|
||||||
<div className="text-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)}>
|
<Button onClick={() => navigate(ROUTES_ENUM.protected.dashboard)}>
|
||||||
<FaArrowLeft className="h-4 w-4 mr-2" />
|
<FaArrowLeft className="h-4 w-4 mr-2" />
|
||||||
Ana Sayfaya Dön
|
Ana Sayfaya Dön
|
||||||
|
|
@ -165,13 +173,15 @@ export const ReportViewer: React.FC = () => {
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-gray-50 flex items-center justify-center">
|
<div className="min-h-screen bg-gray-50 flex items-center justify-center">
|
||||||
<div className="text-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">
|
<p className="text-gray-600 mb-6">
|
||||||
Aradığınız rapor mevcut değil veya silinmiş olabilir.
|
{translate('::App.Reports.ReportViewer.ErrorNotFoundDescription')}
|
||||||
</p>
|
</p>
|
||||||
<Button onClick={() => navigate(ROUTES_ENUM.protected.dashboard)}>
|
<Button onClick={() => navigate(ROUTES_ENUM.protected.dashboard)}>
|
||||||
<FaArrowLeft className="h-4 w-4 mr-2" />
|
<FaArrowLeft className="h-4 w-4 mr-2" />
|
||||||
Ana Sayfaya Dön
|
{translate('::App.Reports.ReportViewer.BackToDashboard')}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</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"
|
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" />
|
<FaDownload className="h-4 w-4 mr-2" />
|
||||||
PDF İndir
|
{translate('::App.Reports.ReportViewer.DownloadPDF')}
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant="solid" onClick={handlePrint}>
|
<Button variant="solid" onClick={handlePrint}>
|
||||||
Yazdır
|
{translate('::App.Reports.ReportViewer.Print')}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -473,7 +483,8 @@ export const ReportViewer: React.FC = () => {
|
||||||
|
|
||||||
{/* Sayfa Footer - Sayfa Numarası */}
|
{/* Sayfa Footer - Sayfa Numarası */}
|
||||||
<div className="page-footer">
|
<div className="page-footer">
|
||||||
Sayfa {index + 1} / {splitContentIntoPages(report.generatedContent).length}
|
{translate('::App.Reports.ReportViewer.Page')} {index + 1} /{' '}
|
||||||
|
{splitContentIntoPages(report.generatedContent).length}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import React from 'react'
|
||||||
import { Button } from '../ui/Button'
|
import { Button } from '../ui/Button'
|
||||||
import { FaFileAlt, FaEdit, FaTrash, FaPlay } from 'react-icons/fa';
|
import { FaFileAlt, FaEdit, FaTrash, FaPlay } from 'react-icons/fa';
|
||||||
import { ReportTemplateDto } from '@/proxy/reports/models'
|
import { ReportTemplateDto } from '@/proxy/reports/models'
|
||||||
|
import { useLocalization } from '@/utils/hooks/useLocalization';
|
||||||
|
|
||||||
interface TemplateCardProps {
|
interface TemplateCardProps {
|
||||||
template: ReportTemplateDto
|
template: ReportTemplateDto
|
||||||
|
|
@ -16,6 +17,8 @@ export const TemplateCard: React.FC<TemplateCardProps> = ({
|
||||||
onDelete,
|
onDelete,
|
||||||
onGenerate,
|
onGenerate,
|
||||||
}) => {
|
}) => {
|
||||||
|
const { translate } = useLocalization()
|
||||||
|
|
||||||
return (
|
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="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">
|
<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"
|
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" />
|
<FaPlay className="h-3 w-3" />
|
||||||
<span className="truncate">Göster</span>
|
<span className="truncate">{translate('::App.Reports.TemplateCard.Show')}</span>
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<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"
|
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" />
|
<FaEdit className="h-3 w-3" />
|
||||||
<span className="truncate">Düzenle</span>
|
<span className="truncate">{translate('::App.Reports.TemplateCard.Edit')}</span>
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<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"
|
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" />
|
<FaTrash className="h-3 w-3" />
|
||||||
<span className="truncate">Sil</span>
|
<span className="truncate">{translate('::App.Reports.TemplateCard.Delete')}</span>
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -138,15 +138,23 @@ export const TemplateEditor: React.FC<TemplateEditorProps> = ({
|
||||||
}
|
}
|
||||||
|
|
||||||
const tabs = [
|
const tabs = [
|
||||||
{ id: 'info', label: translate('::Şablon Bilgileri'), icon: FaFileAlt },
|
{ id: 'info', label: translate('::App.Reports.TemplateEditor.Tab.Info'), icon: FaFileAlt },
|
||||||
{ id: 'parameters', label: translate('::Parametreler'), icon: FaCog },
|
{
|
||||||
{ id: 'content', label: translate('::HTML İçerik'), icon: FaCode },
|
id: 'parameters',
|
||||||
|
label: translate('::App.Reports.TemplateEditor.Tab.Parameters'),
|
||||||
|
icon: FaCog,
|
||||||
|
},
|
||||||
|
{ id: 'content', label: translate('::App.Reports.TemplateEditor.Tab.Content'), icon: FaCode },
|
||||||
]
|
]
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Dialog isOpen={isOpen} onClose={onClose} width="100%">
|
<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">
|
<div className="flex flex-col h-full">
|
||||||
{/* Tab Navigation */}
|
{/* Tab Navigation */}
|
||||||
<div className="border-b border-gray-200 bg-gray-50 flex-shrink-0">
|
<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 className="space-y-6">
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||||
Şablon Adı
|
{translate('::App.Reports.TemplateEditor.Label.Name')}
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
autoFocus
|
autoFocus
|
||||||
|
|
@ -195,14 +203,14 @@ export const TemplateEditor: React.FC<TemplateEditorProps> = ({
|
||||||
name: e.target.value,
|
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"
|
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>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||||
Kategori
|
{translate('::App.Reports.TemplateEditor.Label.Category')}
|
||||||
</label>
|
</label>
|
||||||
<select
|
<select
|
||||||
value={formData.categoryName}
|
value={formData.categoryName}
|
||||||
|
|
@ -224,7 +232,7 @@ export const TemplateEditor: React.FC<TemplateEditorProps> = ({
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||||
Etiketler
|
{translate('::App.Reports.TemplateEditor.Label.Tags')}
|
||||||
</label>
|
</label>
|
||||||
<div className="flex space-x-2 mb-3">
|
<div className="flex space-x-2 mb-3">
|
||||||
<input
|
<input
|
||||||
|
|
@ -232,11 +240,13 @@ export const TemplateEditor: React.FC<TemplateEditorProps> = ({
|
||||||
value={tagInput}
|
value={tagInput}
|
||||||
onChange={(e) => setTagInput(e.target.value)}
|
onChange={(e) => setTagInput(e.target.value)}
|
||||||
onKeyPress={(e) => e.key === 'Enter' && addTag()}
|
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"
|
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">
|
<Button type="button" onClick={addTag} size="sm">
|
||||||
Ekle
|
{translate('::App.Reports.TemplateEditor.Button.Add')}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-wrap gap-2">
|
<div className="flex flex-wrap gap-2">
|
||||||
|
|
@ -262,7 +272,7 @@ export const TemplateEditor: React.FC<TemplateEditorProps> = ({
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||||
Şablon Açıklaması
|
{translate('::App.Reports.TemplateEditor.Label.Description')}
|
||||||
</label>
|
</label>
|
||||||
<Input
|
<Input
|
||||||
value={formData.description}
|
value={formData.description}
|
||||||
|
|
@ -272,7 +282,9 @@ export const TemplateEditor: React.FC<TemplateEditorProps> = ({
|
||||||
description: e.target.value,
|
description: e.target.value,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
placeholder="Şablon hakkında detaylı açıklama yazın..."
|
placeholder={translate(
|
||||||
|
'::App.Reports.TemplateEditor.Placeholder.Description',
|
||||||
|
)}
|
||||||
textArea={true}
|
textArea={true}
|
||||||
rows={12}
|
rows={12}
|
||||||
className="text-left h-full"
|
className="text-left h-full"
|
||||||
|
|
@ -304,10 +316,11 @@ export const TemplateEditor: React.FC<TemplateEditorProps> = ({
|
||||||
{formData.parameters.length === 0 ? (
|
{formData.parameters.length === 0 ? (
|
||||||
<div className="text-center py-12">
|
<div className="text-center py-12">
|
||||||
<FaCog className="h-16 w-16 text-gray-400 mx-auto mb-4" />
|
<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">
|
<p className="text-gray-400 text-sm">
|
||||||
HTML içeriğinde @@PARAMETRE formatında parametreler kullandığınızda burada
|
{translate('::App.Reports.TemplateEditor.NoParametersDescription')}
|
||||||
görünecek.
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
|
|
@ -346,7 +359,9 @@ export const TemplateEditor: React.FC<TemplateEditorProps> = ({
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
updateParameter(param.id, { description: e.target.value })
|
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"
|
className="w-full text-xs text-gray-600 bg-transparent border-none outline-none resize-none"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -358,7 +373,9 @@ export const TemplateEditor: React.FC<TemplateEditorProps> = ({
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
updateParameter(param.id, { defaultValue: e.target.value })
|
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"
|
className="w-full text-xs bg-gray-50 px-1.5 py-0.5 rounded border border-gray-200 outline-none"
|
||||||
/>
|
/>
|
||||||
</div>
|
</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"
|
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>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -388,7 +407,7 @@ export const TemplateEditor: React.FC<TemplateEditorProps> = ({
|
||||||
{/* Tab Footer */}
|
{/* Tab Footer */}
|
||||||
<div className="flex justify-between">
|
<div className="flex justify-between">
|
||||||
<Button variant="default" onClick={onClose} disabled={isSaving}>
|
<Button variant="default" onClick={onClose} disabled={isSaving}>
|
||||||
İptal
|
{translate('::App.Reports.TemplateEditor.Button.Cancel')}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<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"
|
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" />
|
<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>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue