Report Viewer Edit ve Parameters değişikllikleri

This commit is contained in:
Sedat Öztürk 2025-08-15 23:27:19 +03:00
parent 82958c1872
commit c5c7060a52
9 changed files with 109 additions and 87 deletions

View file

@ -42,8 +42,9 @@ public class ReportAppService : PlatformAppService, IReportAppService
public async Task<PagedResultDto<ReportTemplateDto>> GetTemplatesAsync(GetReportTemplatesInput input)
{
// IQueryable başlat
// IQueryable başlat - Parameters'ı include et
var query = await _reportTemplateRepository.GetQueryableAsync();
query = query.Include(x => x.Parameters);
// Filtreleme
if (!string.IsNullOrWhiteSpace(input.Filter))
@ -84,7 +85,15 @@ public class ReportAppService : PlatformAppService, IReportAppService
public async Task<ReportTemplateDto> GetTemplateAsync(Guid id)
{
var template = await _reportTemplateRepository.GetAsync(id);
var query = await _reportTemplateRepository.GetQueryableAsync();
var template = await query
.Include(x => x.Parameters)
.FirstOrDefaultAsync(x => x.Id == id);
if (template == null)
{
throw new ArgumentException($"Template with id {id} not found");
}
return MapToReportTemplateDto(template);
}

View file

@ -18,7 +18,6 @@ export const Dashboard: React.FC = () => {
updateTemplate,
deleteTemplate,
generateReport,
loadTemplatesByCategory,
} = useReports()
const tumuCategory = useMemo(
@ -36,7 +35,7 @@ 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>(null)
const [selectedCategory, setSelectedCategory] = useState<ReportCategoryDto | null>(tumuCategory)
const { translate } = useLocalization()
// Create category options with "Tümü" at the top
@ -45,35 +44,36 @@ export const Dashboard: React.FC = () => {
return categoryNames
}, [categories, tumuCategory])
// Set default category when component mounts
useEffect(() => {
if (!selectedCategory) {
setSelectedCategory(tumuCategory)
}
}, [tumuCategory, selectedCategory])
// Handle category change
useEffect(() => {
if (selectedCategory) {
loadTemplatesByCategory(selectedCategory.name)
}
}, [selectedCategory, loadTemplatesByCategory])
// Load templates for default category on mount
useEffect(() => {
loadTemplatesByCategory('Tümü')
}, [])
// Filter templates based on selected category and search query
const filteredTemplates = useMemo(() => {
return templates.filter((template) => {
const matchesSearch =
template.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
template.description?.toLowerCase().includes(searchQuery.toLowerCase()) ||
template.tags.some((tag: any) => tag.toLowerCase().includes(searchQuery.toLowerCase()))
let filtered = templates
return matchesSearch
})
}, [templates, searchQuery])
// Filter by category (if not "Tümü")
if (selectedCategory && selectedCategory.name !== 'Tümü') {
filtered = filtered.filter(template => template.categoryName === selectedCategory.name)
}
// Filter by search query
if (searchQuery) {
filtered = filtered.filter((template) => {
const matchesSearch =
template.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
template.description?.toLowerCase().includes(searchQuery.toLowerCase()) ||
template.tags.some((tag: any) => tag.toLowerCase().includes(searchQuery.toLowerCase()))
return matchesSearch
})
}
return filtered
}, [templates, selectedCategory, searchQuery])
// Calculate total parameters
const totalParameters = useMemo(() => {
return filteredTemplates.reduce((sum, template) => {
return sum + (Array.isArray(template.parameters) ? template.parameters.length : 0)
}, 0)
}, [filteredTemplates])
const handleCreateTemplate = () => {
setEditingTemplate(null)
@ -174,21 +174,38 @@ export const Dashboard: React.FC = () => {
{/* Main Content */}
<div className="flex-1">
<div className="flex items-center justify-between">
<h2 className="text-2xl font-bold text-gray-900">
{translate('::' + selectedCategory?.name)}
</h2>
<Button
variant="solid"
onClick={handleCreateTemplate}
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"
>
<Plus className="h-5 w-5" />
<span>Yeni Şablon</span>
</Button>
</div>
<div className="text-sm text-gray-500 ml-1 mb-4">
{translate('::' + selectedCategory?.description)}
<div className="flex items-center justify-between mb-4">
<div className="flex-1">
<h2 className="text-2xl font-bold text-gray-900">
{translate('::' + selectedCategory?.name)}
</h2>
<div className="text-sm text-gray-500 ml-1">
{translate('::' + selectedCategory?.description)}
</div>
</div>
<div className="flex items-center space-x-4">
{/* Search Input */}
<div className="relative">
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-gray-400" />
<Input
placeholder="Şablon ara..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="pl-10 w-64"
/>
</div>
{/* New Template Button */}
<Button
variant="solid"
onClick={handleCreateTemplate}
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"
>
<Plus className="h-5 w-5" />
<span>Yeni Şablon</span>
</Button>
</div>
</div>
{/* Stats */}
@ -196,8 +213,10 @@ 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">Toplam Şablon</p>
<p className="text-2xl font-bold text-gray-900">{templates.length}</p>
<p className="text-sm font-medium text-gray-600">
{selectedCategory?.name === 'Tümü' ? 'Toplam Şablon' : 'Kategori Şablonları'}
</p>
<p className="text-2xl font-bold text-gray-900">{filteredTemplates.length}</p>
</div>
<div className="bg-blue-100 p-3 rounded-full">
<FileText className="h-6 w-6 text-blue-600" />
@ -220,9 +239,11 @@ 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">Toplam Parametre</p>
<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">
{templates.reduce((sum, t) => sum + t.parameters.length, 0)}
{totalParameters}
</p>
</div>
<div className="bg-purple-100 p-3 rounded-full">
@ -231,20 +252,7 @@ export const Dashboard: React.FC = () => {
</div>
</div>
</div>
{/* Filters */}
<div className="bg-white rounded-xl shadow-md p-6 mb-8 border border-gray-200">
<div className="flex-1">
<div className="relative">
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-gray-400" />
<Input
placeholder="Şablon ara..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="pl-10"
/>
</div>
</div>
</div>
{/* Templates Grid */}
{filteredTemplates.length === 0 ? (
<div className="bg-white rounded-xl shadow-md p-12 border border-gray-200 text-center">

View file

@ -72,7 +72,7 @@ export const ReportGenerator: React.FC<ReportGeneratorProps> = ({
<>
<Dialog isOpen={isOpen} onClose={onClose} width="60%">
<h5 className="mb-4">{`${template.name} - Rapor Parametreleri`}</h5>
<div className="p-6 space-y-6">
<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>
<p className="text-gray-600 text-sm">{template.description}</p>

View file

@ -6,6 +6,7 @@ import html2canvas from 'html2canvas'
import jsPDF from 'jspdf'
import { ReportGeneratedDto, ReportTemplateDto } from '@/proxy/reports/models'
import { useReports } from '@/utils/hooks/useReports'
import { ROUTES_ENUM } from '@/routes/route.constant'
export const ReportViewer: React.FC = () => {
const { id } = useParams<{ id: string }>()
@ -129,7 +130,7 @@ export const ReportViewer: React.FC = () => {
<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>
<Button onClick={() => navigate('/')}>
<Button onClick={() => navigate(ROUTES_ENUM.protected.dashboard)}>
<ArrowLeft className="h-4 w-4 mr-2" />
Ana Sayfaya Dön
</Button>
@ -143,7 +144,7 @@ export const ReportViewer: React.FC = () => {
<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>
<Button onClick={() => navigate('/')}>
<Button onClick={() => navigate(ROUTES_ENUM.protected.dashboard)}>
<ArrowLeft className="h-4 w-4 mr-2" />
Ana Sayfaya Dön
</Button>
@ -161,7 +162,7 @@ export const ReportViewer: React.FC = () => {
<p className="text-gray-600 mb-6">
Aradığınız rapor mevcut değil veya silinmiş olabilir.
</p>
<Button onClick={() => navigate('/')}>
<Button onClick={() => navigate(ROUTES_ENUM.protected.dashboard)}>
<ArrowLeft className="h-4 w-4 mr-2" />
Ana Sayfaya Dön
</Button>
@ -234,7 +235,7 @@ export const ReportViewer: React.FC = () => {
<div className="min-h-screen bg-gray-50">
{/* Header - Print edilmeyecek */}
<div className="print:hidden bg-white shadow-sm border-b border-gray-200 sticky top-0 z-10">
<div className="w-full px-4 sm:px-6 lg:px-8">
<div className="w-full px-4">
<div className="flex items-center justify-between h-16">
<div className="flex items-center space-x-4 flex-1">
<div className="flex-1">

View file

@ -51,8 +51,8 @@ export const TemplateCard: React.FC<TemplateCardProps> = ({
</div>
{/* Footer with date and actions */}
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-2 mt-auto pt-3 border-t border-gray-100">
<div className="text-xs text-gray-500 flex-shrink-0">
<div className="flex flex-col gap-3 mt-auto pt-3 border-t border-gray-100">
<div className="text-xs text-gray-500">
<p className="truncate">{new Date(template.lastModificationTime || template.creationTime).toLocaleString('tr-TR', {
day: '2-digit',
month: '2-digit',
@ -62,34 +62,35 @@ export const TemplateCard: React.FC<TemplateCardProps> = ({
})}</p>
</div>
<div className="flex items-center gap-1 flex-shrink-0">
<div className="flex items-center justify-center gap-2 w-full">
<Button
variant="solid"
size="sm"
onClick={() => onGenerate(template)}
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-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"
>
<Play className="h-3 w-3" />
<span className="hidden sm:inline">Üret</span>
<span className="truncate">Göster</span>
</Button>
<Button
variant="solid"
variant="default"
size="sm"
onClick={() => onEdit(template)}
className="bg-gray-600 hover:bg-gray-700 font-medium px-2 sm:px-3 py-1.5 rounded text-xs flex items-center gap-1"
className="font-medium px-3 py-1.5 rounded text-xs flex items-center gap-1 flex-1 justify-center min-w-0"
>
<Edit className="h-3 w-3" />
<span className="hidden sm:inline">Düzenle</span>
<span className="truncate">Düzenle</span>
</Button>
<Button
variant="solid"
size="sm"
onClick={() => onDelete(template.id)}
className="bg-red-600 hover:bg-red-700 font-medium px-2 py-1.5 rounded text-xs flex items-center"
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"
>
<Trash2 className="h-3 w-3" />
<span className="truncate">Sil</span>
</Button>
</div>
</div>

View file

@ -3,6 +3,9 @@ export const ROUTES_ENUM = {
home: '/home',
about: '/about',
products: '/products',
checkout: '/checkout',
success: '/success',
payment: '/payment',
services: '/services',
demo: '/demo',
blog: '/blog',

View file

@ -1,6 +1,7 @@
import { Cart } from '@/components/orders/Cart'
import { TenantForm } from '@/components/orders/TenantForm'
import { CustomTenantDto } from '@/proxy/config/models'
import { ROUTES_ENUM } from '@/routes/route.constant'
import {
CartState,
clearCart,
@ -33,16 +34,13 @@ const Checkout: React.FC = () => {
}
const handleBackToCatalog = () => {
navigate('/')
navigate(ROUTES_ENUM.public.home)
}
const handleCartClick = () => {
setIsCartOpen(true)
}
const handleCheckout = () => {
setIsCartOpen(false)
navigate('/checkout')
navigate(ROUTES_ENUM.public.checkout)
}
const handleUpdateQuantity = (id: string, quantity: number) => {

View file

@ -2,6 +2,7 @@ import { BillingControls } from '@/components/orders/BillingControls'
import { Cart } from '@/components/orders/Cart'
import { ProductCatalog } from '@/components/orders/ProductCatalog'
import { BillingCycle } from '@/proxy/order/models'
import { ROUTES_ENUM } from '@/routes/route.constant'
import {
CartState,
clearCart,
@ -40,10 +41,10 @@ const Products: React.FC = () => {
const handleNavigate = (section: string) => {
switch (section) {
case 'home':
navigate('/')
navigate(ROUTES_ENUM.protected.dashboard)
break
default:
navigate('/')
navigate(ROUTES_ENUM.public.home)
}
}

View file

@ -1,4 +1,5 @@
import { OrderSuccess } from '@/components/orders/OrderSuccess'
import { ROUTES_ENUM } from '@/routes/route.constant'
import { useLocalization } from '@/utils/hooks/useLocalization'
import React, { useState, useEffect } from 'react'
import { Helmet } from 'react-helmet'
@ -17,12 +18,12 @@ const Success: React.FC = () => {
// Clear order ID from local storage
localStorage.removeItem('orderId')
} else {
navigate('/')
navigate(ROUTES_ENUM.public.home)
}
}, [navigate])
const handleBackToShop = () => {
navigate('/')
navigate(ROUTES_ENUM.public.home)
}
if (!orderId) {