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) public async Task<PagedResultDto<ReportTemplateDto>> GetTemplatesAsync(GetReportTemplatesInput input)
{ {
// IQueryable başlat // IQueryable başlat - Parameters'ı include et
var query = await _reportTemplateRepository.GetQueryableAsync(); var query = await _reportTemplateRepository.GetQueryableAsync();
query = query.Include(x => x.Parameters);
// Filtreleme // Filtreleme
if (!string.IsNullOrWhiteSpace(input.Filter)) if (!string.IsNullOrWhiteSpace(input.Filter))
@ -84,7 +85,15 @@ public class ReportAppService : PlatformAppService, IReportAppService
public async Task<ReportTemplateDto> GetTemplateAsync(Guid id) 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); return MapToReportTemplateDto(template);
} }

View file

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

View file

@ -72,7 +72,7 @@ export const ReportGenerator: React.FC<ReportGeneratorProps> = ({
<> <>
<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} - Rapor Parametreleri`}</h5>
<div className="p-6 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>
<p className="text-gray-600 text-sm">{template.description}</p> <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 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'
export const ReportViewer: React.FC = () => { export const ReportViewer: React.FC = () => {
const { id } = useParams<{ id: string }>() 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="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 || 'Rapor bulunamadı'}</h1>
<Button onClick={() => navigate('/')}> <Button onClick={() => navigate(ROUTES_ENUM.protected.dashboard)}>
<ArrowLeft className="h-4 w-4 mr-2" /> <ArrowLeft className="h-4 w-4 mr-2" />
Ana Sayfaya Dön Ana Sayfaya Dön
</Button> </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="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">Rapor bulunamadı</h1>
<Button onClick={() => navigate('/')}> <Button onClick={() => navigate(ROUTES_ENUM.protected.dashboard)}>
<ArrowLeft className="h-4 w-4 mr-2" /> <ArrowLeft className="h-4 w-4 mr-2" />
Ana Sayfaya Dön Ana Sayfaya Dön
</Button> </Button>
@ -161,7 +162,7 @@ export const ReportViewer: React.FC = () => {
<p className="text-gray-600 mb-6"> <p className="text-gray-600 mb-6">
Aradığınız rapor mevcut değil veya silinmiş olabilir. Aradığınız rapor mevcut değil veya silinmiş olabilir.
</p> </p>
<Button onClick={() => navigate('/')}> <Button onClick={() => navigate(ROUTES_ENUM.protected.dashboard)}>
<ArrowLeft className="h-4 w-4 mr-2" /> <ArrowLeft className="h-4 w-4 mr-2" />
Ana Sayfaya Dön Ana Sayfaya Dön
</Button> </Button>
@ -234,7 +235,7 @@ export const ReportViewer: React.FC = () => {
<div className="min-h-screen bg-gray-50"> <div className="min-h-screen bg-gray-50">
{/* Header - Print edilmeyecek */} {/* Header - Print edilmeyecek */}
<div className="print:hidden bg-white shadow-sm border-b border-gray-200 sticky top-0 z-10"> <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 justify-between h-16">
<div className="flex items-center space-x-4 flex-1"> <div className="flex items-center space-x-4 flex-1">
<div className="flex-1"> <div className="flex-1">

View file

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

View file

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

View file

@ -1,6 +1,7 @@
import { Cart } from '@/components/orders/Cart' import { Cart } from '@/components/orders/Cart'
import { TenantForm } from '@/components/orders/TenantForm' import { TenantForm } from '@/components/orders/TenantForm'
import { CustomTenantDto } from '@/proxy/config/models' import { CustomTenantDto } from '@/proxy/config/models'
import { ROUTES_ENUM } from '@/routes/route.constant'
import { import {
CartState, CartState,
clearCart, clearCart,
@ -33,16 +34,13 @@ const Checkout: React.FC = () => {
} }
const handleBackToCatalog = () => { const handleBackToCatalog = () => {
navigate('/') navigate(ROUTES_ENUM.public.home)
} }
const handleCartClick = () => {
setIsCartOpen(true)
}
const handleCheckout = () => { const handleCheckout = () => {
setIsCartOpen(false) setIsCartOpen(false)
navigate('/checkout') navigate(ROUTES_ENUM.public.checkout)
} }
const handleUpdateQuantity = (id: string, quantity: number) => { 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 { Cart } from '@/components/orders/Cart'
import { ProductCatalog } from '@/components/orders/ProductCatalog' import { ProductCatalog } from '@/components/orders/ProductCatalog'
import { BillingCycle } from '@/proxy/order/models' import { BillingCycle } from '@/proxy/order/models'
import { ROUTES_ENUM } from '@/routes/route.constant'
import { import {
CartState, CartState,
clearCart, clearCart,
@ -40,10 +41,10 @@ const Products: React.FC = () => {
const handleNavigate = (section: string) => { const handleNavigate = (section: string) => {
switch (section) { switch (section) {
case 'home': case 'home':
navigate('/') navigate(ROUTES_ENUM.protected.dashboard)
break break
default: default:
navigate('/') navigate(ROUTES_ENUM.public.home)
} }
} }

View file

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