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