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 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>

View file

@ -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>

View file

@ -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>

View file

@ -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">

View file

@ -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>
) : ( ) : (

View file

@ -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>

View file

@ -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" />

View file

@ -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>
))} ))}

View file

@ -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>

View file

@ -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 ı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ıı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>